有没有一种简单的方法可以压缩 git 中的许多提交?

Is there an easy way to squash many commits in git?

我有以下问题:
我有很多提交需要压缩(数千个,我们有一个 运行away 脚本)。这些提交在 40 到 200 之间分组。
使用 git rebase -i 是不可行的,因为它会涉及太多的劳动力。 我们有一个工具,可以从分支 HEAD 输出这样一个组相对的第一次和最后一次提交(可用于通过其引用哈希获取实际提交)。

举个例子,我正在寻找可以将 HEAD~400 压缩到 HEAD~200 到单个提交中的东西。然后可以 运行 再次(使用更改参数)将 HEAD~100 压缩到 HEAD~50 到另一个提交中。

编辑 1:
我考虑过创建一个“假”编辑器,它通过对 rebase 文件执行更改来本质上伪造交互性。一个抽象的示例脚本看起来像这样(我可以循环直到所有组都被压扁):

start=$('get start of oldest group')
end=$('get end of oldest group')
git config core.editor "'~/fakeeditor' -start $start -end $end"
git rebase -i

您可以预先为 git rebase -i 生成完整的命令列表,然后将它们粘贴到编辑器中。这个过程看起来像这样:

  1. 对于要压缩的每批提交,生成完整的提交哈希列表,并在前面加上单词 fixup;假设您的开始和结束提交在 $start$end 中:git log "$start...$end" --pretty='fixup %h'
  2. 在每批提交之间,做同样的事情,但 pick;你可以取一批南瓜的末尾和下一批南瓜开始的父代,然后丢弃第一行:git log "$end...$nextStart^" --pretty='pick %h' | sed -n '2,$p'
  3. 在您要压缩的第一个提交之前找到提交,然后 运行 git rebase -i 将其作为参数。
  4. 删除 git 生成的所有行,然后粘贴到您准备好的待办事项列表中。
  5. 保存并退出编辑器,git 的 rebase 工具将在您的列表中运行。

首先,创建一个脚本来操作 git rebase 的待办事项列表:

squash-it 文件:

#!/bin/sh
first=$(git rev-parse --short "")
last=$(git rev-parse --short "")
todo=""
lines=$(
sed -n "/^pick $first /,/^pick $last/{s/^pick/squash/p}" "$todo" | sed "1s/squash/pick/"
sed "/^pick $first /,/^pick $last/d" "$todo"
)
echo "$lines" > "$todo"

然后像这样使用这个脚本:

GIT_SEQUENCE_EDITOR='sh -c "./squash-it HEAD~100 HEAD~50 "' GIT_EDITOR=cat git rebase -i HEAD~100^

如果需要,您可以将 squash 替换为 fixup

您当然可以在脚本本身中生成此行,例如在循环中根据您的喜好替换 HEAD~100HEAD~50

我个人最喜欢使用 git reset --soft

所以,假设您想压缩从 HEAD~1000 到这一点(HEAD~1000 是最后一个 surviving 不会被压缩的提交) :

git reset --soft HEAD~1000
git commit -m "Squashed a lot of stuff"

就是这样。您可以使用修订 ID 而不是使用 HEAD~n 引用。

我看到你想像分段那样做……所以,比如说……让我们把 HEAD~400 压缩到 HEAD~200……然后从 HEAD~200 压缩到 HEAD~100.. . 然后从 HEAD~100 到 HEAD。因此,让我们创建一个临时分支来完成我们的工作。

git checkout -b temp HEAD~200
git reset --soft HEAD~400
git commit -m "squashing first segment"
# next segment
git checkout the-original-branch~100
git reset --soft temp
git commit -m "Second segment"
git branch -f temp #set temp over here
# final segment
git checkout --detach the-original-branch
git reset --soft temp
git commit -m "Final segment"
git branch -f temp #set temp over here
# if you like the result, feel free to move whatever branch over here
git branch -f whatever-branch
# and delete temp if so you want
git branch -D temp

我觉得很简单。

一天后,我才意识到(通过回答另一个问题)可以用更简单的方式完成:

git branch -f temp $( git commit-tree -p HEAD~400 -m "first squash" HEAD~200^{tree} )
# that set up temp on the first squash
git branch -f temp $( git commit-tree -p temp -m "second squash" HEAD~100^{tree} )
# temp has move to second squash
git branch -f temp $( git commit-tree -p temp -m "final squash" HEAD^{tree} )

现在 temp 已经按照我的示例中请求的方式压缩了提交。随意在那边做一个reset --hard(使用reset --hard时通常的警告)。