大多数情况下,当我尝试切换到另一个已存在的分支时,如果当前分支有未提交的更改,Git会不允许我切换。 因此,我必须先提交或隐藏这些更改。
然而,偶尔地,Git允许我在不提交或隐藏更改的情况下切换到另一个分支,并将这些更改带到我要切换的分支上。
这里有什么规则吗? 这是否取决于更改是暂存还是未暂存? 把更改带到另一个分支似乎没有任何意义,为什么Git有时允许这样做? 也就是说,这在某些情况下是否有帮助?
大多数情况下,当我尝试切换到另一个已存在的分支时,如果当前分支有未提交的更改,Git会不允许我切换。 因此,我必须先提交或隐藏这些更改。
然而,偶尔地,Git允许我在不提交或隐藏更改的情况下切换到另一个分支,并将这些更改带到我要切换的分支上。
这里有什么规则吗? 这是否取决于更改是暂存还是未暂存? 把更改带到另一个分支似乎没有任何意义,为什么Git有时允许这样做? 也就是说,这在某些情况下是否有帮助?
本回答旨在解释Git的行为原理,不是推荐采用任何特定的工作流程。(我个人偏好直接提交,避免使用git stash
并且不试图过于狡猾,但其他人可能喜欢其他方法。)
观察到的情况是,在你开始在branch1
中工作(忘记或没有意识到先切换到另一个分支branch2
较好),你运行以下命令:
git checkout branch2
侧边栏:git stash save是旧语法;git stash push在Git 2.13版本中引入,用于修复git stash参数的一些问题并允许新选项。在基本用法下,两者都是相同的。
如果 Git 不允许您切换分支,您已经有解决方法:使用 git stash
或 git commit
;或者,如果您的更改很容易重新创建,请使用 git checkout -f
强制执行。本答案讨论的是当您开始进行一些更改时 Git 何时允许您 git checkout branch2
。为什么有时会起作用,而有时不会呢?
这里的规则在某种程度上很简单,但在另一方面却很复杂/难以解释:
也就是说——请注意,这仍然是简化的;还有一些关于已暂存的 git add
、git rm
等的额外困难情况——假设您在 branch1
上。执行 git checkout branch2
将需要执行以下操作:
branch1
中而不在branch2
中的文件,删除该文件。1branch2
中而不在branch1
中的文件,创建该文件(具有适当的内容)。branch2
中的版本不同,则更新工作树版本。这些步骤中的每一个都可能会覆盖你的工作树中的某些内容:
branch1
提交的版本相同,则删除文件是“安全”的;如果您已经进行了更改,则是“不安全”的。branch2
中显示的方式创建文件,如果该文件现在不存在,则是“安全”的。2如果该文件现在存在但具有“错误”的内容,则为“不安全”。branch1
,则用不同版本替换工作树版本的文件是“安全”的。创建一个新分支(git checkout -b newbranch
)始终被认为是“安全”的:这个过程中不会向工作树中添加、删除或更改任何文件,并且索引/暂存区也保持不变。(警告:当创建一个新分支时,如果更改新分支的起始点,例如git checkout -b newbranch different-start-point
,这可能需要更改一些内容,以移动到different-start-point
。Git将按照通常的检查安全规则应用检查。)
1这需要我们定义文件在分支中是什么意思,而这又需要适当地定义"branch"的含义。(另请参见 我们所说的“分支”到底是什么?) 在这里,我真正的意思是分支名称解析为提交:如果路径为P
的文件的git rev-parse branch1:P
会产生哈希值,则该文件位于branch1
中。 如果您得到错误消息,则该文件不在branch1
中。当回答这个问题时,索引或工作树中路径P
的存在并不重要。因此,这里的秘密是检查每个branch-name:path
的git rev-parse
结果。 这要么失败,因为该文件最多只能“在”一个分支中,要么给我们两个哈希ID。 如果这两个哈希ID相同,则在两个分支中的文件相同。不需要更改。 如果哈希ID不同,则两个分支中的文件不同,必须更改才能切换分支。
2如果文件已经存在且“内容正确”,那么它可能被认为是“有点安全的”,这样Git就不必创建它了。我记得至少有一些Git版本允许这样做,但是现在的测试显示Git 1.8.5.4认为这是“不安全的”。同样的论点也适用于已修改的文件,恰好被修改以匹配要切换到的分支。再次说明,1.8.5.4只是说“会被覆盖”。请参见技术注释的末尾:我的记忆可能有误,因为我认为自从我第一次使用Git(版本为1.5)以来,read-tree规则并没有改变。
在某些方面是有影响的。特别地,您可以将更改暂存,然后“取消修改”工作树文件。以下是两个分支中不同的文件,在branch1
和branch2
中:
$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth
inboth
与branch2
中的文件匹配,尽管我们在branch1
上。这个更改没有为提交做准备,这就是git status --short
在这里显示的内容:$ git status --short
M inboth
$ git checkout branch2
error: Your local changes ...
好的,现在让我们暂存工作树副本,我们已经知道它也与branch2
中的副本匹配。
$ git add inboth
$ git status --short
M inboth
$ git checkout branch2
Switched to branch 'branch2'
这里暂存和工作副本都与branch2
中的内容相匹配,因此允许检出。
让我们再试一步:
$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches
$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
$
但是git checkout
不允许检出:
$ git checkout branch2
error: Your local changes ...
让我们将 branch2
版本设置为工作版本:
$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...
branch2
中的副本匹配,暂存文件却不匹配,因此执行git checkout
会丢失该副本,因此git checkout
被拒绝。
所有这些的底层实现机制都是基于Git的索引。索引,也称为“暂存区”,是您构建下一个提交的地方:它开始匹配当前提交,即您现在检出的内容,然后每次您git add
一个文件时,您就会用您工作树中的内容替换索引版本。
git add
将其添加到索引中。因此,每个文件实际上有三个位置:当前提交、索引和工作树。
当你运行git checkout branch2
时,Git在底层比较branch2
的末尾提交与当前提交和索引中的内容。任何与现在相匹配的文件,Git都可以保持不变。它们都没有受到影响。任何在两个提交中相同的文件,Git也可以保持不变——这些文件让你可以切换分支。
所有这些的完整规则都在git read-tree
文档中的“Two Tree Merge”章节中描述,而不是像你可能预期的那样在git checkout
文档中。
你有两个选择:把你的更改存储起来:
git stash
然后稍后再把它们拿回来:
git stash apply
或者你可以将更改放在一个分支上,这样你就可以获取远程分支,然后将你的更改合并到它上面。这是Git最伟大的事情之一:你可以创建一个分支,提交更改,然后将其他更改拉取到你所在的分支。
你说这没什么意义,但你只是这么做是为了在拉取后随意合并它们。显然,你的另一个选择是在你的分支副本上进行提交,然后再执行拉取操作。假设你不想那样做(在这种情况下,我感到困惑,不知道为什么你不想要一个分支),或者你害怕冲突。
git stash pop
命令,如果应用成功,它将从列表中删除该存储。 - Michael Leonardgit stash pop
,除非您想在存储库历史记录中保留存储记录。 - Damilola Olowookere$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "adding file.txt"
$ git checkout -b experiment
$ echo 'goodbye world' >> file.txt
$ git add file.txt
$ git commit -m "added text"
# experiment now contains changes that master doesn't have
# any future changes to this file will keep you from changing branches
# until the changes are stashed or committed
$ echo "and we're back" >> file.txt # making additional changes
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
file.txt
Please, commit your changes or stash them before you can switch branches.
Aborting
这适用于未跟踪的文件和已跟踪的文件。以下是一个未跟踪文件的示例。
示例:
$ git checkout -b experimental # creates new branch 'experimental'
$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "added file.txt"
$ git checkout master # master does not have file.txt
$ echo 'goodbye world' > file.txt
$ git checkout experimental
error: The following untracked working tree files would be overwritten by checkout:
file.txt
Please move or remove them before you can switch branches.
Aborting
为什么在进行更改时需要在分支之间移动的一个很好的例子是,如果您正在主分支上执行某些实验,并希望提交它们,但还不想立即提交到主分支...
$ echo 'experimental change' >> file.txt # change to existing tracked file
# I want to save these, but not on master
$ git checkout -b experiment
M file.txt
Switched to branch 'experiment'
$ git add file.txt
$ git commit -m "possible modification for file.txt"
正确答案是
git checkout -m origin/master
它会将远程主分支(origin master)的变更与您的本地未提交的变更合并。
git checkout -b <new-branch>
来从现有分支检出一个新的分支。这将创建一个新的分支,你可以将你的更改提交到这个新的分支中。git reset --hard
。
git checkout -m
,它会将工作目录和索引的更改合并到新的检出中。 - jthill