[英]Checkout another branch when there are uncommitted changes on the current branch
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.大多数情况下,当我尝试检出另一个现有分支时,如果我在当前分支上有一些未提交的更改,Git 不允许我这样做。 So I'll have to commit or stash those changes first.
所以我必须首先提交或隐藏这些更改。
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?
将更改带到另一个分支对我来说没有任何意义,为什么 git 有时会允许它? That is, is it helpful in some situations?
也就是说,它在某些情况下有用吗?
This answer is an attempt to explain why Git behaves the way it does.这个答案是试图解释为什么Git的行为方式是这样。 It is not a recommendation to engage in any particular workflows.
不建议参与任何特定的工作流程。 (My own preference is to just commit anyway, avoiding
git stash
and not trying to be too tricky, but others like other methods.) (我自己的偏好是无论如何都提交,避免
git stash
并且不要试图太棘手,但其他人喜欢其他方法。)
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!"有时 Git 会说“好吧,你现在在 branch2 上!” Sometimes, Git says "I can't do that, I'd lose some of your changes."
有时,Git 会说“我不能那样做,我会丢失你的一些更改。”
If Git won't let you do it, you have to commit your changes, to save them somewhere permanent.如果 Git不允许你这样做,你必须提交你的更改,将它们永久保存在某个地方。 You may want to use
git stash
to save them;你可能想使用
git stash
来保存它们; 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."请注意,
git stash save
或git stash push
实际上意味着“提交所有更改,但根本不在任何分支上,然后将它们从我现在所在的位置删除。” That makes it possible to switch: you now have no in-progress changes.这使得切换成为可能:您现在没有正在进行的更改。 You can then
git stash apply
them after switching.然后,您可以在切换后
git stash apply
它们。
Sidebar:
git stash save
is the old syntax;边栏:
git stash save
是旧语法;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.git stash push
是在 Git 版本 2.13 中引入的,用于修复git stash
参数的一些问题并允许新选项。 Both do the same thing, when used in the basic ways.当以基本方式使用时,两者都做同样的事情。
If Git won't let you switch, you already have a remedy: use git stash
or git commit
;如果Git为你转换,你已经有一个补救措施:使用
git stash
或git commit
; or, if your changes are trivial to re-create, use git checkout -f
to force it.或者,如果您的更改很容易重新创建,请使用
git checkout -f
强制它。 This answer is all about when Git will let you git checkout branch2
even though you started making some changes.这个答案是所有关于Git的时候就会让你
git checkout branch2
即使你开始做一些改变。 Why does it work sometimes , and not other times?为什么它有时有效,而其他时候无效?
The rule here is simple in one way, and complicated/hard-to-explain in another:这里的规则一方面很简单,另一方面很复杂/难以解释:
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
.有一些额外困难的
branch1
,例如git add
s、 git rm
s 等等——假设你在branch1
。 A git checkout branch2
would have to do this:一个
git checkout branch2
必须这样做:
branch1
and not in branch2
, 1 remove that file.branch1
而不是在branch2
,1删除该文件。branch2
and not in branch1
, create that file (with appropriate contents).branch2
而不是在branch1
,创建该文件(在适当的内容)。branch2
is different, update the working tree version.branch2
的版本不同,则更新工作树版本。 Each of these steps could clobber something in your work-tree:这些步骤中的每一个都可能会破坏您的工作树中的某些内容:
branch1
;branch1
提交的版本相同,则删除文件是“安全的”; it's "unsafe" if you've made changes.branch2
is "safe" if it does not exist now.branch2
它在branch2
出现的方式创建文件是“安全的”。 2 It's "unsafe" if it does exist now but has the "wrong" contents. branch1
.branch1
,则用不同版本替换文件的工作树版本是“安全的”。 Creating a new branch ( git checkout -b newbranch
) is always considered "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.创建新分支 (
git checkout -b newbranch
)始终被认为是“安全的”:作为此过程的一部分,不会在工作树中添加、删除或更改任何文件,并且索引/暂存区也未受影响。 (Caveat: it's safe when creating a new branch without changing the new branch's starting-point; but if you add another argument, eg, 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 different-start-point
,这可能需要改变一些东西,移动到different-start-point
。然后 Git 将照常应用结帐安全规则。)
1 This requires that we define what it means for a file to be in a branch, which in turn requires defining the word branch properly. 1这要求我们定义文件在分支中的含义,这反过来又需要正确定义分支这个词。 (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
is in branch1
if git rev-parse branch1: P
produces a hash. (参见我们究竟由“分支”是什么意思? )这里,我真正的意思是提交到分支名称解析:其路径是一个文件
P
是在branch1
如果git rev-parse branch1: P
产生哈希。 That file is not in branch1
if you get an error message instead.如果您收到错误消息,则该文件不在
branch1
。 The existence of path P
in your index or work-tree is not relevant when answering this particular question.在回答此特定问题时,索引或工作树中路径
P
的存在与否无关。 Thus, the secret here is to examine the result of git rev-parse
on each branch-name : path
.因此,这里的秘密是检查
git rev-parse
在每个branch-name : path
。 This either fails because the file is "in" at most one branch, or gives us two hash IDs.这要么因为文件“在”最多一个分支而失败,要么给了我们两个哈希 ID。 If the two hash IDs are the same , the file is the same in both branches.
如果两个哈希 ID相同,则两个分支中的文件相同。 No changing is required.
无需更改。 If the hash IDs differ, the file is different in the two branches, and must be changed to switch branches.
如果hash ID不同,说明两个分支的文件不同,必须改成切换分支。
The key notion here is that files in commits are frozen forever.这里的关键概念是提交中的文件永远冻结。 Files you will edit are obviously not 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't in the commit you're going to switch away from and are in the commit you're going to switch to.
不幸的是,我们-或Git的,还必须处理不在提交你打算从切换出来,并在提交你要切换到文件。 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.
这导致了剩余的复杂性,因为文件也可以存在于索引和/或工作树中,而不必存在我们正在处理的这两个特定的冻结提交。
2 It 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. 2如果它已经存在“正确的内容”,那么它可能被认为是“安全的”,这样 Git 毕竟不必创建它。 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.
我记得至少有一些 Git 版本允许这样做,但刚刚的测试表明它在 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.
相同的参数适用于修改后的文件,该文件恰好被修改以匹配 to-be-switch-to 分支。 Again, 1.8.5.4 just says "would be overwritten", though.
同样,1.8.5.4 只是说“将被覆盖”。 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.
另请参阅技术说明的结尾:我的记忆可能有问题,因为我认为自从我在 1.5.something 版本中第一次开始使用 Git 以来,读取树规则没有改变。
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
.此时,工作树文件
inboth
与branch2
文件匹配,即使我们在branch1
。 This change is not staged for commit, which is what git status --short
shows here:此更改不会为提交而暂存,这是
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
工作树副本,我们已经知道它也与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, is gone.
这种变化是不是走了,但我已经上演它的事实,已经一去不复返了。
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.这里的两个
M
表示:staged 文件不同于HEAD
文件,并且,working-tree 文件不同于 staged 文件。 The working-tree version does match the branch1
(aka HEAD
) version:工作树版本与
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
副本匹配,但暂存文件不匹配,因此git checkout
会丢失该副本,并且拒绝git checkout
。
The underlying implementation mechanism for all of this is Git's index .所有这一切的底层实现机制是 Git 的index 。 The index, also called the "staging area", is where you build the next commit: it starts out matching the current commit, ie, whatever you have checked-out now, and then each time you
git add
a file, you replace the index version with whatever you have in your work-tree.索引,也称为“暂存区”,是您构建下一次提交的地方:它开始匹配当前提交,即,无论您现在已签出什么,然后每次
git add
文件时,您都会替换索引版本与您的工作树中的任何内容。
Remember, the work-tree is 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.
在这里,它们有它们的正常形式,而不是像在提交和索引中那样的一些特殊的仅对 Git 有用的形式。 So you extract a file from a commit, through the index, and then on into the work-tree.
所以你从提交中提取一个文件,通过索引,然后进入工作树。 After changing it, you
git add
it to the index.更改后,您
git add
其git add
到索引中。 So there are in fact three places for each file: the current commit, the index, and the work-tree.所以实际上每个文件都有三个位置:当前提交、索引和工作树。
When you run git checkout branch2
, what Git does underneath the covers is to compare the tip commit of branch2
to whatever is in both the current commit and the index now.当您运行
git checkout branch2
,Git 在git checkout branch2
所做的是将branch2
的提示提交与当前提交和索引中的任何内容进行比较。 Any file that matches what's there now, Git can leave alone.任何与现有文件匹配的文件,Git 都可以不理会。 It's all untouched.
这一切都没有受到影响。 Any file that's the same in both commits , Git can also leave alone—and these are the ones that let you switch branches.
任何在两次提交中都相同的文件,Git 也可以单独保留——这些是让你切换分支的文件。
Much of Git, including commit-switching, is relatively fast because of this index.由于这个索引,Git 的大部分,包括提交切换,都相对较快。 What's actually in the index is not each file itself, but rather each file's hash .
索引中的实际内容不是每个文件本身,而是每个文件的hash 。 The copy of the file itself is stored as what Git calls a blob object , in the repository.
文件本身的副本作为 Git 称为blob object的内容存储在存储库中。 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.
这也类似于文件在提交中的存储方式:提交实际上并不包含文件,它们只是将 Git 引导到每个文件的哈希 ID。 So Git can compare hash IDs—currently 160-bit-long strings—to decide if commits X and Y have the same file or not.
因此,Git 可以比较哈希 ID(目前为 160 位长的字符串)来决定提交X和Y是否具有相同的文件。 It can then compare those hash IDs to the hash ID in the index, too.
然后它也可以将这些哈希 ID 与索引中的哈希 ID 进行比较。
This is what leads to all the oddball corner cases above.这就是导致上述所有古怪极端情况的原因。 We have commits X and Y that both have file
path/to/name.txt
, and we have an index entry for path/to/name.txt
.我们有提交X和Y都有文件
path/to/name.txt
,我们有一个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 X or only in Y and is or is not in the index now.而且,我们可能还有
another/file.txt
,它只在X 中或仅在Y中,现在是否在索引中。 Each of these various cases requires its own separate consideration: does Git need to copy the file out from commit to index, or remove it from index, to switch from X to Y ?这些不同的情况中的每一种都需要单独考虑:Git是否需要将文件从提交复制到索引,或从索引中删除,以从X切换到Y ? If so, it also has to copy 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.
否则 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 checkout
文档,而是git read-tree
文档,在标题为“两棵树合并”的部分下。)
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.
假设是你要么不想这样做(在这种情况下我很困惑你不想要一个分支)或者你害怕冲突。
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"
The correct answer is正确答案是
git checkout -m origin/master
It merges changes from the origin master branch with your local even uncommitted changes.它将原始主分支的更改与本地甚至未提交的更改合并。
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.接下来您可以结帐到想要的分支,但请记住,未提交的更改将丢失。
I have faced the same question recently.我最近遇到了同样的问题。 What I understand is, if the branch you are checking in has a file which you modified and it happens to be also modified and committed by that branch.
我的理解是,如果您签入的分支有一个您修改过的文件,并且它恰好也被该分支修改和提交。 Then git will stop you from switching to the branch to keep your change safe before you commit or stash.
然后 git 将阻止您切换到分支以在您提交或存储之前确保您的更改安全。
I've been struggling for a while with this thing too and I would like to give my two cents to the answer.我也一直在为这件事苦苦挣扎,我想为答案付出两分钱。 First thing first, my understanding of the matter came from here: https://medium.com/swimm/a-visualized-intro-to-git-internals-objects-and-branches-68df85864037
首先,我对此事的理解来自这里: https : //medium.com/swimm/a-visualized-intro-to-git-internals-objects-and-branches-68df85864037
The question was:问题是:
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?
将更改带到另一个分支对我来说没有任何意义,为什么 git 有时会允许它? That is, is it helpful in some situations?
也就是说,它在某些情况下有用吗?
When you create a branch out of any other branch, you are only creating a pointer to the same commit, so unless you have committed any change you had begun working on, you will be pointing at the same commit, and thus git will let you change branches that way.当你从任何其他分支创建一个分支时,你只是在创建一个指向同一个提交的指针,所以除非你提交了你开始工作的任何更改,否则你将指向同一个提交,因此 git 会让你以这种方式改变分支。 Only when you commit any change to the new branch is that commits begin to differ between branches and git will complain when trying to checkout those branches if there were any uncommitted changes.
只有当您向新分支提交任何更改时,分支之间的提交才开始不同,如果有任何未提交的更改,git 会在尝试检出这些分支时抱怨。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.