如何从git存储库中删除旧历史记录?


208

恐怕找不到类似此特定情况的任何东西。

我有一个历史悠久的git储存库:500多个分支机构,500多个标签,可以追溯到2007年中期。它包含约19,500次提交。我们希望在2010年1月1日之前删除所有历史记录,以使其更小且更易于处理(我们会将历史记录的完整副本保存在存档存储库中)。

我知道我想要成为新存储库的根的提交。但是,我无法找出正确的git mojo来截断存储库以从该提交开始。我猜是

git filter-branch

涉及移植物是必要的;可能还需要分别对待我们想要保留的200多个分支中的每个分支,然后将回购修补到一起(我确实知道该怎么做)。

有没有人做过这样的事情?如果有的话,我已经有了git 1.7.2.3。

Answers:


118

只需将新的根提交的父级创建为没有父级(或为空的提交,例如,存储库的真实根提交)的嫁接。例如echo "<NEW-ROOT-SHA1>" > .git/info/grafts

创建移植后,它立即生效;您应该能够查看git log并看到不需要的旧提交已消失:

$ echo 4a46bc886318679d8b15e05aea40b83ff6c3bd47 > .git/info/grafts
$ git log --decorate | tail --lines=11
commit cb3da2d4d8c3378919844b29e815bfd5fdc0210c
Author: Your Name <your.email@example.com>
Date:   Fri May 24 14:04:10 2013 +0200

    Another message

commit 4a46bc886318679d8b15e05aea40b83ff6c3bd47 (grafted)
Author: Your Name <your.email@example.com>
Date:   Thu May 23 22:27:48 2013 +0200

    Some message

如果一切都按预期进行,您可以做一个简单的 git filter-branch -- --all地将其永久化。

注意:执行完过滤分支步骤后,所有提交ID都将更改,因此,使用旧存储库的任何人都不得与使用新存储库的任何人合并。


6
我必须做git filter-branch --tag-name-filter cat -- --all更新标签。但是,我还有一些较旧的标签,它们指向我要删除的旧历史记录。如何摆脱所有这些旧标签?如果我不删除它们,那么较早的历史记录不会消失,并且仍然可以通过看到它gitk --all
Craig McQueen

9
“只需创建新根提交的父项的移植,再将其提交给任何父项”就需要详细说明。我试过了,但没有弄清楚“没有父母”的语法。手册页声称需要父提交ID;使用全零只会给我一个错误。
Marius Gedminas 2013年

6
万一其他人想知道它是如何工作的,那很简单:echo "<NEW-ROOT-HASH>" > .git/info/grafts
friederbluemle 2013年

3
我同意,解释什么是移植物将是有用的
Charles Martin

4
引自移植物中链接的Wiki页面。“从Git 1.6.5开始,添加了更灵活的git replace,它允许您将任何对象替换为任何其他对象,并通过可在存储库之间推入和拉出的引用来跟踪关联。” 所以这个答案对于最新版本的git 可能已经过时了。
ThorSummoner

130

发表回复也许为时已晚,但是由于此页面是Google的第一个搜索结果,因此它可能仍会有所帮助。

如果您想在git repo中释放一些空间,但又不想重建所有提交(重新配置或移植),并且仍然能够从拥有完整repo的人那里推/拉/合并,则可以使用git clone 克隆(--depth参数)。

; Clone the original repo into limitedRepo
git clone file:///path_to/originalRepo limitedRepo --depth=10

; Remove the original repo, to free up some space
rm -rf originalRepo
cd limitedRepo
git remote rm origin

通过执行以下步骤,您可以使现有回购协议变浅:

; Shallow to last 5 commits
git rev-parse HEAD~5 > .git/shallow

; Manually remove all other branches, tags and remotes that refers to old commits

; Prune unreachable objects
git fsck --unreachable ; Will show you the list of what will be deleted
git gc --prune=now     ; Will actually delete your data

如何删除所有git本地标签?

附:较旧版本的git不支持从浅存储库克隆/推入/拉入。


9
+1这是较新版本的Git 正确答案。(哦,请回到PPCG!)
wizzwizz4 2016年

6
如何找到cd刚刚删除的文件夹?我觉得这里缺少一些信息。另外,有没有办法将这些更改应用于远程仓库?
Trogdor

4
@Jez那将是另一个获得最高投票的答案。如果您想永久摆脱历史,此答案不适合您。这对工作巨大的历史。
没人

4
要回答我自己的问题:git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02就像魅力!
Micros

5
@Jez,您可以通过运行将浅层回购转换为普通回购git filter-branch -- --all。这将改变其中的所有哈希值,但是之后您可以将其推送到新的回购中
-Ed'ka,

61

这种方法易于理解并且可以正常工作。脚本($1)的参数是对要保留其历史记录的提交的引用(标记,哈希,...)。

#!/bin/bash
git checkout --orphan temp $1 # create a new branch without parent history
git commit -m "Truncated history" # create a first commit on this branch
git rebase --onto temp $1 master # now rebase the part of master branch that we want to keep onto this branch
git branch -D temp # delete the temp branch

# The following 2 commands are optional - they keep your git repo in good shape.
git prune --progress # delete all the objects w/o references
git gc --aggressive # aggressively collect garbage; may take a lot of time on large repos

注意,旧标签仍将保留;因此您可能需要手动删除它们

备注:我知道这和@yoyodin几乎一样,但是这里有一些重要的额外命令和信息。我试图编辑答案,但是由于对@yoyodin的答案进行了实质性更改,因此我的编辑被拒绝了,所以这里的信息是!


我感谢对git prunegit gc命令给出的解释。脚本中的其余命令是否有解释?就目前而言,尚不清楚向其传递什么参数以及每个命令在做什么。谢谢。
user5359531 2016年

2
@ user5359531感谢您的发言,我为每个命令添加了更多注释。希望这可以帮助。
克里斯·梅斯

4
合并冲突到处都是...不是很有用
Warpzit

3
@Warpzit我通过添加命令-p来摆脱合并冲突rebase,如其他答案所建议
leonbloy

1
我完全遵循了这一点,并且我所拥有的与以前一样的历史,从我想要修剪的提交开始,有了一个新的分支,并且拥有与以前相同的所有历史。没有历史记录被删除。
DrStrangepork

51

试试这个方法 如何截断git历史

#!/bin/bash
git checkout --orphan temp $1
git commit -m "Truncated history"
git rebase --onto temp $1 master
git branch -D temp

$1是您要保留的提交的SHA-1,脚本将创建一个新分支,其中包含和之间的所有提交,$1并且master删除了所有较旧的历史记录。请注意,此简单脚本假定您没有名为的现有分支temp。另请注意,此脚本不会清除git数据中的旧历史记录。运行git gc --prune=all && git repack -a -f -F -d您确认后,你真正想失去所有的历史。您可能还需要rebase --preserve-merges但要警告,该功能的git实现并不完美。如果使用手动检查结果。


22
我尝试了此操作,但在此rebase步骤中发生了合并冲突。奇怪-我没想到在这种情况下合并冲突是可能的。
Craig McQueen 2013年

2
使用git commit --allow-empty -m "Truncate history"如果提交签出不包含任何文件。
Friederbluemle 2013年

2
如何将其推回远程主机?当我这样做时,我将拥有新旧历史。
rustyx

1
“温度”应该是什么?您应该以此为论据传递什么?在实际运行这些命令时,是否有这些命令的示例?谢谢。
user5359531 2016年

1
我相信$ 1是提交哈希。(链接的文章中提供了更多详细信息)。
克里斯·诺莱特

34

作为重写历史记录的替代方法,请考虑使用Pro Git书中本文中的git replace as 。讨论的示例涉及替换父提交以模拟树的开头,同时仍将完整历史记录保留为单独的分支进行保管。


是的,我想如果您也取消了单独的完整历史分支,那么您可能会做到这一点。(我们正在尝试缩小存储库。)
ebneter 2012年

1
我对答案不在现场感到沮丧;但是它确实链接到GitScm网站,并且链接到的教程写得很好,并且似乎直接反映了OP的问题。
ThorSummoner 2015年

@ThorSummoner抱歉!我会制定了答案一点更充分的现场
杰夫·鲍曼

不幸的是,这不是重写历史记录的替代方法。在文章开头有一个令人困惑的句子,可能给人以这种印象。可以将其从此答案中删除吗?您将在文章中看到作者确实重写了截断的分支的历史记录,但是提出了一种使用来重新附加旧式“ history”分支的方法git replace。我相信这个问题已在您发布此答案的另一个问题上得到纠正。
米奇

1
关于git replacevs 的讨论,请git graft访问stackoverflow.com/q/6800692/873282
koppor,2016年

25

如果你想保持上游资源库和完整的历史,但地方小签,做一浅克隆用git clone --depth=1 [repo]

提交后,您可以执行

  1. git fetch --depth=1修剪旧的提交。这使得旧的提交及其对象不可访问。
  2. git reflog expire --expire-unreachable=now --all。使所有旧提交及其对象过期
  3. git gc --aggressive --prune=all 清除旧物件

另请参见如何在提交后删除本地git历史记录?

请注意,您不能将此“浅”存储库推送到其他位置:“不允许浅更新”。更改Git远程URL后,请参阅远程拒绝(不允许浅更新)。如果要这样做,则必须坚持嫁接。


1
第一点对我来说很重要。干杯
clapas

21

我需要阅读一些答案和一些其他信息,以了解自己在做什么。

1.忽略所有早于某个提交的内容

该文件.git/info/grafts可以为提交定义假父母。仅包含提交ID的行表示该提交没有父级。如果我们想说我们只关心最近的2000次提交,则可以键入:

git rev-parse HEAD~2000 > .git/info/grafts

git rev-parse给我们当前提交的第2000个父提交的提交ID。上面的命令将覆盖grafts文件(如果存在)。首先检查它是否在那里。

2.重写Git历史记录(可选)

如果要将这个嫁接的假父母变成真实的父母,请运行:

git filter-branch -- --all

它将更改所有提交ID。此存储库的每个副本都需要强制更新。

3.清理磁盘空间

我没有执行第2步,因为我希望我的副本与上游保持兼容。我只是想节省一些磁盘空间。为了忘记所有旧的提交:

git prune
git gc

选择:浅拷贝

如果您拥有另一个存储库的浅表副本,并且只想节省一些磁盘空间,则可以进行更新.git/shallow。但是请注意,没有任何东西指向之前的提交。因此,您可以运行以下内容:

git fetch --prune
git rev-parse HEAD~2000 > .git/shallow
git prune
git gc

浅层的入口就像嫁接一样。但是要注意不要同时使用浅层和浅层移植。至少,那里没有相同的条目,它将失败。

如果您仍然有一些指向较旧提交的旧引用(标记,分支,远程头),则它们不会被清理,也不会节省更多的磁盘空间。


对<GIT_DIR> / info / grafts的支持已被弃用,并将在以后的Git版本中删除。
丹尼

请考虑git replace改用。见stackoverflow.com/questions/6800692/...
乔尔AZEMAR

3

底垫头部/主可能发生该错误

remote: GitLab: You are not allowed to access some of the refs!
To git@giturl:main/xyz.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@giturl:main/xyz.git'

若要解决git仪表板中的此问题,应从“受保护的分支”中删除master分支

在此处输入图片说明

然后您可以运行此命令

git push -f origin master

要么

git rebase --onto temp $1 master

0

这里有太多的答案不是最新的,有些还没有完全解释后果。这是我使用最新的git 2.26整理历史记录的方法:

首先创建一个虚拟提交。该提交将作为截断的存储库中的第一个提交。您需要这样做,因为此提交将保存您保留的历史记录的所有基本文件。SHA是您要保留的提交的上一个提交的ID (在此示例中为8365366)。字符串“ Initial”将显示为第一次提交的提交消息。如果您使用的是Windows,请在Git Bash命令提示符下键入以下命令。

# 8365366 is id of parent commit after which you want to preserve history
echo 'Initial' | git commit-tree 8365366^{tree}

上面的命令将打印SHA,例如d10f7503bc1ec9d367da15b540887730db862023

现在只需键入:

# d10f750 is commit ID from previous command
git rebase --onto d10f750 8365366

这将首先把所有提交时的文件放入8365366虚拟提交中d10f750。然后它将 8365366之后的上方播放所有提交d10f750。最终,master分支指针将更新为上一次提交的回放。

现在,如果您想推送这些截断的仓库,那就去做吧git push -f

没什么要记住的(这些方法适用于其他方法以及该方法):不会传输标签。在保留提交ID和时间戳的同时,您将看到GitHub在总包标题中显示这些提交,例如Commits on XY date

幸运的是,可以将截断的历史记录保留为“存档”,以后您可以将回切后的存储库与归档存储库一起加入。为此,请参阅本指南


-3

您可以使用下面提到的jar [下载它]和命令来删除目录,文件以及与目录或文件有关的整个历史记录

bfg.jar文件: https

git clone --bare repo-url cd repo_dir java -jar bfg.jar --delete-folders folder_name git reflog expire --expire = now --all && git gc --prune = now --aggressive git push --mirror repo_url


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.