在当前分支上有未提交的更改时签出另一个分支 [英] Checkout another branch when there are uncommitted changes on the current branch

查看:255
本文介绍了在当前分支上有未提交的更改时签出另一个分支的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大多数时候,当我尝试检出另一个现有分支时,Git不允许我在当前分支上有一些未提交的更改。所以我必须首先提交或存储这些更改。然而,偶尔Git确实允许我在不提交或隐藏这些更改的情况下签出另一个分支,并且它会将这些更改带到我签出的分支。



这里的规则是什么?这些变化是上演还是未上演?将更改传递到另一个分支对我来说没有任何意义,为什么git有时允许它?也就是说,它在某些情况下有帮助吗?

解决方案

初步记录



这里的观察是,在开始在 branch1 中工作之后(遗忘或不知道切换到另一个分支 branch2 第一个),你运行:

  git checkout branch2 

有时Git会说:好的,你现在在branch2上!有时候,Git会说:我做不到,我会失去一些改变。



如果Git 不会让你这样做,你必须承诺你的改变,将它们保存在永久的地方。 您可能需要使用 git stash 拯救他们;这是它的设计目的之一。注意 git stash保存其实意味着提交所有更改,但根本没有分支,然后把它们从我现在的地方移开。这使得切换成为可能:您现在没有正在进行的更改。你可以在切换后 git stash apply 它们。



如果你喜欢,你可以在这里停止阅读! h2>

如果Git 不允许您切换,您已经有了一个补救办法:使用 git stash git commit ;或者,如果您的更改很容易重新创建,请使用 git checkout -f 强制执行。这个答案是关于什么时候即使您开始进行一些更改,Git也会让您 git checkout branch2 。为什么它有时 ,而不是其他次?



这个规则很简单,复杂/难以在另一个中解释:

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



即是 - 请注意,这仍然是简化的;还有一些额外的难题,例如 git add s, git rm s等等 - 假设你在 BRANCH1 git checkout branch2 必须这样做:


  • 对于每个< branch1 不在 branch2 ,则删除该文件。

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



在工作树中:


  • 如果工作树中的版本与提交版本相同,删除文件是安全的在 branch1 中;如果您进行了更改,则为不安全。

  • 如果以 branch2 出现的方式创建文件是安全现在不存在 1 如果它现在存在但是有错误的内容,则它是不安全的。

  • 当然,替换工作树如果工作树版本已被提交至 branch1 ,则具有不同版本的文件版本是安全的。



创建一个新的分支( git checkout -b newbranch )始终被认为是安全的:没有文件将作为此过程的一部分在工作树中添加,删除或更改,并且索引/登台区域也不变。 (注意:创建新分支时不会改变新分支的起点是安全的;但是如果添加另一个参数,例如 git checkout -b newbranch不同开始点,这可能不得不改变,移动到不同的起点,然后Git会像往常一样应用结帐安全规则。)



这些变化是暂时的还是暂时的?是否在某些方面。特别是,您可以进行更改,然后解除修改工作树文件。这是两个分支中的文件,它们在 branch1 branch2 中不同:

  $ git show branch1:inboth 
这个文件在两个分支中
$ git show branch2:inboth
这个文件在两个分支中
但是它在branch2中有更多的东西现在
$ git checkout branch1
转换到分支'branch1'
$ echo'但它在branch2中有更多东西现在'>> inboth

此时,工作树文件 inboth branch1 上,c>与 branch2 中的一个匹配。这个改变没有提交,这是 git status --short 在这里显示的内容:

  $ git status --short 
M inboth

那么-M表示已修改但未分阶段(或者更确切地说,工作树复制与分段/索引副本不同)。

  $ git checkout branch2 
错误:您的本地更改...

好的,现在让我们阶段工作树副本,我们已经知道也与 branch2 中的副本相匹配。

  $ git add inboth 
$ git status --short
M inboth
$ git checkout branch2
转换到分支'branch2'

这里的分段和工作副本都与 branch2 中的内容匹配,所以结帐是被允许的。



让我们再试一下:

  $ git checkout branch1 
转换到分支'branch1'
$ cat inboth
thi s文件在两个分支中

现在我所做的更改从暂存区丢失(因为checkout写入通过暂存区)。这是一个角落的情况。改变并没有消失,但是我已经上演了它的事实, 没有了。

让我们演示文件的第三个变体,不同的从任一分支复制,然后将工作副本设置为与当前分支版本匹配:

  $ echo'staged version with all from all '> inboth 
$ git add inboth
$ git show branch1:inboth> inboth
$ git status --short
MM inboth

两个 M 这里的意思是:staged文件不同于 HEAD 文件,,工作树文件不同来自上演档案。工作树版本符合分支1 (aka HEAD )版本:

  $ git diff HEAD 
$

但是 git checkout 不允许结账:

  $ git checkout branch2 
错误:您的本地更改...

让我们设置 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 @@
此文件位于两个分支
+中但它现在有更多的东西在branch2中
$ git diff branch2 - inboth
$ git checkout branch2
错误:您的本地更改...

即使当前工作副本与 branch2 中的工作副本相匹配,暂存的文件也不会,所以 git checkout 会丢失该副本,并且 git checkout 会被拒绝。






1 如果它已经存在于正确的内容中,它可能被认为是安全分类,所以Git不会毕竟必须创造它。我记得至少有一些版本的Git允许这样做,但是刚才的测试表明它在Git 1.8.5.4中被认为是不安全的。相同的参数将适用于修改后的文件,该文件恰好被修改为匹配待切换分支。不过,1.8.5.4只是说会被覆盖。请参阅技术说明的结尾:我的内存可能有问题,因为自从我开始在1.5版本以上使用Git以来,我认为读取树规则并未发生变化。






技术说明 - 仅针对疯狂好奇: - )



所有这些的底层实现机制是Git的索引。该索引也称为分段区域,是您建立 next 提交的地方:它开始匹配当前提交,即无论您现在签出什么,然后每次 git add 一个文件,您可以用您的工作树中的任何内容替换索引版本。



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



运行 git checkout branch2 ,Git在封面下面做的是比较 branch2 提示提交与当前现在提交和索引。任何与现在相匹配的文件,Git都可以独自离开。这一切都未变。任何在提交中都是相同的文件,Git也可以 ,这些都可以让您切换分支。



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



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



(所有这些的完整规则在 git checkout git read-tree 文档,在标题为双树合并的部分下。)


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.

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.

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?

解决方案

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:

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

If Git won't let 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 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.

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; or, if your changes are trivial to re-create, use git checkout -f to force it. This answer is all about when Git will let you git checkout branch2 even though you started making some changes. 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 adds, git rms and such—suppose you are on branch1. A git checkout branch2 would have to do this:

  • For every file that is in branch1 and not in branch2, remove that file.
  • For every file that is in branch2 and not in branch1, 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.

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.1 It'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.

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. (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.)

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:

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

$ 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).

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

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

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 Ms 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:

$ git diff HEAD
$

But git checkout won't allow the checkout:

$ git checkout branch2
error: Your local changes ...

Let's set the branch2 version as the working version:

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


1It 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.


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 next commit: 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 replace the index version with whatever you have in your work-tree.

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. 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. 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. 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 also leave alone—and these are the ones that let you switch branches.

Much of Git, including commit-switching, is relatively fast because of this 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-byte-long strings—to decide if commits X and Y have the same file or not. It can then compare those hash IDs to the hash ID in the index, too.

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. 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. 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? 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.

(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".)

这篇关于在当前分支上有未提交的更改时签出另一个分支的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆