git checkout命令无法按预期工作

时间:2018-08-03 18:13:46

标签: git git-checkout

我有这个:

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将使用bar作为“基础”签出一个名为foo的新分支(请更正我的术语)。为什么那样行不通?

我使用的是git版本:2.17.1

也许我应该改用它:

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

更新:可能发生的情况是原始/开发人员正在使用本地要素分支中的更改进行更新。因此,我使用的第一个命令确实使用origin / dev作为基础,这是因为Origin / dev正在从功能分支中查看更新,因为已设置跟踪...

3 个答案:

答案 0 :(得分:2)

您在自己的回答中提到:

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

表示新分支将跟踪origin / dev ...

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

更合适/更好/更现代的术语是,新分支将origin/dev设置为其上游。每个分支名称可以具有一个上游设置。该上游仅仅是分支的名称(例如master)或远程跟踪名称(例如origin/master)。此设置及其实际值的存在会影响git status报告状态,git mergegit rebase在不使用其他参数的情况下的行为,以及git pullgit push的行为没有其他参数。 1

或者,分支可以上游​​没有 no 。如果分支上游没有 no ,则git status不会报告该分支与其不存在的上游的比较,git mergegit rebase需要更多参数,依此类推。请注意,上游设置(或缺少上游设置)与分支名称指向的提交哈希无关。

(另请参见Make an existing Git branch track a remote branch?

  

我所做的修复工作是使用--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-refgit rev-parse能够提取分支的上游设置。此外,Git的某些部分无需费心去验证上游设置的名称(如果已设置)是否有效,但是Git的其他部分却可以;所以这里有很多可能性。


背景(详细信息)

git branchgit checkout的精确默认操作有些复杂。 Git试图提供帮助,但结果只是一团糟。

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

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

(在图形中有一个无法解释的纽结),我们可以查找提交G的实际哈希ID,然后在此处附加一个新的分支名称。假设G的实际哈希ID以491ab94开头,因此我们运行:

git branch marker 491ab94

结果如下:

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

现在有两个分支,以前只有一个。新分支名为marker,标识提交G。现有的master不变:它将继续标识落实J

创建新分支需要选择提交

无论何时创建新的分支名称,都必须回答有关Git的问题:该分支名称应标识哪个现有提交?在这里,我们通过其哈希ID选择了G。由于哈希ID不是名称,因此无法将该ID 设置为新分支的上游。

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

git branch m2

由于HEAD当前已附加到master,因此m2指向与master相同的提交:

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

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

您还可以使用git checkout创建新的分支名称。使用git branchgit checkout的主要区别在于git checkout HEAD附加到新分支,从而移动(签出)另一个如果需要的话,提交比以前的当前提交要大。例如:

git checkout -b m2

(而不是git branch m2)根本不需要移动,也不移动,但是确实重新附着了HEAD,产生了:

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

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

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

我们刚刚看到,如果让Git默认为HEAD,则Git 不会为新分支设置上游。此外,如果您通过哈希ID 选择特定的提交,则Git 不会设置上游。但是有时候,如果您通过 name 选择特定的提交,则Git 设置上游。

所以:当 Git设置上游时?好吧,考虑一下我们用于提交的名称的种类。其中一些是我们自己的分支名称,例如上面的masterm2marker。其中一些是标签名称,例如v1.2。有些是远程跟踪名称,例如origin/masterorigin/develop。哪些名称作为上游名称最有意义?

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

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

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选项,请覆盖alwaysfalsetrue的配置默认值。如果您尚未配置branch.autoSetupMerge,则Git会假装将其设置为true,这就是我们上面概述的内容:如果名称是远程跟踪,则默认为--track名称。

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

您随时可以使用git branch --set-upstream-togit branch --unset-upstream随时更改或删除任何分支的上游。因此,您对--track--no-trackbranch.autoSetupMerge做的任何事情 meant 都会很方便。将此设置为您觉得最方便的

最后一弯:孤枝

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

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

此时,您没有 个提交,git branch显示您也没有没有分支。但是,git status告诉您您在分支master上。怎么会这样?

$ git status
On branch master

No commits yet

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

答案是您所在的分支名称不存在。尽管这听起来有些矛盾,但这实际上只是Git中的一个特例。您进行的下一个提交将是一个 root 提交:没有父项的提交。创建此提交的动作将产生一个提交哈希ID。该提交哈希ID将创建分支。

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

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

您必须首先通过提交来创建分支。分支存在后,然后您可以设置其上游。

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

这归结为分支创建是通过提交来进行的。因此,对于这种特殊情况(实际上还不存在的孤立分支,或新的空存储库中的master分支),您可以“选择”分支将指向的提交,而不用查看现有的提交提交,但通过创建一个 new 提交,并在此过程中说:此新提交是我选择分支名称指向的位置。新提交是一个根提交(没有父项)并且分支现在存在,并且只有Git才能设置其上游。

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

答案 1 :(得分: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}"

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

答案 2 :(得分: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的新分支指针,而无需设置远程跟踪并签出新的分支”