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

时间:2014-02-26 21:29:14

标签: git

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

然而,偶尔Git允许我在没有提交或存储这些更改的情况下签出另一个分支,它会将这些更改带到我签出的分支。

这里有什么规则?更改是分阶段还是未分阶段是否重要?将更改传递给另一个分支对我没有任何意义,为什么git有时会允许它?也就是说,它在某些情况下有用吗?

6 个答案:

答案 0 :(得分:284)

初步说明

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

git checkout branch2

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

如果Git 赢了让你这样做,你必须提交你的更改,将它们永久保存在某个地方。 You may want to use git stash to save them; this is one of the things it's designed for.请注意,git stash savegit stash push实际表示"提交所有更改,但根本没有分支,请将其从我所在的位置删除现在&#34。这使得切换成为可能:您现在没有正在进行的更改。转换后,您可以git stash apply

  

补充工具栏:git stash save是旧语法; git stash push是在Git 2.13版中引入的,用于解决git stash参数的一些问题,并允许新的选项。当以基本方式使用时,两者都做同样的事情。

如果你愿意,你可以在这里停止阅读!

如果Git 赢了让您切换,那么您已经有了补救措施:使用git stashgit commit;或者,如果您的更改很容易重新创建,请使用git checkout -f强制它。这个答案完全是关于 Git让你git checkout branch2即使你开始做出一些改变。为什么它有时,而不是其他次?

这里的规则在某种程度上是简单的,在另一种情况下很复杂/难以解释:

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

那是 - 请注意,这仍然是简化的;有一些特别困难的角落案例,分段为git add s,git rm s等 - 假设你在branch1git checkout branch2必须执行此操作:

  • 对于branch1 branch2的每个文件, 1 删除该文件。< / LI>
  • 对于branch2 branch1的每个文件,请创建该文件(包含适当的内容)。
  • 对于两个分支中的每个文件,如果branch2中的版本不同,请更新工作树版本。

这些步骤中的每一步都可能破坏工作树中的某些内容:

  • 删除文件是&#34;安全&#34;如果工作树中的版本与branch1中的已提交版本相同;它不安全&#34;如果你做了改变。
  • 按照branch2中显示的方式创建文件是&#34;安全&#34;如果它现在不存在。 2 它&#34;不安全&#34;如果它现在确实存在但是有错误的&#34;内容。
  • 当然,用不同的版本替换文件的工作树版本是&#34; safe&#34;如果工作树版本已提交到branch1

创建新分支(git checkout -b newbranch总是考虑&#34;安全&#34;:不会在工作树中添加,删除或更改任何文件作为部分此过程中,索引/暂存区域也未受影响。 (警告:在创建新分支而不更改新分支的起点时,它是安全的;但如果添加另一个参数,例如git checkout -b newbranch different-start-point,则可能需要更改内容,移至different-start-point。Git将照常应用结帐安全规则。)


1 这要求我们定义文件在分支中的含义,这又需要正确定义单词 branch 。 (另请参阅What exactly do we mean by "branch"?)这里,我的意思是分支名称解析的提交:路径为 P的文件如果branch1生成哈希,则git rev-parse branch1:P中的 。如果您收到错误消息,则branch1中的文件不是。在回答此特定问题时,索引或工作树中路径 P 的存在无关紧要。因此,这里的秘诀是检查每个git rev-parsebranch-name:path的结果。这要么失败,因为文件是&#34;在&#34;最多一个分支,或者给我们两个哈希ID。如果两个哈希ID 相同,则两个分支中的文件都相同。无需更改。如果散列ID不同,则两个分支中的文件不同,必须更改为切换分支。

这里的关键概念是提交中的文件永远被冻结。您要编辑的文件显然是而不是已冻结。至少在最初阶段,我们只关注两个冻结提交之间的不匹配。 不幸的是,我们或Git也必须处理不是的文件>在提交中,您将要切换到提交,并且 在您要切换到的提交中。这导致了其余的复杂性,因为文件也可以存在于索引和/或工作树中,而不必存在我们正在使用的这两个特定的冻结提交。

2 可能会考虑&#34;安全等级&#34;如果已经存在&#34;正确的内容&#34;,那么Git毕竟不必创建它。我记得至少有一些版本的Git允许这样做,但刚刚进行的测试表明它被认为是'#34;不安全&#34;在Git 1.8.5.4中。相同的参数将适用于碰巧被修改以匹配to-be-switch-to分支的修改文件。同样,1.8.5.4只是说&#34;会被覆盖&#34;但是。另请参阅技术说明的结尾:我的记忆可能有问题,因为自从我第一次开始在版本1.5.something使用Git时,我认为读取树规则已经发生了变化。


更改是暂存还是未暂停是否重要?

是的,在某些方面。特别是,您可以进行更改,然后&#34; de-modify&#34;工作树文件。这是两个分支中的文件,branch1branch2中的文件有所不同:

$ 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

此时,工作树文件inbothbranch2中的文件匹配,即使我们在branch1上也是如此。此更改不会在提交时暂存,这是git status --short在此处显示的内容:

$ git status --short
 M inboth

空间 - 然后 - M意味着&#34;修改但不上演&#34; (或者更确切地说,工作树副本与分阶段/索引副本不同)。

$ 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中的工作副本匹配,分段文件也不会,因此git checkout会丢失该副本,而git checkout会被拒绝。

技术说明 - 仅适用于疯狂的好奇: - )

所有这些的基础实现机制是Git的索引。索引,也称为&#34; staging area&#34;,是构建 next 提交的地方:它开始匹配当前提交,即,现在已经签出的任何内容,以及然后,每当您git add一个文件时,您索引版本替换为工作树中的任何内容。

请记住,工作树是您处理文件的地方。在这里,它们具有它们的正常形式,而不是像它们在提交和索引中那样的一些特殊的有用的Git形式。因此,您从提交文件索引,然后在工作树中提取文件。更改后,您git add到索引。因此,每个文件实际上有三个位置:当前提交,索引和工作树。

当你运行git checkout branch2时,Git所做的就是将branch2 tip commit 与当前提交和索引中的任何内容进行比较。任何与现在相匹配的文件,Git都可以独自留下。一切都没有动过。 提交中的任何文件都相同,Git也可以 单独留下 - 这些是允许您切换分支的文件。

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

这导致了上面所有奇怪的角落情况。我们提交了 X Y ,它们都有文件path/to/name.txt,我们有path/to/name.txt的索引条目。也许所有三个哈希都匹配。也许其中两个匹配,一个不匹配。也许三者都不同。而且,我们可能只有 X 中的another/file.txt或仅 Y 中的git checkout,现在是或者不在索引中。这些不同情况中的每一种都需要单独考虑:Git 是否需要将文件从提交复制到索引,或从索引中删除它,从 X 切换到< EM>ÿ?如果是这样,它也必须将文件复制到工作树,或者从工作树中删除它。如果 的话,那么索引和工作树版本最好匹配至少一个已提交的版本;否则Git会破坏一些数据。

(所有这些内容的完整规则都在您所期望的{{1}}文档中描述,而不是the git read-tree documentation, under the section titled "Two Tree Merge"。)

答案 1 :(得分:44)

您有两种选择:存储您的更改:

git stash

然后让他们回来:

git stash apply

或将更改放在分支上,以便您可以获取远程分支,然后将更改合并到其上。这是git最棒的事情之一:你可以创建一个分支,提交它,然后将其他更改提取到你所在的分支上。

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

答案 2 :(得分:10)

如果新分支包含的编辑与该特定更改文件的当前分支不同,则在更改提交或存储之前,它不允许您切换分支。如果两个分支上的更改文件相同(即该文件的已提交版本),则可以自由切换。

示例:

$ 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

一个很好的例子,说明为什么你想要在进行更改时在分支之间移动,如果你在master上进行一些实验,想要提交它们,但是还没有掌握...

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

答案 3 :(得分:0)

正确答案是

git checkout -m origin/master

它将来自原始主分支的更改与您本地的甚至未提交的更改合并。

答案 4 :(得分:0)

如果您根本不希望执行此更改,请执行 git reset --hard

接下来,您可以签出所需分支,但请记住,未提交的更改将丢失。

答案 5 :(得分:0)

我最近也遇到了同样的问题。我的理解是,如果您要签入的分支有一个您修改过的文件,并且恰好该分支也对其进行了修改和提交。然后git会阻止您切换到分支,以确保在提交或隐藏之前更改保持安全。