git 当前分支上有未提交的更改时签出另一个分支
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22053757/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Checkout another branch when there are uncommitted changes on the current branch
提问by Xufeng
Most of the time when I try to checkout another existing branch, Git doesn't allow me if I have some uncommitted changes on the current branch. So I'll have to commit or stash those changes first.
大多数情况下,当我尝试检出另一个现有分支时,如果我在当前分支上有一些未提交的更改,Git 不允许我这样做。所以我必须首先提交或隐藏这些更改。
However, occasionally Git does allow me to checkout another branch without committing or stashing those changes, and it will carry those changes to the branch I checkout.
但是,有时 Git 确实允许我在不提交或隐藏这些更改的情况下检出另一个分支,并且它会将这些更改传送到我检出的分支。
What is the rule here? Does it matter whether the changes are staged or unstaged? Carrying the changes to another branch doesn't make any sense to me, why does git allow it sometimes? That is, is it helpful in some situations?
这里的规则是什么?更改是暂存的还是未暂存的有关系吗?将更改带到另一个分支对我来说没有任何意义,为什么 git 有时会允许它?也就是说,它在某些情况下有用吗?
回答by torek
Preliminary notes
初步说明
The observation here is that, after you start working in branch1
(forgetting or not realizing that it would be good to switch to a different branch branch2
first), you run:
这里的观察是,在您开始工作后branch1
(忘记或没有意识到首先切换到不同的分支会很好branch2
),您运行:
git checkout branch2
Sometimes Git says "OK, you're on branch2 now!" Sometimes, Git says "I can't do that, I'd lose some of your changes."
有时 Git 会说“好吧,你现在在 branch2 上!” 有时,Git 会说“我不能那样做,我会丢失你的一些更改。”
If Git won'tlet you do it, you have to commit your changes, to save them somewhere permanent. You may want to use git stash
to save them; this is one of the things it's designed for.Note that git stash save
or git stash push
actually means"Commit all the changes, but on no branch at all, then remove them from where I am now." That makes it possible to switch: you now have no in-progress changes. You can then git stash apply
them after switching.
如果 Git不允许你这样做,你必须提交你的更改,将它们永久保存在某个地方。 你可能想用它git stash
来保存它们;这是它的设计目的之一。请注意,git stash save
或git stash push
实际上意味着“提交所有更改,但根本不在任何分支上,然后将它们从我现在所在的位置删除。” 这使得切换成为可能:您现在没有正在进行的更改。然后你可以git stash apply
在切换后使用它们。
Sidebar:
git stash save
is the old syntax;git stash push
was introduced in Git version 2.13, to fix up some problems with the arguments togit stash
and allow for new options. Both do the same thing, when used in the basic ways.
侧边栏:
git stash save
是旧语法;git stash push
是在 Git 2.13 版中引入的,用于修复参数的一些问题git stash
并允许新选项。当以基本方式使用时,两者都做同样的事情。
You can stop reading here, if you like!
如果你愿意,你可以在这里停止阅读!
If Git won'tlet you switch, you already have a remedy: use git stash
or git commit
; or, if your changes are trivial to re-create, use git checkout -f
to force it. This answer is all about whenGit will let you git checkout branch2
even though you started making some changes. Why does it work sometimes, and not othertimes?
如果混帐不会让你切换,你已经有一个补救措施:使用git stash
或git commit
; 或者,如果您的更改很容易重新创建,请使用git checkout -f
强制它。这个答案是所有关于何时混帐会让你git checkout branch2
即使你开始做一些改变。为什么它有时有效,而其他时候无效?
The rule here is simple in one way, and complicated/hard-to-explain in another:
这里的规则一方面很简单,另一方面很复杂/难以解释:
You may switch branches with uncommitted changes in the work-tree if and only if said switching does not require clobbering those changes.
当且仅当所述切换不需要破坏这些更改时,您才可以在工作树中切换带有未提交更改的分支。
That is—and please note that this is still simplified; there are some extra-difficult corner cases with staged git add
s, git rm
s and such—suppose you are on branch1
. A git checkout branch2
would have to do this:
那就是——请注意,这仍然是简化的;有一些特别困难的极端情况,包括分阶段的git add
s、git rm
s 等等——假设你在branch1
. Agit checkout branch2
必须这样做:
- For every file that isin
branch1
and notinbranch2
,1remove that file. - For every file that isin
branch2
and notinbranch1
, create that file (with appropriate contents). - For every file that is in both branches, if the version in
branch2
is different, update the working tree version.
- 对于每一个文件,该文件是在
branch1
与不中branch2
,1删除该文件。 - 对于每一个文件,该文件是在
branch2
与不中branch1
,创建该文件(在适当的内容)。 - 对于两个分支中的每个文件,如果版本
branch2
不同,则更新工作树版本。
Each of these steps could clobber something in your work-tree:
这些步骤中的每一个都可能会破坏您的工作树中的某些内容:
- Removing a file is "safe" if the version in the work-tree is the same as the committed version in
branch1
; it's "unsafe" if you've made changes. - Creating a file the way it appears in
branch2
is "safe" if it does not exist now.2It's "unsafe" if it does exist now but has the "wrong" contents. - And of course, replacing the work-tree version of a file with a different version is "safe" if the work-tree version is already committed to
branch1
.
- 如果工作树中的版本与 中提交的版本相同,则删除文件是“安全的”
branch1
;如果您进行了更改,则它是“不安全的”。 - 如果文件
branch2
现在不存在,则以它出现的方式创建文件是“安全的”。2如果它现在确实存在但包含“错误”的内容,则它是“不安全的”。 - 当然,如果工作树版本已经提交到
branch1
.
Creating a new branch (git checkout -b newbranch
) is alwaysconsidered "safe": no files will be added, removed, or altered in the work-tree as part of this process, and the index/staging-area is also untouched. (Caveat: it's safe when creating a new branch without changing the new branch's starting-point; but if you add another argument, e.g., git checkout -b newbranch different-start-point
, this might have to change things, to move to different-start-point
. Git will then apply the checkout safety rules as usual.)
创建新分支 ( git checkout -b newbranch
)始终被认为是“安全的”:作为此过程的一部分,不会在工作树中添加、删除或更改任何文件,并且索引/暂存区也不会受到影响。(警告:在不更改新分支的起点的情况下创建新分支是安全的;但是如果您添加另一个参数,例如git checkout -b newbranch different-start-point
,这可能需要更改内容以移动到different-start-point
。然后 Git 将照常应用结帐安全规则.)
1This requires that we define what it means for a file to be in a branch, which in turn requires defining the word branchproperly. (See also What exactly do we mean by "branch"?) Here, what I really mean is the commit to which the branch-name resolves:a file whose path is P
isin branch1
if git rev-parse branch1:P
produces a hash. That file is notin branch1
if you get an error message instead. The existence of path P
in your index or work-tree is not relevant when answering this particular question. Thus, the secret here is to examine the result of git rev-parse
on each branch-name:path
. This either fails because the file is "in" at most one branch, or gives us two hash IDs. If the two hash IDs are the same, the file is the same in both branches. No changing is required. If the hash IDs differ, the file is different in the two branches, and must be changed to switch branches.
1这要求我们定义文件在分支中的含义,这反过来又需要正确定义分支这个词。(参见我们究竟由“分支”是什么意思?)这里,我真正的意思是提交到分支名称解析:其路径是一个文件是在如果产生了哈希值。如果您收到错误消息,则该文件不在。回答此特定问题时,索引或工作树中是否存在路径无关紧要。因此,这里的秘密是检查每个P
branch1
git rev-parse branch1:P
branch1
P
git rev-parse
branch-name:path
. 这要么因为文件“在”最多一个分支而失败,要么给了我们两个哈希 ID。如果两个哈希 ID相同,则两个分支中的文件相同。无需更改。如果hash ID不同,说明两个分支的文件不同,必须改成切换分支。
The key notion here is that files incommits are frozen forever. Files you will edit are obviouslynot frozen. We are, at least initially, looking only at the mismatches between two frozen commits.Unfortunately, we—or Git—also have to deal with files that aren'tin the commit you're going to switch away from and arein the commit you're going to switch to. This leads to the remaining complications, since files can also exist in the index and/or in the work-tree, without having to exist these two particular frozen commits we're working with.
这里的关键概念是提交中的文件永远冻结。您将编辑的文件显然没有被冻结。至少在最初,我们只关注两个冻结提交之间的不匹配。不幸的是,我们-或Git的,还必须处理的文件是不是在提交你打算从开关路程,是在提交你要切换到。这导致了剩余的复杂性,因为文件也可以存在于索引和/或工作树中,而不必存在我们正在处理的这两个特定的冻结提交。
2It might be considered "sort-of-safe" if it already exists with the "right contents", so that Git does not have to create it after all. I recall at least some versions of Git allowing this, but testing just now shows it to be considered "unsafe" in Git 1.8.5.4. The same argument would apply to a modified file that happens to be modified to match the to-be-switch-to branch. Again, 1.8.5.4 just says "would be overwritten", though. See the end of the technical notes as well: my memory may be faulty as I don't think the read-tree rules have changed since I first started using Git at version 1.5.something.
2如果它已经存在“正确的内容”,则它可能被认为是“安全的”,因此 Git 毕竟不必创建它。我记得至少有一些 Git 版本允许这样做,但刚刚的测试表明它在 Git 1.8.5.4 中被认为是“不安全的”。相同的参数适用于修改后的文件,该文件恰好被修改以匹配 to-be-switch-to 分支。同样,1.8.5.4 只是说“将被覆盖”。另请参阅技术说明的结尾:我的记忆可能有问题,因为我认为自从我在 1.5.something 版本中第一次开始使用 Git 以来,读取树规则没有改变。
Does it matter whether the changes are staged or unstaged?
更改是暂存的还是未暂存的有关系吗?
Yes, in some ways. In particular, you can stage a change, then "de-modify" the work tree file. Here's a file in two branches, that's different in branch1
and branch2
:
是的,在某些方面。特别是,您可以暂存更改,然后“取消修改”工作树文件。这是两个分支中的文件,在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
At this point, the working tree file inboth
matches the one in branch2
, even though we're on branch1
. This change is not staged for commit, which is what git status --short
shows here:
此时,工作树文件inboth
与 中的文件匹配branch2
,即使我们在branch1
. 此更改不会为提交而暂存,如下git status --short
所示:
$ git status --short
M inboth
The space-then-M means "modified but not staged" (or more precisely, working-tree copy differs from staged/index copy).
space-then-M 表示“修改但未暂存”(或更准确地说,工作树副本不同于暂存/索引副本)。
$ git checkout branch2
error: Your local changes ...
OK, now let's stage the working-tree copy, which we already know also matches the copy in branch2
.
好的,现在让我们暂存工作树副本,我们已经知道它也与branch2
.
$ git add inboth
$ git status --short
M inboth
$ git checkout branch2
Switched to branch 'branch2'
Here the staged-and-working copies both matched what was in branch2
, so the checkout was allowed.
此处暂存和工作副本都与 中的内容匹配branch2
,因此允许结帐。
Let's try another step:
让我们尝试另一个步骤:
$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches
The change I made is lost from the staging area now (because checkout writes through the staging area). This is a bit of a corner case. The change is not gone, but the fact that I had staged it, isgone.
我所做的更改现在从暂存区丢失了(因为结帐是通过暂存区写入的)。这是一个角落案例。这种变化是不是走了,但我已经上演它其实是走了。
Let's stage a third variant of the file, different from either branch-copy, then set the working copy to match the current branch version:
让我们暂存文件的第三个变体,与任一分支副本不同,然后设置工作副本以匹配当前分支版本:
$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth
The two M
s here mean: staged file differs from HEAD
file, and, working-tree file differs from staged file. The working-tree version does match the branch1
(aka HEAD
) version:
M
这里的两个s 的意思是:staged file 与 file 不同HEAD
,以及,working-tree file 与 staged 文件不同。工作树版本与branch1
(又名HEAD
)版本匹配:
$ git diff HEAD
$
But git checkout
won't allow the checkout:
但git checkout
不允许结帐:
$ git checkout branch2
error: Your local changes ...
Let's set the branch2
version as the working version:
让我们将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 ...
Even though the current working copy matches the one in branch2
, the staged file does not, so a git checkout
would lose that copy, and the git checkout
is rejected.
即使当前的工作副本与 中的匹配branch2
,暂存文件也不匹配,因此 agit checkout
将丢失该副本,并且git checkout
被拒绝。
Technical notes—only for the insanely curious :-)
技术说明 - 仅适用于非常好奇的 :-)
The underlying implementation mechanism for all of this is Git's index. The index, also called the "staging area", is where you build the nextcommit: it starts out matching the current commit, i.e., whatever you have checked-out now, and then each time you git add
a file, you replacethe index version with whatever you have in your work-tree.
所有这一切的底层实现机制是 Git 的index。索引,也称为“暂存区”,是您构建下一次提交的地方:它开始匹配当前提交,即,无论您现在签出什么,然后每次git add
创建文件时,您都会替换索引版本使用您的工作树中的任何内容。
Remember, the work-treeis where you work on your files. Here, they have their normal form, rather than some special only-useful-to-Git form like they do in commits and in the index. So you extract a file froma commit, throughthe index, and then on into the work-tree. After changing it, you git add
it to the index. So there are in fact three places for each file: the current commit, the index, and the work-tree.
请记住,工作树是您处理文件的地方。在这里,它们有它们的正常形式,而不是像在提交和索引中那样的一些特殊的仅对 Git 有用的形式。所以你从提交中提取一个文件,通过索引,然后进入工作树。更改后,您将git add
其添加到索引中。所以实际上每个文件都有三个位置:当前提交、索引和工作树。
When you run git checkout branch2
, what Git does underneath the covers is to compare the tip commitof branch2
to whatever is in both the current commit and the index now. Any file that matches what's there now, Git can leave alone. It's all untouched. Any file that's the same in both commits, Git can alsoleave alone—and these are the ones that let you switch branches.
当您运行git checkout branch2
,什么混帐做封面的下面是比较尖承诺的branch2
到无论是在双方最近提交现在的指数。任何与现有文件匹配的文件,Git 都可以不理会。这一切都没有受到影响。任何在两次提交中都相同的文件,Git 也可以单独保留——这些是让你切换分支的文件。
Much of Git, including commit-switching, is relatively fast because ofthis index. What's actually in the index is not each file itself, but rather each file's hash. The copy of the file itself is stored as what Git calls a blob object, in the repository. This is similar to how the files are stored in commits as well: commits don't actually contain the files, they just lead Git to the hash ID of each file. So Git can compare hash IDs—currently 160-bit-long strings—to decide if commits Xand Yhave the samefile or not. It can then compare those hash IDs to the hash ID in the index, too.
由于这个索引,Git 的大部分,包括提交切换,都相对较快。索引中的实际内容不是每个文件本身,而是每个文件的hash。文件本身的副本作为 Git 称为blob object的内容存储在存储库中。这也类似于文件在提交中的存储方式:提交实际上并不包含文件,它们只是将 Git 引导到每个文件的哈希 ID。因此,Git 可以比较哈希 ID(目前为 160 位长的字符串)来决定提交X和Y是否具有相同的文件。然后它也可以将这些哈希 ID 与索引中的哈希 ID 进行比较。
This is what leads to all the oddball corner cases above. We have commits Xand Ythat both have file path/to/name.txt
, and we have an index entry for path/to/name.txt
. Maybe all three hashes match. Maybe two of them match and one doesn't. Maybe all three are different. And, we might also have another/file.txt
that's only in Xor only in Yand is or is not in the index now. Each of these various cases requires its own separate consideration: does Git needto copy the file out from commit to index, or remove it from index, to switch from Xto Y? If so, it also has tocopy the file to the work-tree, or remove it from the work-tree. And if that's the case, the index and work-tree versions had better match at least one of the committed versions; otherwise Git will be clobbering some data.
这就是导致上述所有古怪极端情况的原因。我们有提交X和Y都有文件path/to/name.txt
,我们有一个索引条目path/to/name.txt
。也许所有三个哈希都匹配。也许其中两个匹配,一个不匹配。可能三个人都不一样吧。而且,我们也可能another/file.txt
只有在X 中或仅在Y 中,并且现在是否在索引中。这些不同的情况中的每一种都需要单独考虑:Git是否需要将文件从提交复制到索引,或从索引中删除,以从X切换到Y?如果是这样,它也必须将文件复制到工作树,或从工作树中删除它。如果是这种情况,索引和工作树版本最好至少匹配一个已提交的版本;否则 Git 会破坏一些数据。
(The complete rules for all of this are described in, not the git checkout
documentation as you might expect, but rather the git read-tree
documentation, under the section titled "Two Tree Merge".)
(所有这些的完整规则在标题为“两棵树合并”的部分下的文档中描述,而不是git checkout
您可能期望的git read-tree
文档,而是文档。)
回答by Rob
You have two choices: stash your changes:
您有两个选择: 隐藏您的更改:
git stash
then later to get them back:
然后稍后让他们回来:
git stash apply
or put your changes on a branch so you can get the remote branch and then merge your changes onto it. That's one of the greatest things about git: you can make a branch, commit to it, then fetch other changes on to the branch you were on.
或者将您的更改放在一个分支上,这样您就可以获得远程分支,然后将您的更改合并到它上面。这是 git 最伟大的事情之一:你可以创建一个分支,提交给它,然后在你所在的分支上获取其他更改。
You say it doesn't make any sense, but you are only doing it so you can merge them at will after doing the pull. Obviously your other choice is to commit on your copy of the branch and then do the pull. The presumption is you either don't want to do that (in which case I am puzzled that you don't want a branch) or you are afraid of conflicts.
你说它没有任何意义,但你这样做只是为了在拉动后可以随意合并它们。显然,您的另一个选择是提交您的分支副本,然后执行拉取操作。假设是你要么不想这样做(在这种情况下我很困惑你不想要一个分支)或者你害怕冲突。
回答by Gordolio
If the new branch contains edits that are different from the current branch for that particular changed file, then it will not allow you to switch branches until the change is committed or stashed. If the changed file is the same on both branches (that is, the committed version of that file), then you can switch freely.
如果新分支包含与该特定更改文件的当前分支不同的编辑,则在提交或隐藏更改之前,它将不允许您切换分支。如果更改的文件在两个分支上相同(即该文件的提交版本),则可以自由切换。
Example:
例子:
$ 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
This goes for untracked files as well as tracked files. Here's an example for an untracked file.
这适用于未跟踪的文件以及跟踪的文件。这是未跟踪文件的示例。
Example:
例子:
$ 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
A good example of why you WOULD want to move between branches while making changes would be if you were performing some experiments on master, wanted to commit them, but not to master just yet...
一个很好的例子说明为什么你想要在进行更改时在分支之间移动,如果你在 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"
回答by J.D.1731
The correct answer is
正确答案是
git checkout -m origin/master
git checkout -m origin/master
It merges changes from the origin master branch with your local even uncommitted changes.
它将原始主分支的更改与本地甚至未提交的更改合并。
回答by Kacpero
In case you don't want this changes to be committed at all do
git reset --hard
.
如果您根本不希望提交此更改,请执行
git reset --hard
.
Next you can checkout to wanted branch, but remember that uncommitted changes will be lost.
接下来您可以结帐到想要的分支,但请记住,未提交的更改将丢失。