何时创建和更新远程跟踪分支?

Tim*_*Tim 1 git

  1. 如果我是正确的,克隆远程存储库时可以创建远程跟踪分支。创建远程跟踪分支时是否还有其他情况?

  2. 如果我是对的,从远程存储库获取/拉取时会更新远程跟踪分支。远程跟踪分支更新时是否还有其他情况?

  3. 作为 2 的特例,当git push本地分支到远程仓库时,如果本地分支在本地仓库中有关联的远程跟踪分支(即如果本地分支是本地跟踪分支,在 Version Control with Git 中定义)作者 Loeliger 2ed),是否git push更新远程跟踪分支,或者远程跟踪分支只能通过运行git fetchgit pull之后从远程存储库间接更新git push

  4. 作为 1 的特例,如果git push推送本地非跟踪分支到远程分支(即如果本地分支没有对应的远程分支到要推送的本地分支),将git push创建与本地非跟踪分支关联的远程跟踪分支和把它们变成本地跟踪的?

tor*_*rek 6

让我们或多或少地按顺序回答这三个四个:-) 问题:

  1. ...克隆远程存储库时可以创建远程跟踪分支。创建远程跟踪分支时是否还有其他情况?

他们是; 至少可能是的,但让我们稍后再谈。

具体来说,当git clone对另一个存储库进行初始克隆时,它会fetch =为远程添加一个配置行。添加的行取决于这两个选项:

  • --origin name: 更改遥控器的名称。通常名称是 justorigin并且获取配置是fetch = +refs/heads/*:refs/remotes/origin/*,但是如果您将遥控器的名称更改为 ,例如zerblatt,您将获得+refs/heads/*:refs/remotes/zerblatt/*.

  • --mirror: 将 fetch 配置更改为 read fetch = +refs/*:refs/*。请注意,在这种情况下,可能根本没有远程跟踪分支,因为“远程跟踪分支”只是一种较弱抽象的表示“refs/remotes/命名空间中的引用”的方式,现在我们没有进行任何重命名. (如果被克隆的存储库有它自己的远程跟踪分支——引用refs/remotes/——它们被复制,我们得到远程跟踪分支。我们可能会git ls-remote在开始克隆之前运行以找出它们有哪些引用,以便找出,虽然这有点棘手,因为如果我们还没有开始克隆,我们没有一个存储库来配置远程以便使用git ls-remote。不过有一个方法!)

让我们继续:

  1. 如果我是对的,从远程存储库获取/拉取时会更新远程跟踪分支。远程跟踪分支更新时是否还有其他情况?

是的:特别是一个远程跟踪分支(它也只是refs/remotes/命名空间中的一个引用)至少在一些成功的git push操作上会自动更新。究竟哪些取决于您的 git 版本,因为push并不总是更新它们:记录此更新的文档首先出现在 git 1.8.4 中,尽管更新可能在 git 1.7 左右;和 git 1.9 及更高版本也在所谓的“三角工作流”中更新它们(从一个远程获取,推送到另一个远程)。


让我在这里休息一下,再做一些关于远程跟踪分支的笔记。

正如我们已经注意到的,远程跟踪分支只是一个全名以 开头的引用refs/remotes/。Git 有一个“管道”命令,git update-ref,您(或任何人或任何脚本)可以使用它来更新任何引用。例如,假设您最近从originfetch 中获取,然后向您自己的提交添加了一个提交master(将 origin/master 设置为上游),这样就git status表明您“领先 1”。如果你要运行:

git update-ref refs/remotes/origin/master master
Run Code Online (Sandbox Code Playgroud)

然后运行git status,您的 git 会声称您现在是最新的。发生的事情是你已经告诉你的 git “他们的”主 ( origin/master) 指向与你自己的相同的提交,即使你还没有推送你自己的提交。(如果你确实运行了这个,你可以简单地运行git fetch origin到 fix refs/remotes/origin/master,或者当然git update-ref用来修复它。)

这暴露了这里的底层机制:git 简单地将refs/remotes/origin/master提交对象的实际 SHA-1 ID写入到git 与他们(远程的)git 交谈时看到的提交对象。对此有一个强约束:除非该 SHA-1 ID 对应于已存储在您自己的存储库中的实际对象,否则git无法放入该 SHA-1 ID。在实践中,git“感觉”(并且)在成功获取或推送后在那里写入该 ID 是安全的,因为在成功获取后,您必须拥有该对象,并且要完成推送,您必须拥有该对象,并且在这两种情况 git 都刚刚看到与给定遥控器上的某个名称相对应的 ID。

这也显示了如何首先git status说“ahead 1”:它只是计算master从你的主节点上游无法访问的提交。那是:

ahead=$(git rev-list --count master@{u}..master)
behind=$(git rev-list --count master..master@{u})
echo "branch master is ahead $ahead and behind $behind"
Run Code Online (Sandbox Code Playgroud)

此信息与上次正确更新远程跟踪分支时一样是最新的(或过时的)。

现在我们还要注意,它git clone可以分成几个单独的 git 命令。让我们假设您不使用--origin也不克隆,--mirror并且 url 很简单$url(并且这些步骤都没有失败):

mkdir myclone && cd myclone && git init
git remote add origin $url
git fetch origin
git checkout ...
Run Code Online (Sandbox Code Playgroud)

(究竟要做什么git checkout有点神秘;git fetch如果我们添加-fgit remote add行中,可以跳过该命令,但为了说明目的,我打算在这里做一些事情)。每个命令的作用是什么?

  • mkdir + cd + git-init 序列创建一个新的空的、适合克隆的存储库。
  • git remote add行将远程配置为origin从中获取$url并添加fetch = +refs/heads/*:refs/remotes/origin/*一行。
  • git fetch origin然后该命令主要完成了克隆过程(丢失的位是最后的git checkout)。

现在,假设在我们运行之前git fetch origin,我们运行了其他 git 命令,例如git config --edit并弄乱了该fetch =行。我们可以设置一些东西,这样我们就不会得到远程跟踪分支。我们可以创建自己的提交,与实际远程上的内容无关,并用于git update-ref将它们分配给远程跟踪分支。我们可以运行git ls-remote以找出遥控器上存在哪些分支。

这些都不是特别有用,但它们都是可能的。(如果有人有充分的理由通过创建很多fetch =行来做棘手的分支名称映射事情,也许它们毕竟有用。)

git checkout在最后一行,我们应该怎么做?答案取决于几件事,只有其中一些是我们可以直接控制的。如果您git clone使用-b branch,那是我们最容易处理的:我们应该git checkout branch。如果有refs/remotes/origin/branch我们”将获得一个本地分支branch,其上游设置为origin/branch-b但是,如果您没有指定选项,那么检查什么,模拟您的 git git clone,取决于的 git 版本和远程版本,以及什么我们会看到git ls-remote. 较新的 git 请求并接收分支名称。较旧的 git 使用 [内部等效的] ls-remote 输出并比较远程 git 显示的 SHA-1HEAD到 SHA-1,远程 git 为每个分支显示:如果恰好有一个匹配项,那就是分支;如果有多个匹配项,则任意选择一个;如果根本没有匹配项,请使用master. 如果较新的 git 正在与不支持新的“按名称告诉我分支”选项的较旧的 git 交谈,则较新的 git 会回退到较旧的方法。)


回到你的问题:

  1. 作为 2 的特例,当git push本地分支到远程仓库时,如果本地分支在本地仓库中有关联的远程跟踪分支(即如果本地分支是本地跟踪分支,在 Version Control with Git 中定义)作者 Loeliger 2ed),是否git push更新远程跟踪分支,或者远程跟踪分支只能通过运行git fetchgit pull之后从远程存储库间接更新git push

我觉得这个问题很混乱。这里没有涉及特殊的外壳。在某些时候,我们知道您git push已决定向远程发送R一个请求,该请求实际上表示:“请将您的设置refs/heads/foo为 SHA-1 1234567890123456789012345678901234567890”(refs/heads/根据需要替换为正确的名称和 SHA-1 ID)。(使用时--force-with-lease请求中包含更多信息,无论如何请求也带有“强制”标志。是否服从“强制”标志由远程决定。但是,这里需要注意的是请求提供原始 SHA-1,而不是本地 git 存储库中分支的名称。远程 git 只获取他的参考名称和 SHA-1。这在实践中意味着远程的接收前和接收后以及更新挂钩无法看到您的分支名称。[他们也看不到力标志,我认为这是一个小错误,但这完全是另一个问题。])

他们的 git 以“是,完成”或“否:错误:<详细信息>”回答此请求。

然后,您的 git 可以选择将“是的,完成”的答案视为足以更新 remote 的远程跟踪分支R。(当然,“否”回复意味着没有任何内容可更新。)无论您在哪个本地分支(如果有),也不要在乎您拥有哪些本地分支,也不管它们中的任何一个是否设置了上游。这部分是因为相同的代码允许您执行以下操作:

git push origin 1234567890123456789012345678901234567890:refs/heads/foo
Run Code Online (Sandbox Code Playgroud)

将他们refs/heads/foo设置为该提交(假设提交 ID 有效;您的 git 将首先检查您的存储库,并在必要时像往常一样将提交提交给他们的 git)。

就执行远程跟踪分支更新而言,您的 git 的棘手之处在于确定您的 git 应替换为什么名称refs/heads/foo。这就是线性与三角形工作流程的用武之地,也是我们必须检查您拥有的 git 版本的地方。如果您使用的是三角工作流并且您的 git 早于 1.9,则您的 git 不知道要更新什么,也不会更新任何内容。如果您的 git 比大约 1.7 旧,它从不尝试找出要更新的内容,也不会更新任何内容。否则,它使用适当的 refspec 映射来转换refs/heads/foo以查看要更新的内容。

最后:

  1. 作为1的特例,如果git push将本地非跟踪分支推送到远程分支(即如果本地分支没有对应的远程分支被推送),git push会创建与本地非跟踪关联的远程跟踪分支- 跟踪分支并将它们变成本地跟踪分支?

这个问题的片段对我来说仍然没有意义,但片段确实有意义。让我们考虑一个具体的例子,并忽略由于复杂的多fetch =行而导致的三角工作流和奇怪的名称转换,以便我们处理简单的git push origin myname:theirname命令。让我们进一步假设 git 版本是合理的最新版本。

同样,您的 git, given git push origin myname:theirname,首先转换myname为原始 SHA-1 ID。如果您git push origin myname的 git 也咨询您从零件中push.default填写零件,但假设您已经给出了一个明确的名称,例如。(这也让您可以通过原始 SHA-1 ID 推送。换句话说,它消除了大部分复杂性,让我们现在只需要担心 git-to-git “推送”会话。)theirnamemynamerefs/heads/foo

您的 git 现在使用遥控器的 URL 给他们的 git 打电话。(如果 URL 指向您自己计算机上的另一个存储库,则您的 git 既扮演“您的 git”角色,也扮演“他们的 git”角色,并且还使用了一堆快捷方式,但让我们只考虑过度的-互联网电话案例在这里。)

在一些基本的协议握手之后,你的 git 发送任何需要的对象,然后发送你所有的更新建议,一次(从你给你的每个 refspec git push):

please set refs/heads/theirname to 123456...
please set refs/heads/anothername to 987654...
Run Code Online (Sandbox Code Playgroud)

等等。

他们的 git 通过其检查规则(内置快进检查和任何接收端挂钩:预接收和更新)运行这些请求,以查看是否允许它们。然后它将新的 SHA-1 写入其引用并说“是的,完成”或拒绝更新并说“不”。

您的 git 接受所有这些回复并决定是更新还是创建refs/remotes/origin/theirname和/或refs/remotes/origin/anothername. (请记住,我们假设远程名为origin,并且您的 git 是最近的,等等)对于任何“是”答复,您的 git更新或创建该名称;对于任何“不”,您的 git 不会。

更新或创建就像您git fetch已经运行一样完成git update-ref(尽管它只是直接调用实际更新,而不是使用 fork/exec 或 spawn,当然)。

现在,一旦所有这些都完成了,你的 git 还可以做一件事,这取决于你是否提供了-u(又名--set-upstream)标志git push(这当然取决于你git push是否足够新来拥有-u标志;我忘了什么时候它出现)。它还要求您的pushrefspec(s)的左侧最初解析为分支名称,而不是原始 SHA-1。在这种情况下,您的 git 仍将拥有所有名称。

如果您指定-u,那么 - 只要推送成功,您的 git 就会有效地运行git branch --set-upstream-to以设置或更改该分支的上游。(当然,它只是在内部执行此操作。)

让我们把所有这些放在一个相当复杂的例子中。假设您有自己的本地分支foo和一个名为 的远程分支origin,并且您执行以下操作:

$ git fetch origin
[output snipped]
$ git for-each-ref refs/remotes/origin  # let's see their branches
biguglysha1 commit  refs/remotes/origin/HEAD
biguglysha1 commit  refs/remotes/origin/master
# this confirms that they don't have a "refs/heads/theirname"
$ git push -u origin foo:refs/heads/theirname
[output snipped, but assume it says "all done"]
Run Code Online (Sandbox Code Playgroud)

refs/heads/theirname需要完整的拼写 ,才能使分支在此处成功创建(如果分支已经存在,您可以使用短名称,但是我们有一个无聊的案例而不是有趣的案例)。

因为他们的 git 根据您提供的名称创建了分支,并且您的 git 足够新并且您没有设置奇怪的名称映射,所以您现在有一个refs/remotes/origin/theirname. 因为您指定了-u,您的本地分支foo现在也将其上游设置origin/theirname为 。Agit branch -vv现在将显示您的foo“跟踪”origin/theirname和最新信息。

这发生在两部分:当他们的 git 接受了 set 的请求时refs/heads/theirname,你的 git created refs/remotes/origin/theirname,并找出refs/remotes/origin/theirname需要通过fetch =地图;然后当您的 git 应用该-u选项时,您的 git 将您的branch.foo.remotetoorigin和您的branch.foo.mergeto refs/heads/theirname。有趣的是,第二部分——应用-u标志——根本不需要地图,因为branch.foo.merge设置为refs/heads/theirname。但是为了git branch -vv显示origin/theirname,它必须通过地图。