大多数时候,当我尝试检出另一个现有分支时,如果我在当前分支上有一些未提交的更改,Git不允许我。所以我必须首先提交或存储这些更改。
然而,偶尔Git允许我在没有提交或存储这些更改的情况下签出另一个分支,它会将这些更改带到我签出的分支。
这里有什么规则?更改是分阶段还是未分阶段是否重要?将更改传递给另一个分支对我没有任何意义,为什么git有时会允许它?也就是说,它在某些情况下有用吗?
答案 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 save
或git stash push
实际表示"提交所有更改,但根本没有分支,请将其从我所在的位置删除现在&#34。这使得切换成为可能:您现在没有正在进行的更改。转换后,您可以git stash apply
。
补充工具栏:
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
s,git rm
s等 - 假设你在branch1
。 git checkout branch2
必须执行此操作:
branch1
中 且branch2
中不的每个文件, 1 删除该文件。< / LI>
branch2
中 且branch1
中不的每个文件,请创建该文件(包含适当的内容)。branch2
中的版本不同,请更新工作树版本。这些步骤中的每一步都可能破坏工作树中的某些内容:
branch1
中的已提交版本相同;它不安全&#34;如果你做了改变。branch2
中显示的方式创建文件是&#34;安全&#34;如果它现在不存在。 2 它&#34;不安全&#34;如果它现在确实存在但是有错误的&#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-parse
上branch-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;工作树文件。这是两个分支中的文件,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
空间 - 然后 - 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会阻止您切换到分支,以确保在提交或隐藏之前更改保持安全。