git: 如何以编程方式确定我是否需要拉取或推送?

3

我正在编写一个程序,用于检查一些克隆的 git 仓库的状态。

如何判断我的仓库是否需要进行 "git pull" 或 "git push" 操作呢?

2个回答

11

首先,pull 只是 fetch 然后再加上 merge(或者rebase)。这很重要,因为你首先想要回答一个相关且更简单的问题:与某个远程仓库相比,你的本地仓库落后或领先多少?(如果你只想看简短回答,请跳到下面第一个标题化部分。)

还有其他潜在的复杂性,但在这里要做的简单事情就是检查你的分支末端与一些其他 Git 仓库的分支末端是否一致。我们给它们一些名称以进行说明。我们可以将你的仓库称为"L"(本地),并假设有两个附加的仓库“RA”(远程A) 和 "RB",它们的 URL 存储在本地仓库 L 中,使用 RA 和 RB 作为远程名称。

一次获取所有您可能需要的内容的简单方法是在 RA 和 RB 上运行 git fetch(或者 git remote update)。 (我们将在稍后看到推迟提取的方法,虽然我不确定其中是否有任何真正的价值。)

在典型的设置中,获取两个远程副本将复制它们的本地分支到您自己的“远程分支”。 让我们假设 RA 有 masterdev,RB 有 masterfeature,因此在完成提取后:

L$ git rev-parse --short refs/remotes/RA/master
feedbee
L$ git rev-parse --short refs/remotes/RA/dev
feedbee
L$ git rev-parse --short refs/remotes/RB/master
badf00d
L$ git rev-parse --short refs/remotes/RB/feature
c0ffee1

(这些数字是虚构的,仅供说明。此外,在实际代码中,您不希望使用--short。如果分支名称可以缩写并且不会产生歧义,则可以省略refs/remotes/ - 及以下内容中的refs/heads/。但是,如果您正在编写脚本,则最好使用完整名称以防万一。)

现在,让我们检查您自己的本地分支的提示:

L$ git rev-parse --short refs/heads/master
feedbee

这意味着你的master与RA的master同步,后者与其dev同步。因此,那里没有需要推送或提取的内容(尽管您已经提取了为了找出这个问题),因此也没有需要合并或变基的内容。

另一方面,远程RB不同步,因为它的master指向提交badf00d。这是否意味着您需要合并或推送某些内容?也许是,也许不是:这就是复杂性所在。如果需要手动合并,Git无法提供太多帮助,但您可以通过查看提交图形如何相互堆叠来确定RB是“超前”,“落后”还是两者都有。

如果仓库L严格“领先于”仓库RB,即您有可以推送的内容,则图形片段必须类似于以下内容:

... - o           <-- RB/master: tip-most commit = badf00d
        \
          o - o   <-- master: tip commit = feedbee

在 git status 命令中,L 是相对于 repo RB 的“2个提交”之前的位置。如果你向 RB 进行推送,git 会将最后两个提交交给 RB,并告诉它将其 master 设置为 feedbee,以使其保持同步。

如果 repo L 严格落后于 RB,则图形片段将相同,但标签将被颠倒:

... - o           <-- master
        \
          o - o   <-- RB/master

在这种情况下,如果你进行合并以将 RB/master 合并到 master 中,Git 会看到一个快进并将你的主分支设置为 badf00d。此时, RA 存储库将落后于 RB 存储库,你可能需要将更改推送到 RA 存储库中。

不过,还有两个可能性。例如,L 可以同时领先和落后:

... - o - o       <-- master
        \
          o - o   <-- RB/master

这需要进行变基或真正的合并,如果git不能自动合并各种更改(即使它可以,也可能会出错),则其中任何一个都可能需要手动协助。

最后,虽然不太可能,但两个分支末端完全不相关(在...部分没有共同的祖先):

... - o   <-- master

... - o   <-- RB/master

由于下面所描述的内容会导致计算出错误的 ahead/behind 数量,因此这是最棘手的问题。如果克隆的版本库中有人重写了所有历史记录,这种情况只会出现在克隆版上。我们可以考虑增加一些偏执检查,例如使用 git merge-base,但我会在此忽略这个问题。

查找前后差异数

解决了上述问题之后,下面是一种快速查找两个分支(一个本地的,一个远程的)之间 "ahead" 和 "behind" 数量的方法。我将切换到常规命名为“origin”的远程仓库,并使用分支名称的简写形式:

$ ahead=$(git rev-list --count origin/master..master)
$ behind=$(git rev-list --count master..origin/master)

这些使用gitrevisions范围语法来选择可从本地分支尖端(由master指定的提交)到达但不是远程分支尖端(由origin/master指定)的修订版本。

如果你超前而不落后,可以安全地运行git push。如果你落后而不超前,则可以安全地运行git merge以获得快进(此时使用pull没有意义,因为你已经完成了fetch步骤)。如果你同时超前且落后,使两个计数都不为零,你必须在合并或变基之间做出决策,并决定在它们失败时该怎么办。当然,如果两个计数都为零,则两个分支尖端标识相同的SHA-1,不需要进行任何操作。

如果不想使用git fetch怎么办?

最终,你必须使用git fetch。然而,如果你愿意,可以先使用git ls-remote,它会输出一个SHA-1和引用名称列表:

$ git ls-remote
From [url redacted]
a17c56c056d5fea0843b429132904c429a900229    HEAD
ca00f80b58d679e59fc271650f68cd25cdb72b09    refs/heads/maint
a17c56c056d5fea0843b429132904c429a900229    refs/heads/master
0029c496ce1b91f10b75ade16604b8e9f5d8d20b    refs/heads/next
fcd56459647e0c41f2ea9c5b7e2ed827f701fc95    refs/heads/pu
e8f6847178db882bd42d5572439333ca4cb3222e    refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86    refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930    refs/tags/gitgui-0.10.0^{}
[mass snippage]

如果远程的SHA-1与本地不同,则这些SHA-1不会提供您需要的所有图形信息,但如果您关心的SHA-1匹配,则可以确定没有任何需要获取或推送的内容。此外,如果它们不同,您可以查看是否具有相应的SHA-1。例如,上面显示远程的master指向提交a17c56c056d5fea0843b429132904c429a900229。如果我有它(我没有),我可以将其用作git rev-list --count中的一个标识符,以了解落后于远程多少。由于我没有它,我几乎肯定落后了:我不知道落后多少,但我需要获取,然后可能合并或变基(在获取之前我也不知道自己是否领先)。

如何知道要查看哪个分支?

最好事先确定,而不是只迭代所有可能的分支。但是,如果您确实想迭代分支,则工具为git for-each-ref,它需要相当多的参数。例如,要查找自己的本地分支:

$ git for-each-ref --format='%(refname)' refs/heads
refs/heads/master
refs/heads/precious
refs/heads/stash-exp

查找远程origin的分支:

$ git for-each-ref --format='%(refname)' refs/remotes/origin
refs/remotes/origin/maint
refs/remotes/origin/master
refs/remotes/origin/next
refs/remotes/origin/pu
refs/remotes/origin/todo

提取这些字符串的足够部分并匹配分支名称非常容易,以便进行比较。如果您想检查(并跳过)相匹配的SHA-1,则可以使用其他选项获取两个分支的名称和SHA-1。这显然会导致ahead和behind计数为零。


不仅回答了我的原始问题,还教会了我很多关于git基础的知识! - Mark Harrison

4

实际上,自Git 2.5发布以来(今天发布),您可以快速查看哪个分支需要推送(超前)或拉取(落后)

请参阅“显示包括远程在内的所有分支的git超前和落后信息

git for-each-ref --format="%(push:track)" refs/heads

这是因为<branch>@{push}是一个新的快捷方式,它专门引用用于推送的上游分支(它不总是用于拉取的分支)。


我接受了torek的答案,因为它涵盖了2.5版本之前的内容,但这个答案也很好。 - Mark Harrison
@MarkHarrison 接受 torek 的答案 总是 一个好主意 :) - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接