0

我有一个 git 项目历史,我有近 400 次提交。我想删除第一个(最早的)200 个提交。然后在剩下的 200 次提交中,我只想删除所有合并提交并保持其余部分井井有条。

完成后,我想检查所有剩余的提交并更改一封特定作者的电子邮件。

有没有办法优雅地做到这一点?

4

2 回答 2

4

正如一些人已经说过的,这很少是一个好主意,原因有几个,我不会重复。不过,我想再添加一件事,然后展示如何使用git filter-branch.

这不是删除,而是一个新副本:本质上,一个新的 repo

关于这一点的关键是你不能从一系列提交的前面或中间删除提交。原因很简单:每个提交记录,作为其身份的一部分,其父提交的身份。技术术语是提交图形成一个Merkle 树

更具体地说,提交的身份——“真名”,如果你愿意的话——是它的 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删除一些提交,您必须将每个提交保留复制到具有新的不同 ID 提交的新提交(具有相同的消息依此类推,但行不同)。2parent

本质上,这样做的结果git filter-branch是制作存储库的新副本,其中至少包含一些,也许是全部,新的和不同的提交。这反过来意味着使用旧存储库的其他任何人都必须丢弃他们的旧存储库并切换到新存储库。

git 过滤器分支

虽然git filter-branch有很多选择,但它的核心工作归结为这一点。对于每个提交:3

  • 展开提交的源代码树
  • 获取作者和提交者(姓名、电子邮件和时间戳)
  • 应用所有过滤器:
    • 对树进行任何必要的更改
    • 对作者和提交者进行任何必要的更改
    • 保留或跳过此特定提交:如果保留此提交,则从剩下的内容中进行新的提交
  • 在映射文件中添加一个条目,“原始 SHA-1”到“新 SHA-1”

这里的重点列表是“复制”步骤,之后还有最后一个任务,“更新引用”。要正确理解这部分,您需要知道 git 的引用是如何工作的,但简而言之,会检查分支名称(以及如果添加--tag-filterwee 标记名称)以查看它们是否指向已重写的旧提交。如果是这样,则将它们更改为指向新副本,或者在跳过提交的情况下指向最近的新副本提交,

要实现您想要的,您需要编写一个提交过滤器,该过滤器使用该skip_commit函数忽略您要删除的提交(前 200 个和合并),并git commit-tree在其余部分上使用。有关更多详细信息,git filter-branch请参阅文档

(有这么多选择的一个原因git filter-branch是扩展和重新压缩整个源树非常慢。脚本试图避免这种情况,并且如果您的所有过滤器都可以在索引和提交图中完成 - 而不扩展源树——过滤器完成得更快。)

基于新提交根的示例实现:

下面的代码将创建一个新的存储库,其中仅包含指定的新 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要复制的提交是从您在 filter-branch 操作中提供的gitrevisions样式参数中获得的。要重写的分支名称也取自这里,使用“肯定引用”。

于 2015-12-08T22:08:16.343 回答
1

首先,如果您真的想这样做,请三思而后行。(更改历史记录,尤其是在公共存储库上,通常是个坏主意。)

你可以使用git rebase -i这样做。在那里,您可以使用fixup将两个提交合并为一个,您可以使用edit更改提交。(包括更改作者。)

对于多个提交的自动更改,您可以使用git filter-branch. 但只有在你知道自己在做什么的情况下才使用它。

于 2015-12-08T20:09:43.483 回答