Git:将已暂存的更改移至不同或新分支

9

我目前可以通过以下三个步骤完成:

  1. 使用git reset HEAD <file>...取消暂存
  2. 切换到分支git checkout [-b] <branchname>
  3. 再次暂存文件git add <file>...

是否有更好的方法来移动已暂存的更改?


可能是 将现有的未提交工作移动到 Git 中的新分支 的重复问题。 - Arpit Aggarwal
3个回答

14

Edmundo's answer是正确的,但它需要一些关于为什么它是正确的细节。

这里有两种情况值得注意:

  • 从你现在所在的提交创建一个新分支非常容易:

  • $ git checkout -b newbranch
    ... continue working; git add and/or git commit as usual ...
    

    git checkout -b 步骤只要新分支本身没有问题(有一个有效的名称且不与现有名称重复),就永远不会失败。

  • 移动到其他已存在的分支可能是非常简单的:

    $ git checkout otherbranch
    ... continue working ...
    
    如果失败了,您至少需要进行一次提交。您可以将此提交作为普通提交进行,然后将其复制到新分支并从当前分支中删除;或者您可以使用git stash,它实际上会进行两个提交。只是这两个提交不在任何分支上,这使得git stash apply可以在任何地方重新应用这些提交。(使用git stash pop就像是说git stash apply && git stash drop,即应用然后,如果Git认为这样做没问题-无论是否真的确实很好-丢弃存储。我建议将这两个操作拆分开来,以便您可以检查它是否正常,尽管通常情况下确实正常,最终这通常是相当微小的。)
    这两个提交是为了保存(a)索引/暂存区和(b)工作树中的相应文件(即所有已跟踪文件)。如果您使用git stash save --untrackedgit stash save --all,它实际上会进行三个提交。您可能不想要三个提交形式,因为它更加棘手。
    当您进行更改时,这些更改存储在文件中。这些文件出现在以下两个位置之一或两者都有:
    - 工作树,这是您以普通文件形式拥有文件的地方,因此可以与它们一起工作(因此称为“工作树”); - 暂存区,Git也称之为索引
    索引或暂存区是您将制作的下一个提交的地方。通常情况下,它已经包含了当前提交中的所有文件,以与当前提交中它们的形式相同的形式(顺便说一下,当前提交始终被称为HEAD)。也就是说,您运行:
    git checkout <branch-name-or-commit-hash-or-similar>
    

    当你使用git checkout命令切换分支时,当前工作目录和暂存区会被重置以匹配你切换到的提交(也就是HEAD所指向的提交)。比如使用git checkout master切换到master分支。

    如果此时你没有对工作目录和暂存区进行修改,那么你可以随意使用git checkout命令切换到其他提交,如git checkout develop。Git可以抛弃掉暂存区和工作目录中的版本,因为它们与你之前检出的提交相同。如果Git删除了一个README文件,甚至是你整个的工作区,也没关系,它们仍然存在于另一个提交中,即master分支的tip提交。当你需要它们时,只需再次使用git checkout master命令即可。

    但这不是你在这里关心的情况。在这里,你已经修改了一些文件,甚至可能已经用git add将它们加入了暂存区。但是git add README只是将文件从工作目录复制到暂存区。这意味着暂存区和工作目录中的README版本相互匹配,但它们都不与HEAD(即当前)提交匹配。

    假设HEADdevelop分支的tip提交,而你应该在feature分支上进行操作。你想使用git checkout feature命令切换到feature分支。但是README会发生什么情况呢?你修改了它,甚至可能已经将其添加到了暂存区中。

    实际上,Git很“懒惰”。如果现在运行git checkout feature命令把HEAD提交移动到feature分支的tip提交,Git必须要做一些工作。这可能包括删除当前的README文件,并用feature分支的tip提交中的文件替换它。

    也许,仅仅是也许,在feature的顶级提交中,有与develop的顶级提交相同的README文件。如果是这样,Git就可以偷懒!Git只需要让原来的README保留在索引/暂存区和/或工作树中,不用费心去删除它们。它可以将修改后的README留在它现在所在的任何位置。这正是Git所做的。

    如果Git不能对文件偷懒,它会进行检查。如果您在索引和/或工作树中拥有的文件与您正在尝试移动的HEAD提交中的文件不匹配,则git checkout feature失败。(请参见git read-tree文档及其列表-哎呀!-“两个树合并”的21种可能情况,以了解确切的成功和失败情况。但大多数情况下,您不必关心所有这些:如果成功,那就好了;如果失败,您必须提交或存储)。

    这就是为什么git checkout -b newbranch总是成功的原因:它使用该分支名称创建新分支,该分支名称指向当前(HEAD)提交。然后它从HEAD提交切换到…好吧,本身。没有什么需要切换的。懒惰的步骤适用于每个文件:不必更改任何文件,因此也不会更改任何文件,因此git checkout -b可以工作。

    这就是为什么git checkout -b otherbranch有时会成功的原因:它需要从HEAD提交切换到otherbranch的顶部。如果该提交具有某些不同的文件,Git将不得不删除索引/暂存区和工作树中现在存在的文件,并用来自otherbranch顶部的版本替换它们。只有当索引/暂存区和工作树中现在存在的版本与HEAD提交中的版本匹配时才允许这样做。


2
非常有用的@torek,不过我想确认一件事。如果使用git checkout(带已暂存更改),它会检查你要checkout的修订版中修改的文件是否与HEAD中的完全相同吗?我认为它并不需要是完全相同的内容... 我认为git在检出时会检查可能存在的冲突(这意味着文件可能不同,但只要已暂存的更改可以成功应用,git就会继续进行checkout)。 - eftshift0
2
有点棘手,因为git checkout允许使用-m来进行合并(提交;它也允许在文件上使用-m,但这些工作方式不同)。本质上,git checkout运行git read-tree来执行两种或三种合并。这些规则在git read-tree文档中详细说明。即使进行了一些压缩,对于两个树的“合并”,也有21行规则! - torek

7

仅检查您想把它们移动到哪里就足够了。Git会进行检查,以查看已暂存的更改是否可以在您检出的点上应用而不发生冲突。另一个简单的技巧是将更改隐藏起来,然后检出分支,然后再恢复更改。


7
在这种情况下使用stash非常有帮助。
git stash

git checkout someBranch

git stash pop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接