【问题标题】:Check whether anything new in the local git history compared to remote与远程相比,检查本地 git 历史记录中是否有任何新内容
【发布时间】:2019-10-14 07:45:37
【问题描述】:

我想(以编程方式)确保远程 git 历史记录的本地副本是准确的,也就是说,本地 git 历史记录包含与远程 git 历史记录完全相同的历史记录(不多也不少)。

实现此目的的可靠方法是删除我的本地副本并再次克隆远程。但是我想在相对容易的情况下节省时间和带宽(在典型情况下,自上次运行我的程序以来没有任何变化;因此每次运行时都没有必要再次下载所有内容)。

(我使用 JGit 在 Java 中编程,但我认为使用 git 命令行的答案也可以,因为它应该很容易转换为 Java 程序。)

我知道如何以编程方式获取,并且我已经检查过它在跟踪原始/主分支的单个主分支的简单情况下是否有效。但我不确定一个简单的git fetch 是否一定会获取远程上我在本地没有的所有内容,因为我认为这取决于跟踪状态。

相反,我忽略了如何简单地检查本地没有更新的提交尚未推送到远程。

我想保证本地历史记录与远程历史记录相同,即使有人手动修改了本地 git 副本(例如,配置了奇怪的跟踪状态),只要合理可能。我知道,如果面对敌对行为,除非重新下载所有内容并比较所有内容(例如,有人可能恶意更改本地副本中的 origin/... 指针),否则无法保证任何事情。我的工作假设是我的用户没有恶意地试图让我的程序崩溃或行为不端。如果有理由相信我的程序操作的本地副本与远程副本相比似乎已被修改,我只是希望能够警告我的用户,并提供重新下载的选项(但如果这样做,请不要打扰他们似乎没有必要)。

我提出问题的原因是,出于效率原因,我想检查本地 git 历史记录,同时确保效果就像我直接从远程数据中读取一样。

我不关心暂存区或工作树中遗留的任何内容,我只关心 git 历史记录(在 .git 文件夹中)。而且我不在乎在本地或远程写任何东西。这只是关于读取数据。

【问题讨论】:

    标签: git


    【解决方案1】:

    您必须确保有几个约束适用于本地存储库:

    1. 它必须只有一个遥控器,或者你必须只关心一个遥控器。否则,您需要本地存储库与两个或多个其他 Git 存储库完全匹配;如果这两个 Git 存储库不匹配,则本地 Git 存储库不可能同时匹配两者。

      (如果您愿意强制所有 other 存储库更改,以便它们都匹配,如果需要,这个限制可以放宽一点。)

    2. 您的本地存储库必须是远程的完整克隆。它不能是浅克隆,也不能是单分支克隆。 (这个条件是你可能应该在创建本地克隆时静态设置的。然后它会持续存在,除非有人故意破坏它。不过,你可以通过测试文件 $GIT_DIR/shallow 是否存在来检查浅层性。应该有git rev-parse这个测试,但是很多版本的Git都没有。看看你的Git版本有没有git rev-parse --is-shallow-repository。你可以通过测试git config --get-all remote.origin.fetch的结果来测试单分支性:将普通存储库的结果与单分支克隆的结果进行比较。)

    3. 您必须选择某种方法来识别本地分支名称,例如master,以及所选远程上的相应分支名称。通常,由于用户喜欢匹配他们的分支名称,这很简单:如果远程命名为 origin,则每个(本地)分支 B 对应于 origin/<em>B</em>

      如果您选择其他方法,请相应修改下面的第二步。

    然后,检查两个存储库是否匹配——在您进行检查时;请记住,任何一个存储库都可以在随后滴答作响的纳秒内被修改——您只需执行这两个步骤。如果需要,请记住将名称 origin 替换为您喜欢的任何名称。

    1. 运行:

      git fetch origin
      

      以便所有远程跟踪名称都是最新的。

    2. 运行与此等效的内容(写成带有一些评论的几个部分)。 注意:这是完全未经测试的。

      TF=$(mktemp) || exit
      trap "rm -f $TF 0 1 2 3 15"  # clean up temp file on exit
      valid=true
      

      这里的临时文件只是因为shell管道强制使用子shell,这意味着变量设置不会传播回主shell进程。在其他语言中,您可能不会遇到此问题。

      # compare all local branches to their updated remote-tracking counterparts
      git for-each-ref --format='%(refname:short) %(objectname)' refs/heads > $TF
      while read branchname hash; do
          theirs=$(git rev-parse -q --verify refs/remotes/origin/$branchname) || {
              # git rev-parse failed: they don't have this branch name at all
              valid=false
              break
          }
          test $hash = $theirs || { valid=false; break; }
      done < $TF
      if $valid; then
          echo "upstream repository has all the local branches and they match"
      else
          echo "upstream repository does not have some branch or does not match"
          exit 1 # failure
      fi
      

      当然,您可以检查所有内容并打印整个缺失或不匹配的集合,而不是提前停止。

      # now make sure a local branch exists for each of its remote-tracking counterparts
      # Note: some or all of this is redundant, but it's easier to re-test here
      git for-each-ref --format='%(refname:short) %(objectname)' refs/remotes/origin > $TF
      while read rtname hash; do
          branchname=${rtname#refs/remotes/origin/}
          ours=$(git rev-parse -q --verify refs/heads/$branchname) || {
              # git rev-parse failed: we don't have this branch name at all
              valid=false
              break
          }
          # no need to test hash: we did that already
      done < $TF
      if $valid; then
          echo "and, we have branches for all their branches"
          echo "so we must be good"
          exit 0
      else
          echo "we're missing some local branch names to match theirs"
          exit 1
      fi
      

      和以前一样,您可以进行全面的交叉检查。

    允许某些情况可能是合理的:例如,没有要求某些本地分支 B 存在只是因为 origin/<em>B</em> 存在。所以你可能想完全省略最后一次检查。

    本地分支 B 匹配 origin/B 的测试非常简单:哈希 ID 要么匹配,要么不匹配。如果他们匹配,历史是相同的。如果不是,那就不是。这样做的原因是 Git 存储库中的历史提交集。分支 name 仅包含 Git 应视为“分支的一部分”的最后一次提交的原始哈希 ID。所有早期历史记录都由 commits 决定。提交是完全只读的,包括它们的父指针;并且每个提交都有一个唯一的哈希 ID,所以如果哈希 ID 匹配,历史也会匹配。

    【讨论】:

    • 哇,谢谢。但是如果一些远程分支没有在本地跟踪,git fetch 在某些情况下会忽略这些,并且第 1 部分或第 2 部分中的检查会检测到这一点吗?而且,如果我之后只使用origin/* 分支(而不是“本地”分支),我是否理解正确,我实际上不必检查第 1 部分或第 2 部分?我的意思是,“本地”一词有歧义:那些origin/* 分支在我看来很重要的意义上是“本地的”:它们不需要网络交换进行检查,即使它们指定了“远程”分支git 术语。
    • origin/* 名称确实是(唯一的)Git 的本地名称(当不连接两个 Git 时,只有一个 Git)。当你连接两个 Git 时,你的 Git 会根据它在 Git 中看到的内容更新自己的 origin/* 名称,尤其是 git fetch。一些git fetch 操作仅更新指定的远程跟踪(origin/*)名称;默认是服从remote.&lt;remote&gt;.fetch,它默认更新所有(因为默认是+refs/heads/*:refs/remotes/origin/*,至少对于名为origin的远程)。
    • 无论如何,一旦你更新了origin/*选择你想强加的匹配规则。使用带有引用前缀的git for-each-ref,例如refs/heads,以遍历其各种名称空间中的引用。所有(本地)分支都在refs/heads/ 下,而所有远程跟踪名称都在refs/remotes/ 下,后跟远程名称和另一个斜杠。
    猜你喜欢
    • 1970-01-01
    • 2020-05-30
    • 2021-08-24
    • 2012-04-04
    • 2013-04-25
    • 2021-08-26
    • 1970-01-01
    • 2018-12-14
    • 1970-01-01
    相关资源
    最近更新 更多