git:如何以编程方式确定我需要拉取还是推送?
git: how can I programmatically determine if I need a pull or push?
我正在编写一个程序来检查几个克隆的 git 存储库的状态。
如何判断我的存储库是否需要 "git pull" 或 "git push"?
首先,pull
只是 fetch
后跟 merge
(或 rebase
)。这很重要,因为您首先想要的是一个相关的、更简单的问题的答案:与某些远程存储库相比,您的本地存储库落后 and/or 多远? (对于 TL;DR 答案,请跳到下面的第一个 header-ized 部分。)
还有其他潜在的并发症,但这里要做的简单的事情就是检查您的 branch-tip(s) 与其他 git repository/ies 的情况。为了便于说明,让我们给它们取一些名字。我们可以调用您的存储库 "L"(对于本地),假设有两个额外的存储库 "RA"(远程 A)和 "RB",它们的 URL 存储在远程名称下的本地存储库 L RA 和 RB.
同时获得您可能需要的一切的简单方法是在 RA 和 RB 上 运行 git fetch
(或 git remote update
)。 (我们稍后会看到一种延迟获取的方法,尽管我不确定它是否有任何实际价值。)
给定一个典型的设置,获取两个遥控器将把它们的本地分支复制到您自己的 "remote branches"。假设 RA 有 master
和 dev
并且 RB 有 master
和 feature
,所以在你的抓取完成后:
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 是否 "ahead"、"behind" 或两者,通过查看提交图如何相互叠加。
如果 repo L 是严格的 "ahead of" repo RB,即你有可以推送的东西,那么图片段必须看起来像这样:
... - o <-- RB/master: tip-most commit = badf00d
\
o - o <-- master: tip commit = feedbee
这里 L 是“领先 2”,用 git status
会给你的术语来说,repo RB 在哪里。如果你向 RB 推送,git 会将最后两个提交交给 RB 并告诉它请将其 master
设置为 feedbee
,这将赶上它。
如果 repo L 严格 "behind" RB,那么图片段看起来是一样的,但标签会颠倒:
... - o <-- master
\
o - o <-- RB/master
在这种情况下,如果您进行合并以将 RB/master
引入 master
,git 会看到一个 fast-forward 并将您的主控设置为 badf00d
.此时 repo RA 会落后,你可能想推到那里。
不过还有两种可能性。 L可以同时领先和落后,例如:
... - o - o <-- master
\
o - o <-- RB/master
这需要变基或真正的合并,如果 git 不能自行组合各种更改(或者即使可以,它也可能需要手动 hand-holding可能会弄错)。
最后,尽管不太可能,这两个分支提示可能完全无关(在 ...
部分没有共同的祖先):
... - 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)
这些使用可从本地分支提示(由 master
标识的提交)而不是远程分支提示(由 [=48= 标识)访问的 gitrevisions
range syntax 到 select 修订]).
如果你是 ahead-but-not-behind,你可以安全地 git push
。如果你是 behind-but-not-ahead,你可以安全地 git merge
得到 fast-forward(在这一点上,使用 pull
没有意义,因为你已经完成了 fetch
步骤)。如果你们都领先 和 落后,所以两个计数都不为零,您必须在合并或变基之间做出决定,以及如果这些失败该怎么办。当然,如果两个计数都为零,则两个分支提示标识相同的 SHA-1,无需执行任何操作。
不想用git fetch
怎么办?
最终你拥有 使用 git fetch
。但是,如果您愿意,可以从 git ls-remote
开始,它会生成 SHA-1 和 refnames 的列表:
$ 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你关心匹配,然后你可以知道没有什么可以获取或推送的。此外,如果它们不同,您可以查看是否有相应的 SHA-1。例如,上面显示远程的 master
指向提交 a17c56c056d5fea0843b429132904c429a900229
。如果我有它(我没有),我可以将它用作 git rev-list --count
中的说明符之一,以找出遥控器落后的程度。因为我没有它,我几乎肯定落后了:我不知道有多少,但我需要获取,然后可能合并或变基(我不知道我是否领先,直到我取)。
你怎么知道要查看哪些分支?
最好提前确定,而不是遍历所有可能的分支。但是,如果您 do 想要遍历分支,则工具是 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,这显然会为前面和后面的计数提供零。
实际上,自从Git 2.5(今天发布)以来,您可以快速看到哪个分支在前面(需要推)或在后面(需要拉)
参见“Show git ahead and behind info for all branches, including remotes”
git for-each-ref --format="%(push:track)" refs/heads
这是因为 <branch>@{push}
是一个新的快捷方式,它专门引用用于推送的上游分支(它不是 总是 用于拉取的分支)。
我正在编写一个程序来检查几个克隆的 git 存储库的状态。
如何判断我的存储库是否需要 "git pull" 或 "git push"?
首先,pull
只是 fetch
后跟 merge
(或 rebase
)。这很重要,因为您首先想要的是一个相关的、更简单的问题的答案:与某些远程存储库相比,您的本地存储库落后 and/or 多远? (对于 TL;DR 答案,请跳到下面的第一个 header-ized 部分。)
还有其他潜在的并发症,但这里要做的简单的事情就是检查您的 branch-tip(s) 与其他 git repository/ies 的情况。为了便于说明,让我们给它们取一些名字。我们可以调用您的存储库 "L"(对于本地),假设有两个额外的存储库 "RA"(远程 A)和 "RB",它们的 URL 存储在远程名称下的本地存储库 L RA 和 RB.
同时获得您可能需要的一切的简单方法是在 RA 和 RB 上 运行 git fetch
(或 git remote update
)。 (我们稍后会看到一种延迟获取的方法,尽管我不确定它是否有任何实际价值。)
给定一个典型的设置,获取两个遥控器将把它们的本地分支复制到您自己的 "remote branches"。假设 RA 有 master
和 dev
并且 RB 有 master
和 feature
,所以在你的抓取完成后:
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 是否 "ahead"、"behind" 或两者,通过查看提交图如何相互叠加。
如果 repo L 是严格的 "ahead of" repo RB,即你有可以推送的东西,那么图片段必须看起来像这样:
... - o <-- RB/master: tip-most commit = badf00d
\
o - o <-- master: tip commit = feedbee
这里 L 是“领先 2”,用 git status
会给你的术语来说,repo RB 在哪里。如果你向 RB 推送,git 会将最后两个提交交给 RB 并告诉它请将其 master
设置为 feedbee
,这将赶上它。
如果 repo L 严格 "behind" RB,那么图片段看起来是一样的,但标签会颠倒:
... - o <-- master
\
o - o <-- RB/master
在这种情况下,如果您进行合并以将 RB/master
引入 master
,git 会看到一个 fast-forward 并将您的主控设置为 badf00d
.此时 repo RA 会落后,你可能想推到那里。
不过还有两种可能性。 L可以同时领先和落后,例如:
... - o - o <-- master
\
o - o <-- RB/master
这需要变基或真正的合并,如果 git 不能自行组合各种更改(或者即使可以,它也可能需要手动 hand-holding可能会弄错)。
最后,尽管不太可能,这两个分支提示可能完全无关(在 ...
部分没有共同的祖先):
... - 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)
这些使用可从本地分支提示(由 master
标识的提交)而不是远程分支提示(由 [=48= 标识)访问的 gitrevisions
range syntax 到 select 修订]).
如果你是 ahead-but-not-behind,你可以安全地 git push
。如果你是 behind-but-not-ahead,你可以安全地 git merge
得到 fast-forward(在这一点上,使用 pull
没有意义,因为你已经完成了 fetch
步骤)。如果你们都领先 和 落后,所以两个计数都不为零,您必须在合并或变基之间做出决定,以及如果这些失败该怎么办。当然,如果两个计数都为零,则两个分支提示标识相同的 SHA-1,无需执行任何操作。
不想用git fetch
怎么办?
最终你拥有 使用 git fetch
。但是,如果您愿意,可以从 git ls-remote
开始,它会生成 SHA-1 和 refnames 的列表:
$ 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你关心匹配,然后你可以知道没有什么可以获取或推送的。此外,如果它们不同,您可以查看是否有相应的 SHA-1。例如,上面显示远程的 master
指向提交 a17c56c056d5fea0843b429132904c429a900229
。如果我有它(我没有),我可以将它用作 git rev-list --count
中的说明符之一,以找出遥控器落后的程度。因为我没有它,我几乎肯定落后了:我不知道有多少,但我需要获取,然后可能合并或变基(我不知道我是否领先,直到我取)。
你怎么知道要查看哪些分支?
最好提前确定,而不是遍历所有可能的分支。但是,如果您 do 想要遍历分支,则工具是 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,这显然会为前面和后面的计数提供零。
实际上,自从Git 2.5(今天发布)以来,您可以快速看到哪个分支在前面(需要推)或在后面(需要拉)
参见“Show git ahead and behind info for all branches, including remotes”
git for-each-ref --format="%(push:track)" refs/heads
这是因为 <branch>@{push}
是一个新的快捷方式,它专门引用用于推送的上游分支(它不是 总是 用于拉取的分支)。