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

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

442784 次浏览

来自git-rebase手册(拆分提交部分)

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

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

  • 标记您要拆分的提交操作“编辑”。

  • 当涉及到编辑该提交时,执行git reset HEAD^。效果是HEAD重绕1,索引也跟着重绕。但是,工作树保持不变。

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

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

  • 重复最后两个步骤,直到你的工作树是干净的。

  • 使用git rebase --continue继续rebase。

在没有交互式rebase的情况下,最简单的做法是(可能)在您要拆分的分支之前创建一个从提交开始的新分支,选择-n提交、重置、存储、提交文件移动、重新应用存储并提交更改,然后与前一个分支合并或选择随后的提交。(然后将前一个分支名称切换到当前头部。)(最好遵循MBO的建议并进行交互式rebase。)

你可以做交互式rebasegit rebase -i。手册页有你想要的:

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

#0会做的。

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

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

A)拆分最近的提交

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

$ git reset HEAD~

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

B)拆分更远的提交

这需要重新定位,即重写历史。要指定正确的提交,您有以下几种选择:

  • 如果返回三次提交,则

      $ git rebase -i HEAD~3

    其中3是返回的提交数。

  • 如果它在树的后面比你想数的要远,那么

      $ git rebase -i 123abcd~

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

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

      $ git rebase -i master

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

$ git reset HEAD~

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

终于

$ git rebase --continue

使用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看起来像这样:

onetwothree

我们的git log如下所示(好吧,我使用git log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three2b613bc Rose Perrone 14 seconds ago ---- two9aac58f Rose Perrone 24 seconds ago ---- one

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

git rebase --interactive HEAD~2

这会显示一条如下所示的消息:

pick 2b613bc twopick bfb8e46 three

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

git reset HEAD~

git diff向我们展示了我们刚刚取消了为第二次提交所做的提交:

diff --git a/A b/Aindex 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<<<<<<< HEADtwo and a thirdtwo and two thirds=======twothree>>>>>>> bfb8e46... three

onetwo and a thirdtwo and two thirdsthree

git add .; git rebase --continue

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

commit e59ca35bae8360439823d66d459238779e5b4892Author: Rose Perrone <roseperrone@fake.com>Date:   Sun Jul 7 13:57:00 2013 -0700
three
diff --git a/A b/Aindex 5aef867..dd8fb63 100644--- a/A+++ b/A@@ -1,3 +1,4 @@onetwo and a thirdtwo and two thirds+three
commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8eAuthor: Rose Perrone <roseperrone@fake.com>Date:   Sun Jul 7 14:07:07 2013 -0700
two and two thirds
diff --git a/A b/Aindex 575010a..5aef867 100644--- a/A+++ b/A@@ -1,2 +1,3 @@onetwo and a third+two and two thirds
commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44Author: Rose Perrone <roseperrone@fake.com>Date:   Sun Jul 7 14:06:40 2013 -0700
two and a third
diff --git a/A b/Aindex 5626abf..575010a 100644--- a/A+++ b/A@@ -1 +1,2 @@one+two and a third
commit 9aac58f3893488ec643fecab3c85f5a2f481586fAuthor: Rose Perrone <roseperrone@fake.com>Date:   Sun Jul 7 13:56:40 2013 -0700
one
diff --git a/A b/Anew file mode 100644index 0000000..5626abf--- /dev/null+++ b/A@@ -0,0 +1 @@+one

git rebase --interactive可用于将提交拆分为更小的提交。rebase上的git文档对流程有一个简洁的走查-拆分提交

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

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

  • 标记您要拆分的提交操作“编辑”。

  • 当涉及到编辑该提交时,执行git reset HEAD^。效果是HEAD重绕1,索引也跟着重绕。但是,工作树保持不变。

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

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

  • 重复最后两个步骤,直到你的工作树是干净的。

  • 使用git rebase --continue继续rebase。

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

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

事实证明,在巨型提交的情况下非常有用。

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

打开rebase对话框配置它,然后执行以下步骤。

  • 右键单击要拆分的提交并选择“Edit”(在选择、压扁、删除…之间)。
  • 单击“Start”开始重新设置。
  • 一旦它到达提交拆分,检查“Edit/Split”按钮并直接点击“Amend”。提交对话框打开。
    编辑/拆分提交
  • 取消选择要放在单独提交中的文件。
  • 编辑提交消息,然后单击“commit”。
  • 直到有文件要提交,提交对话框才会反复打开。当没有更多文件要提交时,它仍然会询问您是否要再添加一次提交。

非常有用,感谢TortoiseGit!

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

如果你有这个:

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 mybranchgit reset --hard A

创建第一次提交(C):

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

创建第二次提交(D):

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

之前的答案已经介绍了使用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 statusinteractive rebase in progress; onto be83b41Last commands done (3 commands done):pick 4847406 US135756: add debugging to the file download codee 65dfb6a US135756: write data and download from remote(see more in file .git/rebase-merge/done)...

在这种情况下,我正在编辑的提交具有sha165dfb6a。知道这一点,我可以使用git checkout的形式在我的工作目录上查看该提交的内容,它同时接受提交和文件位置。在这里我使用.作为文件位置来替换整个工作副本:

git checkout 65dfb6a .

不要错过最后的点!

这将签出并暂存您正在编辑的提交之后的文件,但相对于您之前的提交,因此您已经提交的任何更改都不会成为提交的一部分。

您可以现在继续进行并按原样提交以完成拆分,或者再次进行,在进行另一个临时提交之前删除提交的某些部分。

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

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

最后,一旦你提交了所有的更改,

git rebase --continue

将继续并完成重设基地行动。

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

以下是如何在IntelliJ IDEAPyCharmPhpStorm等中拆分一个提交

  1. 在版本控制日志窗口中,选择您想要的提交拆分,右键单击并选择#0

  2. 将要拆分的标记为#0,单击开始重新基于

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

  4. 现在这些提交返回到暂存区域,然后您可以提交它们在所有更改提交之后,旧的提交

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

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的博客文章

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

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

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

使用最新提交

如果你只是想从现有的提交中提取一些东西并保留原始的,你可以使用

git reset --patch HEAD^

而不是git reset HEAD^。此命令允许您重置所需的块。

选择要重置的块后,您将拥有将重置前一次提交中的更改的分阶段块。现在您更改最后一次提交,从其中删除这些更改

git commit --amend --no-edit

并且您有未暂存的块,您可以通过以下方式将其添加到单独的提交中

git add .git commit -m "new commit"

不使用最新提交

当然,按照上面的建议使用git rebase --interactive去一些以前的提交。

离题事实:

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

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

假设您的历史看起来像… —> P –> Q –> R –> … –> Z = mybranch,您想将P –> Q拆分为两个提交,以P –> Q1 –> Q' –> R' –> … Z' = mybranch结束,其中Q'R'等处的代码状态与QR等相同。

在开始之前,如果你是偏执狂,请备份mybranch,这样你就不会有丢失历史记录的风险:

git checkout mybranchgit checkout -b mybranch-backup

首先,签出P(要拆分之前的提交),并创建一个新的分支来使用

git checkout Pgit 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 Qgit reset --soft Q1git commit -m "Added unit tests for widgets"git branch -f mybranch-splitting

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

git rebase --onto HEAD Q mybranch

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

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

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

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

作为互动回扣的忠实粉丝,这是我能想到的最简单、最直接的一组步骤。我希望这能帮助任何面临这个问题的人!