5

当我手动检查本地 Git 存储库是否需要来自远程服务器的更新时,我将运行git remote show upstream并解释其输出。但现在我正试图在我添加 Git 支持的 Python 应用程序中执行此操作。

实际上,我正在尝试确定远程服务器上的给定分支是否与给定的本地分支不同,如果不同,关系如何(可快速转发、领先、落后、发散)。

我知道如何通过比较 和 的结果来做到这一点git rev-list master..upstream/mastergit rev-list upstream/master..master. 但这仅在从远程服务器获取后才有效。

有没有办法在不先获取的情况下完成这样的比较?
一种用途是更新应用程序本身,为此我认为先获取是可以接受的。但我也想遍历所有注册的遥控器及其分支,告诉用户他可以从哪里获得更多东西。我认为首先获取所有遥控器是不可接受的,因为用户可能不需要其中的大部分。

我假设ls-remote是我正在寻找的命令,但我不知道如何实现我需要的。我可以比较 和 的结果git ls-remote --heads upstreamgit rev-parse HEAD^确定是否存在差异,但我不知道如何进行。
我是否必须使用git ls-remote upstream来获取完整的提交列表并手动将其与本地提交列表进行比较?实际上,我希望找到一个git rev-list同样适用于远程存储库的等价物。
也许有人知道如何git remote show upstream进行比较?


编辑:@torek:非常感谢您的详细回答。这将需要一些时间来消化,但我会在一天中更有效率的时间完成它,承诺 ;-)
也许需要澄清预期用途的上下文。也许有些事情比你想象的要简单(因为我没有做类似通用 Git GUI 客户端的事情)。

我们有一个托管在 Github 上的现有 Python 应用程序。只有主要开发人员可以推送到存储库,并且他只公开他的master分支。

有些用户使用可下载的软件包,有些用户从 Git 存储库运行应用程序(这对于 Python 作为解释语言特别有用)。

我目前正在实现的第一件事是应用程序内部的一个接口,用于通过 Git 进行自我更新。(好吧,这并不是真正具有开创性的,因为任何人都可以进入命令行并发出git pull origin master或任何他命名为遥控器的东西。但我说这是作为更高级工具的第一步(学习)步骤,以提供用于处理应用程序的 Git 工作流文档/项目。
为此,始终可以,fetch因为单击“检查更新”按钮的人应该会接受提取。一切如何工作也很清楚,我通过查看他们的 URL 来确定遥控器的名称知道哪个(如果有多个)指向“官方”存储库。

但也有一些用户(比如我)同时也是贡献者。他们通常已经分叉了存储库,因此至少有两个遥控器,主仓库和他们的个人分叉。有时他们还注册了其他人的分叉,以便在合并到 master 之前检查他们的贡献。当我接近一个拉取请求时,我有时也会四处询问以获取我的新材料,以便提供拉取请求前的反馈。

我现在想要实现的基本上是所有遥控器上所有分支的列表,其中包含其中哪些具有新材料以及它们可能与upstream/master. 例如,告诉它是从 master 后面的 17 个提交分支出来的,并且包含上游 repo 中不包含的 12 个提交。
我的理由是,完全(并且定期)获取所有这些远程分支不是好的行为。我认为用户应该只获取他真正想要检查的分支。

但是从第一次阅读您的答案开始,很可能我最终会在后台获取所有内容,然后解释本地和“本地远程”分支之间的比较。

4

1 回答 1

7

乱序:

实际上,我希望找到与 git rev-list 等效的方法,它也适用于远程存储库。

没有一个。这在下面很重要,如果我们想查看某个远程有多少我们没有的提交。

实际上,我正在尝试确定远程服务器上的给定分支是否与给定的本地分支不同,如果不同,关系如何(可快速转发、前进、落后、发散)。...有没有办法在不先获取的情况下完成这样的比较?

好吧,基本上没有,尽管这部分取决于你想在这里的字面意思,以及你需要的结果有多精确。另外,请记住,在您与遥控器断开连接并从中获取更新后,其他人可能会连接到同一个遥控器并更改所有内容。你也写遥控器,好像只有一个;可能有不止一个遥控器。

Usinggit fetch与遥控器建立连接并查询它们的参考资料(主要是分支头和标签,还有诸如 git 注释之类的东西),然后根据需要提供任何新的东西。

使用git ls-remote与遥控器建立连接并查询它们(然后停在那里)。

因此,如果远程“难以到达”(例如,建立连接需要一两秒钟,或者需要输入 ssh 密码或短语之类的内容)但更新很小和/或很快(一旦建立连接,传输quick) 更经济fetch,因为稍后进行第二次连接会很痛苦。如果它“易于访问”但更新可能很大和/或很慢,那么使用ls-remote. 但无论哪种方式,您都在建立与远程的连接,您可能认为这与执行fetch. 如果你需要列出中间提交 ID,你必须把这些提交带过来,所以你必须做一个完整的fetch.

还有一个问题fetch,我稍后会解决。

让我们看一下示例git ls-remote输出,并且git remote show origin. 我会先做一个git fetch origin(虽然没有输出,因为它已经是最新的了):

$ git fetch origin
$ git ls-remote origin
120a630b0b71193a33cd033ae9ddcee1db3df07e    HEAD
120a630b0b71193a33cd033ae9ddcee1db3df07e    refs/heads/master
$ git remote show origin
* remote origin
  Fetch URL: ssh://[host]//tmp/tt.git/
  Push  URL: ssh://[host]//tmp/tt.git/
  HEAD branch: master
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (fast-forwardable)

(这里HEAD branch显示的是一个猜测,您通常应该忽略它。它是通过将 SHA-1HEAD与所有 SHA-1 进行匹配来计算的refs/heads/*。只有在只有一个匹配时才能保证是正确的。如果有两个或更多匹配项,它可能会意外正确,但 git 需要更改协议以使其可靠地工作。)

URL 分别来自git config --get remote.origin.urlgit config --get remote.origin.pushurl(默认推送 URL,如果未设置,则与获取 URL 相同)。

现在让我们看看为什么master merges with remote master。那是因为这两个配置项:

$ git config --get branch.master.remote
origin
$ git config --get branch.master.merge
refs/heads/master

(在后一种设置中存在一些深刻的怪异,可能是历史事故。如果您阅读git merge 的文档,您会看到:

branch.<current branch>.merge查询远程named by的name分支的值,branch.<current branch>.remote映射remote.<remote>.fetch到对应的remote-tracking分支,合并这些tracking分支的tips。

使用“健全”的配置——见git fetch下面的注释——这意味着refs/heads/master上面的真正意思refs/remotes/origin/master。)

此外,master pushes to master在这种特殊情况下,因为我git config push.default matching在这个 repo 中设置,让它像 git 在有push.default. 如果您有较新版本的 git 和/或未设置push.default,或设置不同,它可能会推送到其他内容。现在可能的值为nothing, current, upstream, simple, 和matching; 请参阅git-config 文档

现在,至于为什么这个推送是快进的:从ls-remote输出中,我们看到遥控器refs/heads/master(即,我们master将推送到的内容)指的是120a630b0b71193a33cd033ae9ddcee1db3df07e. 正如您已经知道(但可能没有意识到),我们可以看到我们拥有的东西,他们没有:

$ git rev-list 120a630b0b71193a33cd033ae9ddcee1db3df07e..master
eed7b697cab0cbd5babf382f720668e12a86cf2a
224384fed46e1949c88eb514fa67743be66a4c5a
ddc0aab680bab0bd6a7dde4a6ef8cb58ba0368e6
ade842c8562cdccd1e98f7ffd5149a12ddc9226c

我们有四个他们没有的提交。而且,因为我在开始这一切之前就跑了git fetch并且有一个理智的配置,我们可以看到他们有什么我们没有:

$ git rev-list master..120a630b0b71193a33cd033ae9ddcee1db3df07e

这没什么。我们还需要知道一点——事实上,我们应该从这个开始——即:“120a630...实际上是我们的master( ade842c...) 的祖先,或者如果不是,那和我们的 之间是否有一些共同的祖先master?” 我将在这里使用一个缩写的 SHA-1 和名称master,作为长度:

$ if git merge-base --is-ancestor 120a630 master; then echo OK; fi
OK

——所以这是“快进的”:我们领先 4 和落后 0。(事实上,作为祖先意味着我们没有落后:这是最简单的测试,如果你只有的输出ls-remote。)

如果120a630不是 的祖先master,那将意味着两件事之一。也许我们master与他们完全无关master,而且我们根本没有“领先”或“落后”,我们处于完全不同的火车轨道上。或者——可能更有可能——它们就在我们前面(我们可以快进),或者我们有一些共同的祖先,有一个像这样的提交图片段:

        D--E--F
       /
A--B--C
       \
        G--H

(例如,C共同祖先在哪里,他们在哪里F,我们在哪里H,我们可以变基或合并)。

要找出答案,我们需要从他们开始master并向后工作,从我们的开始master并向后工作,看看它们是否会在某个时候相遇。我们可以使用git merge-basewill find the point ,但这意味着我们不仅需要它们的mastercommit-ID F,还需要中间的 ID(DE)导致该点。这再次意味着我们需要git fetch

如果你运行git fetch,它不仅会发现他们的refs/heads/masteris at 120a630b0b71193a33cd033ae9ddcee1db3df07e,它还会带来任何需要的提交(可能没有,可能很多),这当然会为你提供他们的 ID,以便你可以使用git rev-list它们。

Usinggit fetch还将更新我们的git 引用到 set refs/remotes/origin/master。但这仅仅是因为:

$ git config --get remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*

此配置项表示,在fetch获取 refs 列表(ls-remote打印相同的)之后,它应该采用任何 match refs/heads/*,将 name 更改为refs/remotes/origin/<match>,并将它们填充到本地 repo 中。

可以更改它,因此git fetch不会更新. 如果有人这样做,将没有用。(而且我不确定我们是否会得到 commits ,,或者两者之一!我从来没有使用过疯狂的 fetch 配置。)origin/mastergit rev-list origin/master..masterDEF

总而言之,您需要弄清楚:

  • 联系哪个遥控器(如果有)
  • 哪些本地分支(refs/heads/*)对应于那些远程(用于拉和/或推)
  • 他们的分公司负责人是否与我们有关(是否同名或异名)
  • 推送是否会推送到相同的名称 ( matching, current, simple-if-name-same)、可能不同的名称 ( upstream) 或“从不” ( nothing, simple-if-name-different)
  • 如果您选择不联系部分或全部遥控器,是否信任 refs refs/remotes/(基于行)remote.name.fetch

这一切都很混乱,因为pushfetch是不对称的。有可能git push blarg会推送matching(所以如果blarg有一个名为 的分支glink,我们会推送到glink那里,即使glink没有branch.glink.remote设置)。还有配置变量remote.pushdefault,等;以及更多配置(再次,请参阅 git-config 文档)。remote.name.pushfetch

(我怀疑你最好只运行git fetch,然后可能使用git branch -vv。)

于 2013-10-11T18:35:47.750 回答