0

我有这个:

git branch # I am on a feature branch "X"  
git fetch origin dev;
git checkout -b "${new_branch}" "origin/dev"

问题是最后一个命令签出一个以 X 为基础的新分支,而不是“origin/dev”作为基础。为什么会这样做?我的印象是git checkout -b foo bar会检查一个名为 foo 的新分支,使用 bar 作为“基础”(请更正我的术语)。为什么那行不通?

我在 git 版本:2.17.1

也许我应该改用这个:

git checkout -b "${new_branch}" --track origin/dev

?

update:可能发生的情况是 origin/dev 正在通过本地功能分支的更改进行更新。所以我使用的第一个命令确实使用 origin/dev 作为基础,只是 origin/dev 看到来自功能分支的更新,因为设置了跟踪......

4

3 回答 3

2

在您自己的回答中,您提到:

git checkout -b "${new_branch}" "origin/dev"

意味着新分支将跟踪 origin/dev ...

这是正确的,尽管它使用了这个严重超载的单词“track”。在过去的几年里,在我看来 Git 文档已经(慢慢地)远离这个词,这可能是一个好主意(尽管它仍然存在于--trackand--no-track选项中!)。

更合适/更好/更现代的术语是新分支将origin/dev设置为其上游。每个分支名称可以有一个上游设置。这个上游只是一个分支的名称(例如,master)或远程跟踪名称(例如,origin/master)。此设置的存在及其实际值会影响git status报告状态的方式、在没有附加参数的情况下使用时的方式git merge和行为,以及在没有附加参数的情况下的方式和行为。1git rebasegit pullgit push

或者,分支可以没有上游。如果一个分支没有上游,git status则不报告该分支与其不存在的上游的比较,git mergegit rebase要求更多参数,等等。请注意,上游设置或缺少设置与分支名称指向的提交哈希无关。

(另请参阅让现有的 Git 分支跟踪远程分支?

我修复它的方法是使用 --no-track,如下所示:

git branch --no-track "${new_branch}" "remotes/origin/dev"
git checkout "${new_branch}"

这将完成这项工作,但您也可以这样做:

git checkout --no-track -b "${new_branch}" origin/dev

1这并不是一个完整的列表。特别是git branch -vv还查看上游设置,并且git for-each-ref能够git rev-parse提取分支的上游设置。此外,Git 的某些部分不需要验证上游设置的名称是否有效,如果设置了的话,但 Git 的其他部分会这样做;所以这里有很多可能性。


背景(有很多细节)

两者的精确默认操作git branch有点git checkout复杂。Git 试图提供帮助,但结果却是一团糟。

我认为记住分支名称充当指向特定提交的指针会有所帮助。Git 将此称为分支的提示提交。您可以选择整个存储库中的任何现有提交,并在那里附加一个分支名称。例如,给定一个像这样的提交链:

...--E--F--G
            \
             H--I--J   <-- master (HEAD)

(图中还有一个莫名其妙的扭结),我们现在可以找到提交的实际哈希 IDG并在那里附加一个新的分支名称。假设G实际的哈希 ID 以 开头491ab94,所以我们运行:

git branch marker 491ab94

结果如下所示:

...--E--F--G   <-- marker
            \
             H--I--J   <-- master (HEAD)

现在有两个分支,以前只有一个。名为 的新分支marker标识了 commit G。现有master的没有改变:它继续识别 commit J

创建一个新分支需要选择一个提交

每当您创建一个新的分支名称时,您必须回答 Git 的一个问题:该分支名称应该标识哪个现有提交? 在这里,我们选择G了它的哈希 ID。由于哈希 ID 不是name ,因此不能将此 ID设置为新分支的上游。

如果省略 中的哈希 ID git branch,Git 默认使用HEAD

git branch m2

由于HEAD当前附加到master,这m2表明与以下相同的提交master

...--E--F--G   <-- marker
            \
             H--I--J   <-- master (HEAD), m2

m2在这种情况下,默认情况下未设置上游。

您还可以使用创建新的分支名称git checkoutgit branch使用和之间的主要区别在于git checkoutgit checkout 附加HEAD到新分支,如果需要,移动到(签出)与先前当前提交不同的提交。例如:

git checkout -b m2

(而不是git branch m2) 根本不必移动,也不会移动,但会重新连接HEAD,从而产生:

...--E--F--G   <-- marker
            \
             H--I--J   <-- master, m2 (HEAD)

CommitJ仍然是已签出的提交,但现在HEAD附加到 name m2。和以前一样,m2没有上游。

有时,选择一个特定的提交会设置一个上游

正如我们刚刚看到的,如果让 Git 默认为HEAD,Git不会为新分支设置上游。此外,如果您通过哈希 ID选择特定提交,Git不会设置上游。但有时,如果您按名称选择特定提交,Git确实会设置一个上游。

那么:Git 什么时候设置上游?好吧,考虑一下我们对提交的名称。其中一些是我们自己的分支名称,like and masterabove 。其中一些是标签名称,例如. 有些是远程跟踪名称,例如or 。哪些作为上游名称最有意义?m2markerv1.2origin/masterorigin/develop

如果您说的是“远程跟踪名称”,那么恭喜:您和 Git 的想法一致!如果不是,那么,也许这就是你总是与 Git 交战的原因。:-) 无论如何,使用远程跟踪名称作为起点告诉 Git:我不仅希望您创建这个新分支,还希望您将此远程跟踪名称设置为分支的上游。

您可以使用明确地--track做同样的事情,在这种情况下,您可以让 Git 将上游设置为您自己的分支之一。例如,要在创建时将上游设置为developmasterdevelop可以使用:

git branch --track develop master

或者:

git checkout --track -b develop master

如果您始终不喜欢--track行为(即上游设置),您可以始终添加--no-track您的命令,或者配置 Git自动将远程跟踪名称设置为上游,使用:

git config branch.autoSetupMerge false

如果您真的非常喜欢 --track行为,并且希望即使使用本地分支名称作为起点也能发生这种情况,您也可以配置 Git 来执行此操作:

git config branch.autoSetupMerge always

和选项--track--no-track如果你使用它们,覆盖配置的默认值alwaysorfalsetrue。如果您还没有配置branch.autoSetupMerge,Git 会假装您已将其设置为true,这意味着我们刚刚在上面概述的内容: 如果名称是远程跟踪名称,则默认为。--track

以上所有内容纯粹是为了方便

您可以随时使用git branch --set-upstream-to或更改或删除任何分支的上游git branch --unset-upstream。因此,您使用--trackor--no-track或或进行的任何摆弄branch.autoSetupMerge都是为了方便。将此设置为您个人认为最方便的任何内容。

最后一个转折:孤儿枝

上面,我说过创建一个新分支需要选择一个起始提交。这是真的,但也几乎是谎言。每个新的、完全空的存储库中都会出现一个极端情况。考虑:

$ mkdir newrepo
$ cd newrepo
$ git init
Initialized empty Git repository in ...

此时,您没有提交,并git branch表明您也没有分支。然而,git status告诉你你在 branch master。怎么会这样?

$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

答案是您在一个不存在的分支名称上。虽然这听起来自相矛盾,但它实际上只是 Git 中的一个特例。您进行的下一个提交将是提交:没有父级的提交。创建此提交的行为将产生一个提交哈希 ID。该提交哈希 ID 将创建分支。

因此,您所在的这个分支(Git 已将名称保存在 中HEAD)不存在,并且您无法设置其上游:

$ git branch --set-upstream-to=origin/master
fatal: branch 'master' does not exist

您必须首先通过提交来创建分支。一旦分支存在,您可以设置它的上游。

这对于一个新的空存储库来说是正确的,因为还没有提交。但对于您使用创建的任何分支名称也是如此git checkout --orphan,它执行相同的技巧:它将新分支名称写入HEAD,但实际上并不创建分支

这归结为通过提交来创建分支。所以对于这个特殊的极端情况——一个还没有真正存在的孤立分支,或者master一个新的空存储库中的分支——你“选择”分支将指向的提交,不是通过查看一些现有的提交,而是通过创建一个的提交,并在此过程中说:这个新的提交是我选择分支名称指向的位置。 新提交是根提交(没有父提交)并且分支现在存在,只有现在 Git 才能设置其上游。

因此,对于孤立分支,在分支实际存在之前,您无法设置上游。

于 2018-08-03T21:56:16.020 回答
1

我的猜测是发生了什么是这个命令:

git checkout -b "${new_branch}" "origin/dev"

意味着新分支将跟踪 origin/dev,这意味着 origin/dev 会随着本地更改而更新。

我修复它的方法是使用 --no-track,如下所示:

git branch --no-track "${new_branch}" "remotes/origin/dev"
git checkout "${new_branch}"

我还没有完全验证它是否有效,但似乎到目前为止。有点像噩梦。

于 2018-08-03T18:47:50.620 回答
1

我创建了这个别名来执行这个任务:

git config --global alias.nb '!bash -c "git fetch --prune; git checkout -b $1 --no-track ${2-origin/dev}" -'

您可以使用以下命令运行它:

$ git nb <branch name> [<source commitish>]

它使用 prune 执行完整提取,并接受两个参数,第一个是新分支名称,第二个是源分支,但如果没有提供,则默认为 origin/dev。

使用--track会设置您将您的功能直接推送到 origin/dev。

还要记住,分支只是指针,因此该命令可以读作“创建一个新的分支指针,该指针以<branch name>引用的提交命名,origin/dev而不设置远程跟踪并签出新分支”

于 2018-08-03T18:50:05.923 回答