简体   繁体   English

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

[英]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?也就是说,它在某些情况下有用吗?

Preliminary notes初步说明

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 savegit 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 to git 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.当以基本方式使用时,两者都做同样的事情。

You can stop reading here, if you like!如果你愿意,你可以在这里停止阅读!

If Git won't let you switch, you already have a remedy: use git stash or git commit ;如果Git为你转换,你已经有一个补救措施:使用git stashgit 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:这里的规则一方面很简单,另一方面很复杂/难以解释:

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 .有一些额外困难的branch1 ,例如git add s、 git rm s 等等——假设你在branch1 A git checkout branch2 would have to do this:一个git checkout branch2必须这样做:

  • For every file that is in branch1 and not in branch2 , 1 remove that file.对于每一个文件,该文件branch1不是branch2 ,1删除该文件。
  • For every file that is in branch2 and not in branch1 , create that file (with appropriate contents).对于每一个文件,该文件branch2不是branch1 ,创建该文件(在适当的内容)。
  • For every file that is in both branches, if the version in branch2 is different, update the working tree version.对于两个分支中的每个文件,如果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 ;如果工作树中的版本与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.如果文件现在不存在, branch2它在branch2出现的方式创建文件是“安全的”。 2 It's "unsafe" if it does exist now but has the "wrong" contents. 2如果它现在确实存在但包含“错误”的内容,则它是“不安全的”。
  • 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 ,则用不同版本替换文件的工作树版本是“安全的”。

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. (参见我们究竟由“分支”是什么意思? )这里,我真正的意思是提交到分支名称解析:其路径是一个文件Pbranch1如果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 以来,读取树规则没有改变。


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 :这是两个分支中的文件,在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

At this point, the working tree file inboth matches the one in branch2 , even though we're on branch1 .此时,工作树文件inbothbranch2文件匹配,即使我们在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

Technical notes—only for the insanely curious :-)技术说明 - 仅适用于非常好奇的 :-)

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 addgit 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 位长的字符串)来决定提交XY是否具有相同的文件。 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 .我们有提交XY都有文件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.

相关问题 即使当前分支中有未提交的更改,如何以编程方式编辑另一个分支中的文件? - How to programmatically edit files in another branch even when there are uncommitted changes in the current branch? 将未提交的更改从当前分支移动到与那些更改冲突的另一个分支 - Move uncommitted changes from current branch to another branch that conflicts with those changes Git(无分支)具有未提交的更改 - Git (no branch) with uncommitted changes 用当前分支子目录的未提交内容破坏另一个git分支 - Clobber another git branch with uncommitted contents of current branch subdirectory 将未提交的更改移动到新分支:git:存储与结帐 - Moving uncommitted changes to a new branch: git: stash vs checkout Git:将当前的主节点变成分支并保留未提交的更改 - Git: Turn current master into branch and keep uncommitted changes 如果当前分支有未提交的更改,如何防止git合并 - How to prevent git merge if current branch has uncommitted changes 将分支提交到当前目录作为对工作目录的未提交更改? - Move branch commits to current one as uncommitted changes to working directory? 如何列出仅在git中当前分支中进行的所有未提交的更改 - how to list all uncommitted changes made only in current branch in git 更改分支和本地未提交的更改 - changed branch and local uncommitted changes
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM