在当前分支有未提交的更改时检出另一个分支

大多数情况下,当我尝试检出另一个现有的分支时,Git不允许我在当前分支上有一些未提交的更改。 所以我必须首先提交或存储这些更改。

然而偶尔Git确实允许我签出另一个分支而不提交或存储这些更改,并将这些更改带到我签出的分支。

这里的规则是什么? 这些变化是上演还是非上演? 把这些改变带到另一个分支对我来说没有任何意义,为什么有时候git会允许它呢? 也就是说,在某些情况下有帮助吗?

初步笔记

这里的观察是,在开始在branch1工作branch1 (忘记或者没有意识到先切换到另一个分支branch2是好的),可以运行:

 git checkout branch2 

有时Git会说:“好吧,你现在在branch2上!” 有时,Git说:“我不能这样做,我会失去你的一些改变。”

如果Git 不会让你这么做的话,那么你必须提交你的修改,把它们保存在永久的地方。 你可能想用git stash来保存它们; 这是它的设计目的之一。 注意, git stash save实际上意味着 “提交所有更改,但完全没有分支,然后将它们从我现在所在的位置删除”。 这使得切换成为可能:您现在没有正在进行的更改。 然后你可以在切换之后git stash apply它们git stash apply它们。

如果你喜欢,你可以在这里停止阅读!

如果Git 不会让你切换,你已经有了一个补救办法:使用git stashgit commit ; 或者,如果您的更改很容易重新创建,请使用git checkout -f强制执行。 这个答案是所有关于何时 Git会让你git checkout branch2即使你开始做一些改变。 为什么它有时会工作,而不是其他时间?

这里的规则在一个方面是简单的,在另一个方面是复杂/难以解释的:

当且仅当所述切换不需要破坏这些改变时,可以在工作树中切换具有未提交改变的分支。

这是 – 请注意,这仍然是简化的; 有一些额外的困难的角落情况下,分阶段的git add s, git rm s等 – 假设你在branch1 。 一个git checkout branch2将不得不这样做:

  • 对于branch1不是 branch1每个文件,请删除该文件。
  • 对于branch1不是 branch1每个文件,创建该文件(使用适当的内容)。
  • 对于两个分支中的每个文件,如果branch2的版本不同,则更新工作树版本。

这些步骤中的每一步都可能会在工作树中摧毁一些东西:

  • 如果工作树中的版本与branch1的已提交版本相同,则删除文件是“安全”的; 如果你做了改变,这是“不安全的”。
  • 如果它现在不存在,按照出现在branch2的方式创建一个文件是“安全的”。 1现在确实存在但“内容错误”是“不安全的”。
  • 当然,如果工作树版本已经提交到branch1 ,用不同版本替换文件的工作树版本是“安全的”。

创建一个新的分支( git checkout -b newbranch总是被认为是“安全的”:作为这个过程的一部分,在工作树中不会添加,删除或修改文件,索引/登台区也是不变的。 (注意:创建一个新的分支而不改变新分支的起始点是安全的,但是如果你添加另一个参数,例如, git checkout -b newbranch different-start-point ,这可能需要改变,移动到different-start-point git checkout -b newbranch different-start-point然后,Git将像往常一样应用结帐安全规则。)

这些变化是上演还是非上演?

是的,在某些方面。 特别是,您可以进行更改,然后“修改”工作树文件。 这是两个分支中的文件,在分支branch1和分支2中是不同的:

 $ 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 

此时,虽然我们在branch2 ,但工作树文件与branch2文件相匹配。 这个改变是不会提交的,这就是git status --short在这里显示的内容:

 $ git status --short M inboth 

M空间意味着“修改,但不分阶段”(或更确切地说,工作树复制不同于阶段/索引副本)。

 $ 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 

这里的两个M表示:暂存文件与HEAD文件不同, 并且工作文件与暂存文件不同。 工作树版本与branch1 (又名HEAD )版本匹配:

 $ 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副本相匹配,但是staged文件不会,所以git checkout会丢失该副本,并且git checkout被拒绝。


1如果它已经存在与“正确的内容”,它可能被认为是“安全的”,所以Git不必创建它。 我记得至少有一些版本的Git允许这样做,但刚才的测试表明它在Git 1.8.5.4中被认为是“不安全的”。 相同的参数将适用于修改后的文件,该文件恰好被修改为匹配待切换分支。 不过,1.8.5.4只是说“会被覆盖”。 请参阅技术说明的结尾:我的内存可能有问题,因为我不认为阅读树规则已经改变,因为我第一次开始使用1.5版本的Git。


技术笔记 – 只为疯狂好奇:-)

所有这一切的底层实现机制都是Git的索引 。 该索引也称为“暂存区域”,是您构建下一个提交的地方:它开始匹配当前的提交,即无论您现在签出了什么,然后每次您git add一个文件,您将替换索引版本与你的工作树中的任何东西。

请记住, 工作树就是你在文件上的工作。 在这里,他们有他们的正常形式,而不是像他们在提交和索引中的一些特殊的唯一有用的Git形式。 所以你一个提交中提取一个文件, 通过索引,然后进入工作树。 改变它后,你git addgit add到索引。 所以实际上每个文件都有三个地方:当前提交,索引和工作树。

当你运行git checkout branch2 ,Git在封面下面做的是将branch2提示提交与当前提交和索引中的提示进行比较。 任何与现在相匹配的文件,Git都可以独自离开。 这一切都没有改变。 任何提交的文件都是相同的,Git也可以独自离开 – 这些是让你切换分支的文件。

由于这个索引,Git中的很多,包括commit-switching,都是相对较快 。 索引中的实际内容不是每个文件本身,而是每个文件的散列 。 文件本身的副本是作为Git在存储库中调用blob对象的方式存储的。 这与文件在提交中的存储方式类似:commit实际上不包含文件 ,它们只是将Git引导到每个文件的哈希ID。 所以Git可以比较哈希ID(目前是160字节长的字符串)来决定提交XY是否具有相同的文件。 然后,它可以将这些散列ID与索引中的散列ID进行比较。

这就是上面所有古怪的角落案例。 我们提交了XY ,它们都有文件path/to/name.txt ,并且我们有一个path/to/name.txt的索引条目。 也许所有三个哈希匹配。 也许两个匹配,一个不匹配。 也许所有三个不同。 而且,我们可能还有another/file.txt ,它只在X中,或者只在Y中 ,现在或者不在索引中。 每种情况都需要自己单独考虑:Git 是否需要将文件从提交复制到索引,或者从索引中删除文件,从X切换到Y ? 如果是这样,它也必须将文件复制到工作树,或从工作树中删除它。 如果是这样的话,索引和工作树版本至少有一个承诺版本匹配; 否则Git会破坏一些数据。

(所有这些的完整规则都是在你所期望的git checkout文档中描述的,而不是你所期望的git read-tree文档,而是标题为“ git read-tree合并”一节中的git read-tree文档 。

您有两个选择:隐藏您的更改:

 git stash 

然后再把它们拿回来:

 git stash apply 

或者将更改放在分支上,以便获得远程分支,然后将更改合并到该分支上。 这是关于git最重要的事情之一:你可以创建一个分支,执行它,然后将其他更改提交到你所在的分支上。

你说这没有任何意义,但你只是这样做,所以你可以在拉动之后随意合并它们。 显然你的另一种选择是在你的副本上进行分支,然后进行拉取。 推定是你不想这样做(在这种情况下,我很疑惑你不想要一个分支),或者你害怕冲突。

如果新分支包含与当前分支不同的编辑,那么它将不允许您切换分支,直到提交或隐藏更改为止。 如果更改的文件在两个分支(即该文件的承诺版本)上相同,则可以自由切换。

例:

 $ 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" 

完全同意@totrek作为一个更简单的解释..让我们说我们有一个文件已经提交并推送到分支A上的分支B不存在,所以你不能移动到一个容器退出在另一个分支

如果两个分支都指向相同的提交,git将允许结账,否则不会。