将先前的提交分解为多个提交

Posted

技术标签:

【中文标题】将先前的提交分解为多个提交【英文标题】:Break a previous commit into multiple commits 【发布时间】:2011-09-07 05:05:03 【问题描述】:

如果不创建分支并在新分支上做一堆时髦的工作,是否可以在将单个提交提交到本地存储库后将其分解为几个不同的提交?

【问题讨论】:

一个很好的学习方法是Pro Git §6.4 Git Tools - Rewriting History,在“Splitting a Commit”部分。 上述评论中链接的文档非常好,比下面的答案解释得更好。 我建议使用这个别名***.com/a/19267103/301717。它允许使用git autorebase split COMMIT_ID 拆分提交 没有交互式变基的最简单的事情是(可能)在要拆分的分支之前从提交开始创建一个新分支,cherry-pick -n 提交,重置,存储,提交文件移动,重新应用存储并提交更改,然后与前一个分支合并或挑选随后的提交。 (然后将以前的分支名称切换到当前的头。)(最好遵循 MBO 的建议并进行交互式 rebase。)(从下面的 2010 答案复制) 我在之前的提交中意外压缩了两个提交后遇到了这个问题。我修复它的方法是检查压缩的提交,git reset HEAD~git stash,然后是 git cherry-pick 壁球中的第一个提交,然后是 git stash pop。我的cherry-pick案例在这里非常具体,但git stashgit stash pop对其他人来说非常方便。 【参考方案1】:

git rebase -i 会做到的。

首先,从一个干净的工作目录开始:git status 应该没有待处理的修改、删除或添加。

现在,您必须决定要拆分哪些提交。

A) 拆分最近的提交

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

$ git reset HEAD~

现在以通常的方式单独提交各个部分,根据需要生成尽可能多的提交。

B) 将提交往后拆分

这需要rebaseing,也就是重写历史。要指定正确的提交,您有多种选择:

如果是三个commit返回,那么

  $ git rebase -i HEAD~3

3 是返回的提交次数。

如果它在树中的位置比你想数的更远,那么

  $ git rebase -i 123abcd~

123abcd 是您要拆分的提交的 SHA1。

如果您在要合并到 master 的不同分支(例如,功能分支)上:

  $ git rebase -i master

当您看到变基编辑屏幕时,找到您想要拆分的提交。在该行的开头,将pick 替换为edit(简称e)。保存缓冲区并退出。 Rebase 现在将在您要编辑的提交之后停止。那么:

$ git reset HEAD~

以通常的方式单独提交片段,根据需要生成尽可能多的提交。

终于

$ git rebase --continue

【讨论】:

感谢您的回答。我想在暂存区有一些以前提交的文件,所以对我的说明有点不同。在我可以git rebase --continue 之前,我实际上必须git add (files to be added)git commit,然后是git stash(对于剩余的文件)。在git rebase --continue之后,我使用git checkout stash .获取剩余文件 manojlds's answer 其实this link 到git-scm 上的文档,也很清楚的解释了拆分提交的过程。 您还需要利用git add -p 仅添加文件的部分部分,可能使用e 选项来编辑差异以仅提交一些大块。 git stash 如果您想推进一些工作但将其从当前提交中删除,也很有用。 如果你想拆分 and 重新排序提交,我喜欢做的是拆分 first 然后使用另一个 git rebase -i HEAD^3 命令单独重新排序。这样,如果拆分失败,您不必撤消太多工作。 @kralyk HEAD 中新提交的文件将在git reset HEAD~ 之后留在磁盘上。他们没有丢失。【参考方案2】:

来自git-rebase 手册(SPLITTING COMMITS 部分)

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

使用git rebase -i <commit>^ 启动交互式变基,其中<commit> 是您要拆分的提交。事实上,任何提交范围都可以,只要它包含该提交。

使用“编辑”操作标记要拆分的提交。

在编辑该提交时,执行git reset HEAD^。效果是 HEAD 倒带一,索引也随之而来。但是,工作树保持不变。

现在将更改添加到您希望在第一次提交中拥有的索引。您可以使用git add(可能以交互方式)或git gui(或两者)来执行此操作。

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

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

使用git rebase --continue 继续变基。

【讨论】:

在 Windows 上,您可以使用 ~ 而不是 ^ 警告:使用这种方法我丢失了提交消息。 @user420667 是的,当然。毕竟,我们是resetting 提交 - 包括消息。谨慎的做法是,如果您知道要拆分提交但想要保留其部分/全部消息,请获取该消息的副本。所以,git showrebaseing 之前提交,或者如果您忘记或喜欢这个:稍后通过reflog 回到它。在 2 周内或其他时间将垃圾收集起来之前,它们实际上都不会“丢失”。 ~^ 是不同的东西,即使在 Windows 上也是如此。你仍然需要插入符号^,所以你只需要根据你的shell 转义它。在 PowerShell 中,它是 HEAD`^。使用 cmd.exe,您可以将其翻倍以像 HEAD^^ 一样进行转义。在大多数(全部?)shell 中,您可以使用 "HEAD^" 之类的引号括起来。 你也可以git commit --reuse-message=abcd123。它的简短选项是-C【参考方案3】:

以前的答案已经涵盖了使用git rebase -i 来编辑要拆分的提交,并分部分提交。

这在将文件拆分为不同的提交时效果很好,但如果您想拆分对单个文件的更改,您需要了解更多信息。

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

    使用git reset HEAD~ 后,使用git add -p 逐个检查补丁以在每次提交中选择您想要的补丁

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

如果您要拆分大型提交,选项 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

将进行并完成rebase操作。

【讨论】:

谢谢!!!这应该是公认的答案。今天会为我节省很多时间和痛苦。这是最终提交的结果将您带到与正在编辑的提交相同的状态的唯一答案。 我喜欢你使用原始提交消息的方式。 使用选项 2,当我做 git checkout *Sha I'm Editing* . 时,它总是说 Updated 0 paths from *Some Sha That's Not In Git Log* 并且没有改变。【参考方案4】:

使用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

让我们暂存该更改,并在文件 A 中的该行中添加“和第三个”。

git add .

这通常是在交互式 rebase 期间我们将运行 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

【讨论】:

【参考方案5】:

git rebase --interactive 可用于将提交拆分为较小的提交。 Git docs on rebase have a concise walkthrough of the process - Splitting Commits:

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

使用git rebase -i &lt;commit&gt;^ 启动交互式变基,其中&lt;commit&gt; 是您要拆分的提交。事实上,任何提交范围都可以,只要它包含该提交。

使用“编辑”操作标记要拆分的提交。

在编辑该提交时,执行git reset HEAD^。效果是 HEAD 倒带一,索引也随之而来。但是,工作树保持不变。

现在将更改添加到您希望在第一次提交中拥有的索引。您可以使用git add(可能以交互方式)或 git gui(或两者)来执行此操作。

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

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

使用git rebase --continue 继续变基。

如果您不确定中间版本是否一致(它们可以编译、通过测试套件等),您应该使用 git stash 在每次提交、测试和修改后隐藏尚未提交的更改如果需要修复,则提交。

【讨论】:

在 Windows 下,请记住 ^ 是命令行的转义字符:它应该加倍。例如,发出git reset HEAD^^ 而不是git reset HEAD^ @Frédéric:我从来没有遇到过这个。至少在 PowerShell 中并非如此。然后使用 ^ 两次重置当前 HEAD 上方的两个提交。 @Farway,在经典命令行中尝试一下。 PowerShell 是另一种野兽,它的转义字符是 backtilt。 总结一下:"HEAD^" 在 cmd.exe 或 PowerShell 中,HEAD^^ 在 cmd.exe 中,HEAD`^ 在 PowerShell 中。了解 shell - 以及您的特定 shell - 如何工作(即命令如何变成传递给程序的单独部分)非常有用,这样您就可以在线将命令调整为特定 shell 的正确字符。 (不特定于 Windows。)【参考方案6】:

现在在 Windows 上最新的 TortoiseGit 中,您可以非常轻松地做到这一点。

打开变基对话框configure it,然后执行以下步骤。

右键单击要拆分的提交,然后选择“Edit”(在pick、squash、delete 中...)。 单击“Start”开始变基。 一旦到达要拆分的提交,检查“Edit/Split”按钮并 直接点击“Amend”。提交对话框打开。 取消选择要单独提交的文件。 编辑提交信息,然后点击“commit”。 在有文件要提交之前,提交对话框会一次又一次地打开。当没有更多要提交的文件时,它仍然会询问您是否要再添加一个提交。

非常有帮助,感谢 TortoiseGit !

【讨论】:

【参考方案7】:

您可以进行交互式 rebase git rebase -i。手册页正是您想要的:

http://git-scm.com/docs/git-rebase#_splitting_commits

【讨论】:

提供更多关于如何解决问题的背景信息而不是仅仅提供 RTFM 会更有帮助。【参考方案8】:

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

在大量提交的情况下非常有用。

【讨论】:

【参考方案9】:

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

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's blog post。

【讨论】:

【参考方案10】:

如果没有交互式变基,最简单的事情是(可能)在要拆分的分支之前的提交开始创建一个新分支,cherry-pick -n 提交,重置,存储,提交文件移动,重新应用存储并提交更改,然后与前一个分支合并或挑选随后的提交。 (然后将以前的分支名称切换到当前的 head。)(最好听从 MBO 的建议并进行交互式 rebase。)

【讨论】:

根据如今的 SO 标准,这应该被认定为非答案;但这仍然对其他人有帮助,所以如果您不介意,请将其移至原帖的 cmets @YakovL 似乎很合理。以最小的行动为原则,我不会删除答案,但如果有人这样做,我不会反对。 这比所有rebase -i 建议要容易得多。不过,我认为由于缺乏任何格式,这并没有引起太多关注。也许你可能会复习它,因为你有 126k 点并且可能知道如何 SO。 ;)【参考方案11】:

这里是如何在 IntelliJ IDEAPyCharmPhpStorm 等中拆分一个提交

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

    将要拆分的标记为edit,点击Start Rebasing

    你应该看到一个黄色标签被放置,这意味着 HEAD 已设置 到那个提交。右键单击该提交,选择 Undo Commit

    现在这些提交返回到暂存区,然后您可以提交它们 分别地。提交所有更改后,旧提交 变为非活动状态。

【讨论】:

【参考方案12】:

已经 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: 并且您的 repo 处于您即将提交所有这些文件的状态。从现在开始,您可以像往常一样轻松地再次提交它。希望对您有所帮助。

【讨论】:

【参考方案13】:

我认为这是我使用git rebase -i 的最佳方式。我创建了一个视频来展示拆分提交的步骤:https://www.youtube.com/watch?v=3EzOz7e1ADI

【讨论】:

【参考方案14】:

如果你有这个:

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 以上的提交怎么办?【参考方案15】:

如果您只想从现有提交中提取某些内容并保留原始提交,则可以使用

git reset --patch HEAD^

而不是git reset HEAD^。这个命令允许你只重置你需要的块。

在您选择要重置的块后,您将拥有暂存的块,这些块将在您完成后重置先前提交中的更改

git commit --amend --no-edit

以及可以添加到单独提交中的未暂存块

git add .
git commit -m "new commit"

题外话:

在 mercurial 中,他们有 hg split - 我想在 git 中看到 hg absorb 之后的第二个功能。

【讨论】:

【参考方案16】:

如果您的更改主要是添加新内容,则此方法最有用。

有时您不想丢失与正在拆分的提交相关的提交消息。如果您提交了一些想要拆分的更改,您可以:

    编辑要从文件中删除的更改(即删除行或适当更改文件以适应第一次提交)。您可以结合使用您选择的编辑器和git checkout -p HEAD^ -- path/to/file 将一些更改还原到当前树中。 将此编辑作为一个新提交提交,类似于git add . ; git commit -m 'removal of things that should be changed later',因此您将在历史记录中拥有原始提交,并且您还将拥有另一个包含您所做更改的提交,因此当前 HEAD 上的文件看起来像您想要的它们在拆分后的第一次提交中。
000aaa Original commit
000bbb removal of things that should be changed later
    使用git revert HEAD 恢复编辑,这将创建恢复提交。文件看起来就像它们在原始提交时所做的那样,您的历史记录现在看起来像
000aaa Original commit
000bbb removal of things that should be changed later
000ccc Revert "removal of things that should be changed later" (assuming you didn't edit commit message immediately)
    现在,您可以使用git rebase -i 将前两个提交压缩/修复为一个,如果您之前没有向它提供有意义的提交消息,则可以选择修改恢复提交。你应该留下
000ddd Original commit, but without some content that is changed later
000eee Things that should be changed later

【讨论】:

【参考方案17】:

大多数现有答案建议使用交互式变基 - git rebase -i 或类似的。对于像我这样对“互动”方式有恐惧症并喜欢在下楼梯时抓住扶手的人,这里有一个替代方案。

假设您的历史看起来像 … —&gt; P –&gt; Q –&gt; R –&gt; … –&gt; Z = mybranch,并且您想将 P –&gt; Q 拆分为两个提交,最终得到 P –&gt; Q1 –&gt; Q' –&gt; R' –&gt; … Z' = mybranch,其中 Q'R' 等处的代码状态与 @ 相同987654327@、R

开始之前,如果您偏执,请备份mybranch,以免丢失历史记录:

git checkout mybranch
git checkout -b mybranch-backup

首先,检查P(您要拆分之前的提交),然后创建一个新分支来使用

git checkout P
git checkout -b mybranch-splitting

现在,从 Q 签出您想要的任何文件,并根据需要进行编辑以创建新的中间提交:

git checkout Q file1.txt file2.txt
[…edit, stage commit with “git add”, etc…]
git commit -m "Refactored the widgets"

记下此提交的哈希,如Q1。现在检查Q 的完整状态,在Q1 上分离HEAD,提交它(创建Q'),并将工作分支拉到它:

git checkout Q
git reset --soft Q1
git commit -m "Added unit tests for widgets"
git branch -f mybranch-splitting

你现在在mybranch-splittingQ' 上,它应该具有与Q 完全相同的代码状态。现在将原始分支(从QZ)重新设置为:

git rebase --onto HEAD Q mybranch

现在mybranch 应该看起来像… P -&gt; Q1 –&gt; Q' –&gt; R' –&gt; … Z',如您所愿。因此,在检查一切正常后,您可以删除您的工作和备份分支,并(如果合适)将重写的 mybranch 推送到上游。如果它已被推送,则需要强制推送,并且所有关于强制推送的常见警告都适用。

git push --force mybranch
git branch -d mybranch-splitting mybranch-backup

【讨论】:

备份分支在变基后很有用。由于您只是拆分提交,因此您希望确保您的树保持不变。所以你做git diff mybranch-backup 以确保你没有不小心忘记一些东西。如果它显示差异 - 你可以git reset --hard mybranch-backup 重新开始。 git checkout Q file1.txt file2.txt 也是 IMO 比 reset HEAD^commit -p 更脆弱的方法。【参考方案18】:

我用变基做到了这一点。编辑提交对我不起作用,因为它已经选择了提交文件并允许您对其进行修改,但我想将所有文件添加为未跟踪的文件,这样我就可以选择其中的一些。步骤是:

    git rebase -i HEAD~5(我想拆分我历史上的第 5 次提交) 复制目标提交 ID(稍后您将需要它) 用d 标记提交以删除它;在提交之后添加一个b 行以停止变基过程并稍后继续。即使这是最后一次提交,这也为您提供了一些空间来仅git rebase --abort 并重置所有内容以防出现问题。 当变基到达断点时,使用git cherry-pick -n &lt;COMMIT ID&gt;。这将选择提交更改而不选择提交本身,使它们处于未跟踪状态。 在第一次提交中添加您想要的文件(或使用git add -i 和补丁,以便您可以添加特定的块) 提交您的更改。 决定如何处理剩余的更改。就我而言,我希望它们在历史的末尾并且没有冲突,所以我做了git stash,但你也可以直接提交它们。 git rebase --continue 选择其他更改

作为交互式变基的忠实拥护者,这是我能想到的最简单、最直接的一组步骤。我希望这可以帮助任何面临这个问题的人!

【讨论】:

以上是关于将先前的提交分解为多个提交的主要内容,如果未能解决你的问题,请参考以下文章

使用 MRJob 将作业提交到 EMR 集群

问题 G: 最优分解问题

SmartCommit让复合提交不在是难题

将 Vuex 操作分解为多个文件

Git更改提交

9718 整数因子分解(必做) 分治法