将先前的提交分成多个提交


1220

如果不创建分支并在新分支上进行大量时髦的工作,是否有可能在将单个提交提交到本地存储库后将其分成几个不同的提交?


36
Pro Git§6.4Git工具-重写历史记录是了解如何执行此操作的一个很好的来源,在“拆分提交”部分中。

2
上面评论中链接的文档非常出色,比下面的答案有更好的解释。
Blaisorblade

2
我建议使用此别名stackoverflow.com/a/19267103/301717。它允许使用git autorebase split COMMIT_ID
–JérômePouiller

没有交互性基准库,最简单的操作是(可能)在要拆分的分支之前先提交一个新分支,然后执行Cherry-pick -n提交,重置,存储,提交文件移动,重新应用存储和提交更改,然后与以前的分支合并,或者选择后续的提交。(然后将以前的分支机构名称更改为当前的名称。)(最好遵循MBO的建议并进行交互式基础调整。)(从2010年下面的答案中复制)
William Pursell,

1
我在较早提交中在重新基准化过程中不小心压缩了两个提交之后遇到了这个问题。我解决它的办法就是检出压扁的承诺,git reset HEAD~git stash,然后git cherry-pick第一个南瓜内提交,然后git stash pop。我摘樱桃的情况是相当具体的在这里,但git stashgit stash pop是对别人很方便。
SOFe

Answers:


1796

git rebase -i 会做的。

首先,从一个干净的工作目录开始:git status不应显示任何挂起的修改,删除或添加。

现在,您必须确定要拆分的提交。

A)分割最近的提交

要拆分最近的提交,请首先:

$ git reset HEAD~

现在,以通常的方式分别提交片段,产生所需数量的提交。

B)进一步拆分提交

这需要重新定基础,即重写历史记录。要找到正确的提交,您有几种选择:

  • 如果是三次提交,那么

    $ git rebase -i HEAD~3
    

    哪里3有多少个提交。

  • 如果它在树中的距离比您想计算的要远,则

    $ git rebase -i 123abcd~
    

    123abcd您要拆分的提交的SHA1 在哪里。

  • 如果您打算合并到母版中的其他分支(例如要素分支)上:

    $ git rebase -i master
    

当您获得rebase编辑屏幕时,找到您想要分解的提交。在该行的开头,pickedite简称)替换。保存缓冲区并退出。Rebase现在将在您要编辑的提交之后停止。然后:

$ git reset HEAD~

以通常的方式分别提交片段,生成所需数量的提交,然后

$ git rebase --continue

2
感谢您的回答。我希望在暂存区中有一些以前提交的文件,因此对我的说明有所不同。还没等我git rebase --continue,其实我不得不git add (files to be added)git commit,则git stash(对于剩余的文件)。之后git rebase --continue,我习惯git checkout stash .了获取剩余文件
Eric Hu

18
manojlds的答案实际上具有git-scm上的文档的链接,该文档还非常清楚地解释了拆分提交的过程。

56
您还需要利用git add -p仅添加文件的部分区域的优势,可能还可以e选择编辑差异以仅提交一些块。git stash如果您希望继续进行一些工作,但又将其从当前提交中删除,则该功能也很有用。
2014年

2
如果要拆分提交重新排序,我想做的是拆分,然后使用另一个git rebase -i HEAD^3命令分别重新排序。这样,如果拆分变坏,您不必撤消太多工作。
大卫·劳埃德

4
@kralyk在HEAD中新提交的文件将在之后保留在磁盘上git reset HEAD~。他们没有迷路。
韦恩·康拉德'18

312

git-rebase手册(分割提交部分)

在交互模式下,您可以使用操作“ edit”标记提交。但是,这并不一定意味着git rebase希望此编辑的结果恰好是一次提交。实际上,您可以撤消提交,也可以添加其他提交。这可用于将提交分为两部分:

  • 使用开始一个交互式的变基git rebase -i <commit>^<commit>您想拆分的提交在哪里。实际上,任何提交范围都可以,只要它包含该提交即可。

  • 用操作“ edit”标记要拆分的提交。

  • 关于编辑该提交,请执行git reset HEAD^。结果是HEAD倒退了一个,索引也随之变化。但是,工作树保持不变。

  • 现在,将更改添加到您希望在第一次提交中拥有的索引。您可以使用git add(可能以交互方式)或git gui(或同时使用两者)来做到这一点。

  • 使用现在合适的提交消息来提交当前索引。

  • 重复最后两个步骤,直到工作树干净为止。

  • 继续进行基础调整git rebase --continue


12
在Windows上,您可以使用~而不是^
Kevin Kuszyk '16

13
忠告:通过这种方法,我丢失了提交消息。
user420667 '16

11
@ user420667是的,当然。reset毕竟,我们正在提交(包括消息)。如果您知道要拆分一个提交但想保留部分/全部消息,则明智的做法是获取该消息的副本。因此,git show提交之前rebase,或者如果您忘记或喜欢此提交:稍后通过进行恢复reflog。直到两周之内将其垃圾收集起来,否则任何东西都不会被“丢失”。
underscore_d

4
~^是不同的东西,甚至在Windows上。您仍然需要插入符号^,因此只需要根据自己的外壳对其进行转义即可。在PowerShell中,它是HEAD`^。使用cmd.exe,您可以将其加倍以像一样进行转义HEAD^^。在大多数(全部?)shell中,您可以使用诸如的引号引起来"HEAD^"
AndrewF

7
您也可以git commit --reuse-message=abcd123。它的简短选择是-C
j0057

41

使用git rebase --interactive到编辑早些时候提交,运行git reset HEAD~,然后再git add -p添加一些,然后再做出承诺,然后添加一些再拍承诺,多次你喜欢。完成后,运行git rebase --continue,您将在堆栈中更早地获得所有拆分提交。

重要提示:请注意,您可以随意进行并进行所有所需的更改,而不必担心丢失旧更改,因为您始终可以git reflog在项目中查找包含所需更改的点,(称之为a8c4ab) ,然后git reset a8c4ab

这是显示其工作方式的一系列命令:

mkdir git-test; cd git-test; git init

现在添加一个文件 A

vi A

添加此行:

one

git commit -am one

然后将此行添加到A:

two

git commit -am two

然后将此行添加到A:

three

git commit -am three

现在文件A看起来像这样:

one
two
three

和我们的git log样子如下(好吧,我用git log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three
2b613bc Rose Perrone 14 seconds ago ---- two
9aac58f Rose Perrone 24 seconds ago ---- one

假设我们要拆分第二个提交two

git rebase --interactive HEAD~2

这将显示一条消息,如下所示:

pick 2b613bc two
pick bfb8e46 three

将第pick一个更改为,e以编辑该提交。

git reset HEAD~

git diff 向我们表明,我们刚刚取消了对第二次提交所做的提交:

diff --git a/A b/A
index 5626abf..814f4a4 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two

让我们分阶段进行更改,然后在file的该行中添加“第三个” A

git add .

这通常是我们在交互式基础库中将要运行的地方git rebase --continue,因为我们通常只想返回到提交堆栈中以编辑较早的提交。但是这次,我们要创建一个新的提交。这样我们就出发了git commit -am 'two and a third'。现在我们编辑文件A并添加行two and two thirds

git add . git commit -am 'two and two thirds' git rebase --continue

我们与自己的提交有冲突three,所以让我们解决它:

我们会改变

one
<<<<<<< HEAD
two and a third
two and two thirds
=======
two
three
>>>>>>> bfb8e46... three

one
two and a third
two and two thirds
three

git add .; git rebase --continue

现在我们git log -p看起来像这样:

commit e59ca35bae8360439823d66d459238779e5b4892
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 13:57:00 2013 -0700

    three

diff --git a/A b/A
index 5aef867..dd8fb63 100644
--- a/A
+++ b/A
@@ -1,3 +1,4 @@
 one
 two and a third
 two and two thirds
+three

commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8e
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 14:07:07 2013 -0700

    two and two thirds

diff --git a/A b/A
index 575010a..5aef867 100644
--- a/A
+++ b/A
@@ -1,2 +1,3 @@
 one
 two and a third
+two and two thirds

commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 14:06:40 2013 -0700

    two and a third

diff --git a/A b/A
index 5626abf..575010a 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two and a third

commit 9aac58f3893488ec643fecab3c85f5a2f481586f
Author: Rose Perrone <roseperrone@fake.com>
Date:   Sun Jul 7 13:56:40 2013 -0700

    one

diff --git a/A b/A
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/A
@@ -0,0 +1 @@
+one

38

先前的答案涵盖了使用git rebase -i来编辑要拆分的提交并将其部分提交。

将文件拆分为不同的提交时,这种方法效果很好,但是如果您希望将各个文件的更改分开,则需要了解更多信息。

到达要拆分的提交,使用rebase -i并将其标记为edit,您有两个选择。

  1. 使用完后,使用分别git reset HEAD~浏览补丁git add -p以选择每次提交中所需的补丁

  2. 编辑工作副本以删除不需要的更改;提交临时状态;然后拉回完整提交以进行下一轮。

如果要分割大型提交,选项2很有用,因为它使您可以检查过渡版本在合并过程中是否正确构建和运行。这进行如下。

使用rebase -iedit提交后,使用

git reset --soft HEAD~

撤消提交,但将提交的文件保留在索引中。您还可以通过省略--soft来进行混合重置,具体取决于您的初次提交将接近最终结果。唯一的区别是,是先进行所有已分阶段的更改,还是先进行所有未分阶段的更改。

现在进入编辑代码。您可以删除更改,删除添加的文件以及执行想要构造的所有提交的所有操作。您还可以构建,运行它,并确认您拥有一致的源集。

满意后,请根据需要暂存/取消暂存文件(我喜欢为此使用git gui),然后通过UI或命令行提交更改

git commit

那是第一次完成。现在,您想将工作副本还原为拆分提交后的状态,以便您可以在下一次提交时进行更多更改。要查找正在编辑的提交的sha1,请使用git status。在状态的前几行中,您将看到当前正在执行的rebase命令,在其中您可以找到原始提交的sha1:

$ git status
interactive rebase in progress; onto be83b41
Last commands done (3 commands done):
   pick 4847406 US135756: add debugging to the file download code
   e 65dfb6a US135756: write data and download from remote
  (see more in file .git/rebase-merge/done)
...

在这种情况下,我正在编辑的提交具有sha1 65dfb6a。知道了这一点,我可以在工作目录中使用git checkout提交和文件位置的形式检出该提交的内容。在这里,我.用作替换整个工作副本的文件位置:

git checkout 65dfb6a .

不要错过最后的点!

这将签出并暂存文件,并按照您正在编辑的提交后的状态进行登台,但相对于您之前所做的提交,因此,您已经提交的所有更改将不属于提交。

您可以立即进行并按原样提交以完成拆分,也可以再次进行,删除提交的某些部分,然后再进行一次临时提交。

如果您想将原始提交消息重用于一个或多个提交,则可以直接从rebase的工作文件中使用它:

git commit --file .git/rebase-merge/message

最后,一旦您完成所有更改,

git rebase --continue

将进行并完成变基操作。


3
谢谢!!!这应该是公认的答案。今天可以为我节省很多时间和痛苦。这是唯一的答案,即最终提交的结果将您带到与正在编辑的提交相同的状态。
Doug Coburn

1
我喜欢您使用原始提交消息的方式。
萨拉曼达'18

使用选项2时,我git checkout *Sha I'm Editing* .总是说Updated 0 paths from *Some Sha That's Not In Git Log*并且不做任何更改。
Noumenon

18

git rebase --interactive可用于将提交拆分为较小的提交。Rebase上Git文档对该过程进行了简要的演练-拆分提交

在交互模式下,您可以使用操作“ edit”标记提交。但是,这并不一定意味着git rebase期望此编辑的结果恰好是一次提交。实际上,您可以撤消提交,也可以添加其他提交。这可用于将提交分为两部分:

  • 使用开始一个交互式的变基git rebase -i <commit>^<commit>您想拆分的提交在哪里。实际上,任何提交范围都可以,只要它包含该提交即可。

  • 用操作“ edit”标记要拆分的提交。

  • 关于编辑该提交,请执行git reset HEAD^。结果是HEAD倒退了一个,索引也随之变化。但是,工作树保持不变。

  • 现在,将更改添加到您希望在第一次提交中拥有的索引。您可以使用git add(可能是交互方式)或git gui(或两者都使用)来做到这一点。

  • 使用现在合适的提交消息来提交当前索引。

  • 重复最后两个步骤,直到工作树干净为止。

  • 继续进行基础调整git rebase --continue

如果您不确定中间版本是否一致(它们可以编译,通过测试套件等),则应该git stash在每次提交,测试和修正必要的修改后,将尚未提交的更改保存起来。 。


在Windows下,请记住^是命令行的转义符:应将其加倍。例如,发出git reset HEAD^^而不是git reset HEAD^
弗雷德里克

@Frédéric:s我从未遇到过。至少在PowerShell中不是这种情况。然后使用^两次重置当前HEAD上方的两次提交。

@Farway,在经典命令行中尝试。PowerShell是另一种野兽,其转义字符是后倾斜。
弗雷德里克

总结一下:"HEAD^"在cmd.exe或PowerShell中,HEAD^^在cmd.exe中,HEAD`^在PowerShell中。了解外壳(以及您的特定外壳)如何工作(即命令如何变成传递到程序的各个部分)很有用,以便您可以在线调整命令以适应特定外壳的正确字符。(不特定于Windows。)
AndrewF

11

现在,在Windows上最新的TortoiseGit中,您可以轻松完成此操作。

打开变基对话框,对其进行配置,然后执行以下步骤。

  • 右键单击要拆分的提交,然后选择“ Edit”(在选择,压缩,删除...中)。
  • 点击“ Start”开始重新设定基准。
  • 一旦到达要拆分的提交,请检查“ Edit/Split”按钮并Amend直接单击“ ”。提交对话框打开。
    编辑/拆分提交
  • 取消选择您要单独提交的文件。
  • 编辑提交消息,然后单击“ commit”。
  • 直到有文件要提交,提交对话框才会一次又一次打开。如果没有其他文件可以提交,它仍然会询问您是否要添加一个文件。

非常有帮助,谢谢TortoiseGit!



8

请注意还有git reset --soft HEAD^。它类似于git reset(默认为--mixed),但保留了索引内容。这样,如果您添加/删除了文件,则它们已经在索引中了。

事实证明,在进行大型提交时非常有用。


3

这是如何在IntelliJ IDEAPyCharmPhpStorm等中拆分一个提交的方法

  1. 在版本控制日志窗口中,选择要拆分的提交,右键单击并选择 Interactively Rebase from Here

  2. 将您要分割的那个标记为edit,单击Start Rebasing

  3. 您应该看到放置了一个黄色标签,这意味着将HEAD设置为该提交。右键单击该提交,选择Undo Commit

  4. 现在,这些提交又回到了暂存区,您可以分别提交它们。提交所有更改之后,旧的提交将变为非活动状态。


2

没有交互性基准库,最简单的操作是(可能)在要拆分的分支之前先提交一个新分支,即执行Cherry-pick -n提交,重置,存储,提交文件移动,重新应用存储和提交更改,然后与以前的分支合并,或者选择后续的提交。(然后将以前的分支名称更改为当前的头。)(最好遵循MBO的建议并进行交互式的基准调整。)


根据目前的SO标准,这应被视为“无答案”;但这对其他人还是有帮助的,因此,如果您不介意,请将其移至原始帖子的评论
YakovL

@YakovL似乎合理。基于最小动作的原则,我不会删除答案,但是如果有人这样做,我不会反对。
William Pursell

这比所有rebase -i建议要容易得多。我认为由于缺少任何格式,因此没有引起足够的重视。既然您现在拥有126k积分,并且可能知道该怎么做,也许您可​​以进行复习。;)
erikbwork


1

如果您有这个:

A - B <- mybranch

您在提交B中提交了一些内容的位置:

/modules/a/file1
/modules/a/file2
/modules/b/file3
/modules/b/file4

但是您想将B拆分为C-D,并得到以下结果:

A - C - D <-mybranch

您可以例如这样分割内容(来自不同目录中不同提交的内容)...

将分支重置为提交之前要拆分的提交:

git checkout mybranch
git reset --hard A

创建第一次提交(C):

git checkout B /modules/a
git add -u
git commit -m "content of /modules/a"

创建第二个提交(D):

git checkout B /modules/b
git add -u
git commit -m "content of /modules/b"

如果提交高于B怎么办?
CoolMind

1

已经超过8年了,但是也许有人会发现它对您有所帮助。没有我我就能做到了rebase -i。这个想法是使git达到您之前的状态git commit

# first rewind back (mind the dot,
# though it can be any valid path,
# for instance if you want to apply only a subset of the commit)
git reset --hard <previous-commit> .

# apply the changes
git checkout <commit-you-want-to-split>

# we're almost there, but the changes are in the index at the moment,
# hence one more step (exactly as git gently suggests):
# (use "git reset HEAD <file>..." to unstage)
git reset

在此之后,您将看到此光泽,Unstaged changes after reset:并且您的存储库处于将要提交所有这些文件的状态。从现在开始,您可以像平时一样轻​​松地再次提交它。希望能帮助到你。


0

必要命令的快速参考,因为我基本上知道该怎么做,但总是忘记正确的语法:

git rebase -i <sha1_before_split>
# mark the targeted commit with 'edit'
git reset HEAD^
git add ...
git commit -m "First part"
git add ...
git commit -m "Second part"
git rebase --continue

感谢Emmanuel Bernard的博客文章

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.