我认为这可以用图片来解释,虽然我的 ASCII 艺术能力在几个--amends 之后就耗尽了。诀窍是要意识到 git commit --amend 所做的,与没有 --amend 的 git commit 相比,是更改存储在新提交中的 父哈希。
普通的git commit 将索引内容冻结到树中,并使用新树进行新提交,您作为作者和提交者,您的日志消息和 当前提交 作为父级新提交的:
...--F--G <-- [you were here]
\
H <-- branch (HEAD) [you are here now]
然后我们在理顺绘图后,重新提交I:
...--F--G--H--I <-- branch (HEAD)
和新的提交 J:
...--F--G--H--I--J <-- branch (HEAD)
等等。
然而,使用git commit --amend,我们像往常一样进行新的提交H除了H的父是F而不是@987654334 @:
G [you were here]
/
...--F--H <-- branch (HEAD)
然后,创建I,我们像往常一样除了I的父是F而不是H:
G [you were here]
/
...--F--H [you were here too]
\
I <-- branch (HEAD)
等等。
如果您想在此时运行git log(提交I 指向提交F),您将看到提交I,然后提交F,然后是E,依此类推.提交 G 和 H 将对 gitk 或 git log 不可见(但将显示在 git reflog 输出中,因为它不遵循父链)。
在进行了 100 次这样的操作之后,我已经用完了提交信,并且不可能吸引所有提交的疯狂粉丝都指向 F,但你可以想象它们;或者我可以只绘制 9 个这样的提交,G 到 O:
GH
| I
|/ J
...--F==K
|\ L
| M
ON
这些不同的提交中的每一个都有您想要的 tree 和 message。除了 G 本身之外,这些提交中的每一个都有什么问题是它有错误的 parent: 你希望 G 有 F 作为它的父级(它确实),但是您希望 H 将 G 作为其父级(它没有)。
这意味着你必须复制错误的提交。让我们首先将H 复制到H',将G 作为其父级,但在其他方面使用与H 相同的树 和message 以及其他元数据:
H'
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
现在我们需要将I 复制到新的提交I'。 I' 的父级不是G 而是H':
H'-I'
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
我们重复J 到J',使用I' 作为J' 的父级,依此类推,直到我们将每个“错误”提交复制到“正确”提交。然后我们可以设置一个分支名称来指向最后一个这样的复制提交:
H'-I'-J'-K'-L'-M'-N'-O' <-- repaired
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
在repaired 或gitk --all 上运行git log 现在将显示提交N' 导致返回M' 导致返回L' 等等。请记住,git log(和 gitk)向后跟随父链接,根本不查看 reflog。
如果您愿意让某些元数据(作者和提交者姓名、电子邮件和时间戳)被破坏,则可以使用 git commit-tree 使用 shell 脚本循环轻松完成每个提交。如果你想保留那个元数据,那就更难了:你需要在每次调用git commit-tree之前设置一系列Git环境变量,设置:
-
GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE: 给作者
-
GIT_COMMITTER_NAME、GIT_COMMITTER_EMAIL、GIT_COMMITTER_DATE:类似,但针对提交者
日志消息可以直接从不正确的提交中复制,使用sed 或类似的方法来切断所有内容,包括第一个空行(注意这会丢弃任何 i18n 编码数据,sed 可能表现不佳提交消息中有未终止的最终文本行,但这些可能是可以容忍的;如果不是,请从 git filter-branch 中提取相关代码。
使用git reflog 以正确的顺序获取所有要复制的提交的哈希ID(最旧的先复制,最后复制的=最新的最后)。将它们放在一个文件中,每行一个条目。那么下面的 untested shell 脚本可能就足够了:
parent=$hash # hash ID of commit G, onto which new chain will be built
while read tocopy; do
tree=$(git rev-parse $tocopy^{tree})
parent=$(git cat-file -p $tocopy | sed '1,/^$/d' |
git commit-tree -p $parent -t $tree)
done < $file_of_commits
git branch repaired $parent
这会创建一个新的分支名称repaired 来保存新构建的链。