如何压缩分支中的提交

How to squash commits in branches

我正在将旧的 svn 存储库导入 git。有一次,一个文件夹在所有分支中都被重命名了。这是在 svn 中通过创建具有历史记录的副本来完成的,然后在第二次提交时删除原始文件。所以我有一个看起来像这样的回购协议:

A -> B -> C -> D* -> E* -> F -> G -> H
      \-> 1 -> 2* -> 3* - > 4 -/

其中 D/E 和 2/3 是我要压缩的提交。 压缩的原因是虽然 svn 知道 "duplicate with history",但 git 不认为这是重命名,因为原始文件直到下一次提交才被删除,我因此失去了历史记录点.

我已经尝试了一些可行的 rebase 脚本,但它们也压平了我的所有分支。以上是我必须做的事情的一个非常简化的版本,这就是为什么我真的需要脚本,因为我不能手动完成。在 SVN 存储库的整个历史中有超过 1,000 个分支,并且可能有十几个并行分支完成了此更改(全部同时进行)。

git 存储库尚未发布,因此维护哈希无关紧要。我假设我需要使用一些过滤器分支脚本,但我仍在尝试弄清楚如何管理我希望能在这里得到帮助的东西。我 可以 提供每个需要压缩的提交及其父项的 sha1。

您想使用 git filter-branch 使用 --parent-filterD 的 SHA 的任何外观替换为 C 的 SHA。您还可以查看 .git/info/graftsgit replace,这可能比编写 --parent-filter 更简单,并且可以使用 filter-branch.

使其永久化

更新: 正如@torek 所说,你绝对应该使用git replace。举一个真实的例子,这里是从 readme.mdREADME.md 的重命名,中间重命名为 README1.mdhttps://github .com/dahlbyk/posh-git/compare/dahlbyk:2b9342c...dahlbyk:57394c5。让我们将 2b9342c 称为 C,将 57394c5 称为 E:

$ git tag E 57394c5
$ git tag C 2b9342c
$ git tag G 450d8f1
$ git log --oneline --graph --decorate C~..G
*   450d8f1 (tag: G) Merge pull request #320 ...
|\  
| * 941935c Fix a few kbd / missing markdown issues/
| * f13dcf9 Upcase readme and have more prompt examples.
| * 57394c5 (tag: E) Now rename to README.md.
| * eb79ef2 Prepare to upcase README.md filename.
* |   536c57f Merge pull request #319 ...
|\ \  
| |/  
|/|   
| * 7fafb7b Speed up Get-GitStatus
|/  
* 2b9342c (tag: C) Merge pull request #313 ...

为了假装中间移动从未发生过,我可以 replace E 的父级 (E~) 与其祖父级 (E~2 = C):

$ git log --stat --oneline C..E
57394c5 Now rename to README.md.
 README1.md => README.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
eb79ef2 Prepare to upcase README.md filename.
 readme.md => README1.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
$ git replace E~ C
$ git log --stat --oneline C..E
57394c5 Now rename to README.md.
 readme.md => README.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
eb79ef2 Merge pull request ...

最后,filter-branch 将使更改永久生效:

$ git filter-branch -- ^C G E  # For demo, only rewrite G & E afer C
$ git log --graph --oneline --decorate C~..G
*   fcfd345 (tag: G) Merge pull request #320 ...
|\  
| * fa76267 Fix a few kbd / missing markdown issues/
| * 4900687 Upcase readme and have more prompt examples.
| * b25aa5a (tag: E) Now rename to README.md.
* |   536c57f Merge pull request #319 ...
|\ \  
| |/  
|/|   
| * 7fafb7b Speed up Get-GitStatus
|/  
* 2b9342c (tag: C) Merge pull request #313 ...

为了您的目的,您将执行如下操作:

$ git replace E~ E~2
$ git replace 3~ 3~2
$ git filter-branch -- ^A --all

更新 2:

The commit message I get is off of E, which I don't care about. I'd rather have D's commit message (or a script provided message).

为了保留 D 的提交元数据,我建议重新开始并使用 --commit-filter 来指定 Etreegit cat-file -p E ) 对于 D(并且应该跳过 E),例如

git filter-branch --commit-filter '
  if [ "$GIT_COMMIT" = "SHA of D" ];
  then
    git commit-tree "TREE of E" -p "SHA of C";
  elif [ "$GIT_COMMIT" = "SHA of E" ];
  then
    skip_commit "$@";
  else
    git commit-tree "$@";
  fi;
  ' -- ^A E G