如何删除Git历史中给定日期之前的所有提交?

时间:2015-03-13 22:06:50

标签: git

鉴于存储库,我想删除特定提交之前的所有提交或历史记录中的日期。

我的存储库中有大约10000个提交,我想只保留最后1000个,然后删除其余的。基本上我想要做的是说将第一个提交转移到X

起初我以为我可以将所有这些提交重新绑定并压缩成一个,但这会在rebase期间导致很多合并冲突。如果有一种方法来压缩提交,使得壁球之后的版本是最后一次提交,那么它也会起作用。

4 个答案:

答案 0 :(得分:19)

警告:以下内容很危险,因为它会重写历史记录。在进行任何类似的重大历史记录重写之前,请务必确保备份您的仓库。

将下面的哈希替换为您想要作为新的第一次提交的提交的父级的哈希值。

git filter-branch --parent-filter '
    read parent
    if [ "$parent" = "-p 5bdd44e5919cb0a95a9924817529cd7c980f88b5" ]
    then
        echo
    else
        echo "$parent"
    fi'

这会重写每个提交的父母;对于大多数提交,它使它们保持相同,但是父项与给定的哈希匹配,它将替换为空父项,这意味着它现在将成为没有父项的提交。这将分离你所有的旧历史。

请注意,如果您希望第一次提交的是合并提交,那么您需要以正确的顺序为合并提交的每个父项匹配-p parent1 -p parent2 -p parent3之类的内容。

如果要将其应用于所有分支和标记而不是仅应用于当前分支,请在命令末尾(脚本之后)传入--all

完成此操作并检查它是否正常工作后,您可以删除原始分支并运行gc以清除现在未引用的提交:

git update-ref -d refs/original/refs/heads/master

请注意,由于git倾向于尝试保留数据,为了实际释放空间,您还必须从reflog中删除提交,然后运行gc来清除它起来。

git reflog expire --expire-unreachable=all --all
git gc --prune=all

如果您不是为了节省空间或根除旧提交,那么您可以将旧历史保留在分支中,例如git branch old-master refs/original/refs/heads/master;你甚至可以虚拟地重新连接"它使用git replace,此时您将有两个未连接的历史记录(因此当您推送到远程仓库时,您只会推送截断的历史记录)但是当您查看本地仓库中的历史记录时,您将看到完整的历史。

答案 1 :(得分:7)

对我来说更简单的是使用git replace(编辑:已成功测试!)。

首先将您想要的所有提交压缩成一个: (我们将调用您想要压缩的最后一次提交的sha 和第一次提交的sha,所以你的root提交)

git checkout -b big_squash <LastSha>
git reset --soft <RootSha>
git commit --amend -m "My new root"

现在,你必须让你的分支big_squash指向一个新的根(在这里称为<NewRootSha>。我们在这里只对sha1感兴趣,一旦你成功完成就可以删除分支操作)。

然后你有两种可能性:

  • 如果很容易就做了git rebase --onto稍后的提交(这是git book的首选解决方案,但是在成功测试了其他解决方案之后,那不是我的;)
  • 使用git replace 隐藏旧历史记录(历史记录仍在存储库中!但我们会将其永久保存在git filter-branch

要替换您想要使用新创建的提交压缩的最后一次提交:

git replace <RootSha> <NewRootSha>

现在,您可以在git filter-branch之后git replace使其永久化!

更换后,请执行:

git filter-branch master, <put here the name of all your branches>

如果结果适合您,请删除文件夹.git/refs/original(其中包含git filter-branch之前的所有已保存参考号)和文件夹.git/refs/replace(其中包含您不再需要的替代品。)

此解决方案的优点是简单且可恢复(除了删除文件夹后的最后一步;)

已经完成了!

你可以在这里找到文件:

答案 2 :(得分:3)

您无法获得所需内容,因为您无法从存储库中删除任何内容,只能为其添加新内容。

要重述,但使用提交图形绘制,您现在拥有的是(简化):

<jumble of commits> - K - L - M - etc ...  <-- master
                        \      / (merges)  <-- etc
                        (branches)

和你想要的(同样简化)是:

K - L - M - etc ...  <-- master
 \      / (merges)  <-- etc
 (branches)

以便K现在是根提交。

您无法,但您可以获得新的根提交,其几乎与{{完全相同1}},有两个很大的区别:一个不同的SHA-1,没有父提交ID。提交将具有与提交K相同的树和所有相同的文件。

K复制到K后,您可以将K'复制到L,依此类推,这样您获得的是具有相同内容的新提交图形状和相同的文件等,只需使用全新的SHA-1 ID。

执行此操作的git是L'

使用filter-branch至少有两种方法可以实现这一目标。一种是使用提交过滤器:

  • 跳过所有提交,直到提交filter-branch,然后
  • 复制所有提交(包括K本身)

(然后添加通常的K等等)。由于提交过滤器不是--tag-name-filter cat,所以这个有点痛苦,所以你必须记住&#34;外部跳过/保持状态(例如,在文件中)。

另一种方法是使用eval as already described by Brian Campbell

这些之间的区别在于--parent-filter方法更容易,但复制了所有&#34; pre - --parent-filter&#34;提交也是如此,这样你就可以在副本中找到两个独立的图形了。你可能想要这个或不想要;如果在清除K名称空间后,没有引用&#34; pre - refs/original&#34;承诺,他们将像往常一样被垃圾收集,以便差异消失。

答案 3 :(得分:3)

您可以通过git clone --depth 1000使用浅层克隆。浅克隆仍具有完全提交能力,请参阅https://github.com/git/git/commit/82fba2b9d39163a0c9b7a3a2f35964cbc039e1a

您甚至可以保留旧树,以防您仍然需要它并且它完全兼容,无需更改历史记录。