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

时间:2011-06-02 16:11:12

标签: git

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

18 个答案:

答案 0 :(得分:1540)

git rebase -i会这样做。

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

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

$ git reset HEAD~

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

如果它再次回到树中,那么

$ git rebase -i HEAD~3

其中3是多少次提交。

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

$ git rebase -i 123abcd~

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

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

$ git reset HEAD~

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

$ git rebase --continue

答案 1 :(得分:284)

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

  

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

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

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

  •   
  • 在编辑提交时,执行git reset HEAD^。结果是HEAD被一个重绕,索引也随之而来。但是,工作树保持不变。

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

  •   
  • 使用现在适当的提交消息提交now-current索引。

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

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

  •   

答案 2 :(得分:38)

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

这通常是我们运行git rebase --continue的交互式rebase期间的重点,因为我们通常只想返回我们的提交堆栈来编辑早期的提交。但这一次,我们想要创建一个新的提交。所以我们将运行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

答案 3 :(得分:32)

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

将文件拆分为不同的提交时效果很好,但是如果要拆分各个文件的更改,还需要了解更多信息。

使用rebase -i并将其标记为edit,您必须进行要拆分的提交,您有两种选择。

  1. 使用git reset HEAD~后,使用git add -p单独检查修补程序,以便在每次提交中选择所需的修补程序

  2. 编辑工作副本以删除您不想要的更改;承认临时国家;然后撤回下一轮的完整提交。

  3. 如果您要拆分大型提交,则选项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操作。

答案 4 :(得分:17)

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

  

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

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

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

  •   
  • 在编辑提交时,执行git reset HEAD^。结果是HEAD被一个重绕,索引也随之而来。但是,工作树保持不变。

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

  •   
  • 使用现在适当的提交消息提交now-current索引。

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

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

  •   
     

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

答案 5 :(得分:10)

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

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

答案 6 :(得分:9)

现在,在最新的Windows版TortoiseGit中,您可以轻松完成。

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

  • 右键点击要拆分的提交,然后选择&#34; Edit&#34; (在挑选,壁球,删除......)。
  • 点击&#34; Start&#34;开始变基。
  • 一旦到达提交分割,请检查&#34; Edit/Split&#34;按钮和 点击&#34; Amend&#34;直。提交对话框打开 Edit/Split commit
  • 取消选择要单独提交的文件。
  • 编辑提交消息,然后单击&#34; commit&#34;。
  • 在有文件提交之前,提交对话框将一次又一次打开。当没有更多要提交的文件时,它仍然会询问您是否要再添加一次提交。

非常有帮助,谢谢TortoiseGit!

答案 7 :(得分:8)

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

在巨型提交的情况下变得非常有用。

答案 8 :(得分: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's blog post

答案 9 :(得分:2)

没有交互式rebase的最简单的事情是(可能)在你要分割的那个之前创建一个新的分支,提交,重置,存储,提交文件移动,重新应用存储并提交更改,然后与前一个分支合并或者选择后面的提交。 (然后将前一个分支名称切换到当前头部。)(最好遵循MBO建议并进行交互式变基。)

答案 10 :(得分:2)

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

答案 11 :(得分: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"

答案 12 :(得分:0)

这是在 IntelliJ IDEA PyCharm PhpStorm

中分割一次提交的方法
  1. 在“版本控制”日志窗口中,选择您想要的提交 拆分,右键单击并选择 Interactively Rebase from Here

  2. 将要拆分的那个标记为 edit ,然后单击 Start Rebasing

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

  4. 现在,这些提交已返回到暂存区,然后可以提交它们 分别。提交所有更改后,旧的提交 变得不活动。

答案 13 :(得分:0)

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

答案 14 :(得分:0)

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

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

  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 commit
000bbb removal of things that should be changed later
  1. 使用 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)
  1. 现在,您可以使用 git rebase -i 将前两个提交压缩/修复为一个,如果您之前没有向其提供有意义的提交消息,则可以选择修改还原提交。你应该留下
000ddd Original commit, but without some content that is changed later
000eee Things that should be changed later

答案 15 :(得分:0)

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

git reset --patch HEAD^

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

选择要重置的块后,您将拥有暂存的块,这些块将在您执行后重置上一次提交中的更改

git commit --amend --no-edit

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

git add .
git commit -m "new commit"

偏离主题的事实:

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

答案 16 :(得分:0)

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

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

在开始之前,如果您有疑虑,请备份 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 处的一个分离的 HEAD 上,提交这个(创建 Q1),并将工作分支拉到它:

Q'

您现在位于 git checkout Q git reset --soft Q1 git commit -m "Added unit tests for widgets" git branch -f mybranch-splitting mybranch-splitting,它应该具有与 Q' 完全相同的代码状态。现在将原始分支(从 QQ)重新设置为:

Z

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

mybranch

答案 17 :(得分:0)

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

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

作为交互式 rebase 的忠实粉丝,这是我能想到的最简单、最直接的一组步骤。我希望这可以帮助任何遇到此问题的人!