删除git历史记录中的第一个x提交,并从历史记录的其余部分中删除所有合并分支

时间:2015-12-08 19:57:34

标签: git github github-for-mac git-history-graph

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

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

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

2 个答案:

答案 0 :(得分:3)

正如有几个人已经说过的,这几乎不是一个好主意,因为有几个原因我不会重复。我想再添加一件事,然后展示如何使用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似乎删除一些提交,您必须复制每次提交-to-keep 到一个新的提交,它具有一个新的,不同的ID提交(具有相同的消息等等,但以前一样,但是不同的parent行。 2

实质上,执行git filter-branch的结果是创建存储库的新副本,其中至少包含一些,可能完全是新的和不同的提交。这反过来意味着使用旧存储库的任何其他人都必须丢弃旧存储库并切换到新存储库。

git filter-branch

虽然git filter-branch有很多选择,但其核心工作归结为此。对于每次提交: 3

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

这里的子弹头列表是&#34;副本&#34;步骤,之后是最后一个任务,&#34;更新参考&#34;。要正确理解这一部分,您需要知道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 &#34;加密&#34;的含义形容词是指你不能简单地对提交进行一些改动,例如,在消息中添加文本,以生成与之前相同的旧SHA-1。在计算上可行的时间内完成此操作的唯一方法是打破加密。

2 在不太密集的变更案例中,如果您制作原始提交的精确副本,最终会使用之前的SHA-1。例如,如果您有一个过滤器分支操作,它删除链中的第二个最尖端的提交,则只有最尖端的提交会获得一个新的SHA-1。但是,在这种特殊情况下,我们建议删除根提交,这必须对每个后续提交重新编号。

3 要复制的提交是从作为过滤器分支操作的一部分提供的gitrevisions - 样式参数中获取的。要重写的分支名称也使用&#34;肯定参考&#34;。

从这里获取

答案 1 :(得分:1)

首先请三思而后行,如果你真的想这样做的话。 (改变历史,特别是在公共存储库中,通常是一个坏主意。)

您可以使用git rebase -i来执行此操作。在那里,您可以使用fixup将两个提交合并为一个,您可以使用edit来更改提交。 (包括改变作者。)

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