【发布时间】:2016-03-13 21:43:43
【问题描述】:
我有一个 git 项目历史,我有近 400 次提交。我想删除第一个(最早的)200 个提交。然后在剩下的 200 次提交中,我只想删除所有合并提交并保持其余部分按顺序排列。
完成后,我想检查所有剩余的提交并更改一个特定作者的电子邮件。
有没有办法优雅地做到这一点?
【问题讨论】:
标签: git github github-for-mac git-history-graph
我有一个 git 项目历史,我有近 400 次提交。我想删除第一个(最早的)200 个提交。然后在剩下的 200 次提交中,我只想删除所有合并提交并保持其余部分按顺序排列。
完成后,我想检查所有剩余的提交并更改一个特定作者的电子邮件。
有没有办法优雅地做到这一点?
【问题讨论】:
标签: git github github-for-mac git-history-graph
正如一些人已经说过的,这很少是一个好主意,原因有几个,我不会重复。不过,我想再添加一件事,然后展示如何使用git filter-branch 来做到这一点。
了解这一点的关键是您不能从一系列提交的前面或中间删除提交。原因很简单:每个提交记录,作为其身份的一部分,其父提交的身份。技术术语是提交图形成Merkle Tree。
更具体地说,提交的身份——“真名”,如果你愿意的话——是它的 SHA-1。 SHA-1 是提交中数据的加密1 散列。其中一条数据是parent 行。这是 git 源本身的实际提交(减去 @ 符号以阻止垃圾邮件收集):
tree 55c0d854767f92185f0399ec0b72062374f9ff12
parent 8413a79e67177d026d2d8e1ac66451b80bb25d62
author Junio C Hamano <gitster pobox.com> 1436563740 -0700
committer Junio C Hamano <gitster pobox.com> 1436563740 -0700
The last minute bits of fixes
Signed-off-by: Junio C Hamano <gitster pobox.com>
如果您要尝试删除父提交,在链中的任何位置,您都会为子提交获得一个新的、不同的哈希值。这意味着所有 其 子级也需要进行更改,以将新的 SHA-1 纳入整个链条。
这对你意味着什么,包括git filter-branch,要似乎删除一些提交,你必须复制每个提交-keep 到具有新的不同 ID 提交的新提交(具有相同的 tree 和 message 等等,但不同parent 行)。2
本质上,执行git filter-branch 的结果是制作存储库的新副本,其中至少包含一些,也许是全部,新的和不同的提交。这反过来意味着使用旧存储库的其他任何人都必须丢弃旧存储库并切换到新存储库。
虽然git filter-branch 有很多选择,但它的核心工作归结为这一点。对于每个提交:3
这里的重点列表是“复制”步骤,之后是最后一个任务,“更新引用”。要正确理解这部分,您需要知道 git 的引用是如何工作的,但简而言之,会检查分支名称(如果您添加 --tag-filter,则标记名称为 wee)以查看它们是否指向已重写的旧提交。如果是这样,则将它们更改为指向新副本,或者在跳过提交的情况下指向最近的新副本提交,
要实现您想要的,您需要编写一个提交过滤器,该过滤器使用skip_commit 函数来忽略您要删除的提交(前200 个和合并),并在其余部分使用git commit-tree。详情请见the git filter-branch documentation。
(git filter-branch 有这么多选项的一个原因是扩展和重新压缩整个源代码树非常慢。脚本试图避免这种情况,如果您的所有过滤器都可以在索引和提交图中完成 -无需扩展源代码树——过滤器完成得更快。)
下面的代码将创建一个新的 repo,其中仅包含指定的新 STARTCOMMIT 之下的所有提交。保留分支和标签。
export STARTCOMMIT=.....
git filter-branch --tag-name-filter cat \
--commit-filter '
git merge-base --is-ancestor ${STARTCOMMIT} ${GIT_COMMIT};
if [ $? -eq 1 ];
then
skip_commit "$@";
else
git commit-tree "$@";
fi' \
-- --all
# remove original references
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
# reduce repo size
git reflog expire --expire=now --all && git gc --aggressive --prune=all
1“加密”形容词的含义是您不能简单地对提交进行轻微更改,例如,在消息中添加文本,以生成相同的旧 SHA-1你以前有的。在计算上可行的时间内做到这一点的唯一方法是破解加密。
2在不太密集的更改情况下,如果您制作原始提交的精确副本,您最终会得到与以前相同的 SHA-1。例如,如果您有一个过滤分支操作删除链中倒数第二个提交,那么只有最尖端的提交会获得新的 SHA-1。不过,在这种特殊情况下,我们建议删除根提交,这必然会重新编号每个后续提交。
3要复制的提交是从您作为过滤器分支操作的一部分提供的gitrevisions 样式参数获得的。要重写的分支名称也取自这里,使用“正面引用”。
【讨论】:
--tag-name-filter cat 添加到上面。我也会从git gc 中删除--aggressive:见stackoverflow.com/a/28721047/1256452
如果您真的想这样做,请先三思。 (更改历史记录,尤其是在公共存储库上,通常是个坏主意。)
您可以使用git rebase -i 来执行此操作。在那里您可以使用fixup 将两个提交合并为一个,您可以使用edit 更改一个提交。 (包括更改作者。)
对于多次提交的自动更改,您可以使用git filter-branch。但只有在你知道自己在做什么的情况下才使用它。
【讨论】:
git help rebase 和git help filter-branch 获取文档或使用您选择的搜索引擎来查找示例。但请确保您知道自己在做什么。 - 你被警告了。