我认为这更能用图片来解释,尽管我的 ASCII-art 能力在几个--amends
. 诀窍是要意识到git commit --amend
,与git commit
没有相比--amend
,所做的是更改存储在新提交中的父哈希。
法线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
而不是G
:
G [you were here]
/
...--F--H <-- branch (HEAD)
然后,I
make ,我们像往常一样制作它,除了父级是I
而F
不是H
:
G [you were here]
/
...--F--H [you were here too]
\
I <-- branch (HEAD)
等等。
如果你想象git log
在这一点上运行——commitI
指向F
commit——你会看到 commit I
,然后是 commit F
,然后E
是 ,依此类推。提交G
并且H
将对gitk
or不可见git log
(但会显示在git reflog
输出中,因为它不遵循父链)。
在 100 次这样的操作之后,我已经用完了提交信,并且不可能吸引所有提交的疯狂粉丝都指向F
,但你可以想象它们;G
或者我可以通过以下方式仅绘制 9 个这样的提交O
:
GH
| I
|/ J
...--F==K
|\ L
| M
ON
这些不同的提交中的每一个都有您想要的树和消息。除了它自己之外,这些提交中的每一个都有什么问题,G
那就是它有错误的父级:你想G
拥有F
它的父级(它确实如此),但是你想H
拥有G
它的父级(它确实如此)不是)。
这意味着您必须复制错误的提交。让我们首先将其复制H
到H'
它G
的父级,但否则使用相同的树和消息以及其他元数据H
:
H'
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
现在我们需要复制I
到一个新的提交I'
。I'
is not G
but rather的父级H'
:
H'-I'
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
我们重复J
to J'
,使用I'
作为 的父级J'
,依此类推,直到我们将每个“错误”提交复制到“正确”提交。然后我们可以设置一个分支名称来指向最后一个这样的复制提交:
H'-I'-J'-K'-L'-M'-N'-O' <-- repaired
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
git log
在repaired
, 或, 时运行gitk --all
现在将显示提交N'
导致返回到M'
返回L'
等等。请记住,git log
(and gitk
) 向后跟随父链接,根本不查看 reflog。
如果您愿意让一些元数据(作者和提交者姓名、电子邮件和时间戳)被破坏,使用git commit-tree
. 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(最旧的先复制,最后复制的 = 最新的最后)。将它们放在一个文件中,每行一个条目。那么以下未经测试的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
来保存新构建的链。