Git清理刪除歷史提交文件
既然來到這里,想必你認(rèn)為你的Git工程已經(jīng)處于臃腫狀態(tài)了拿愧,一些很久之前引入的大文件,而現(xiàn)在又不用這些大文件的情況下昏翰,請(qǐng)不要遲疑進(jìn)行清理操作。
常見的Git清理方式有兩種舍咖,一種是使用BFG
工具矩父,另外一種是使用git filter-branch
手動(dòng)處理。
注意:無論使用哪種方式排霉,都涉及破壞性操作,使用時(shí)應(yīng)嚴(yán)格謹(jǐn)慎民轴。在開始操作之前攻柠,請(qǐng)使用--mirror
參數(shù)克隆備份你的Git倉(cāng)庫(kù)。
使用BFG
的方式后裸,簡(jiǎn)單易操作瑰钮,使用方法可參考BFG Repo-Cleaner 。
本文主要介紹的是使用 git filter-branch
的方式進(jìn)行瘦身操作微驶。
為了模擬整個(gè)過程浪谴,讓我們先從一個(gè)從0到1的demo開始吧。
1.建立遠(yuǎn)程倉(cāng)庫(kù)
為了模擬團(tuán)隊(duì)協(xié)作因苹,需要一個(gè)遠(yuǎn)程倉(cāng)庫(kù)苟耻,可以選擇GitHub或碼云上建立倉(cāng)庫(kù),這里選擇碼云 扶檐,倉(cāng)庫(kù)名字為 gitthin
凶杖,地址為 git@gitee.com:coderhony/gitthin.git
。
2.克隆到本地
通過Terminal款筑,把剛才新建的代碼文件克隆到本地智蝠。
$ cd ~/Desktop/
$ mkdir gitthin && cd gitthin
$ git clone git@gitee.com:coderhony/gitthin.git
此時(shí)在Desktop的gitthin文件夾下,就有一份Git管理的倉(cāng)庫(kù)了奈梳。
3.基本操作
$ cd gitthin
$ echo -n "戰(zhàn)國(guó)是一個(gè)群國(guó)爭(zhēng)雄的時(shí)代杈湾,今天的故事就從戰(zhàn)國(guó)開始" > zhanguo.txt
$ git add .
$ git commit -m "創(chuàng)建了zhanguo.txt文件"
$ echo -n "戰(zhàn)國(guó)的開端是由趙魏韓三家分晉開始,從此戰(zhàn)國(guó)進(jìn)入七雄爭(zhēng)霸的時(shí)代" >> zhanguo.txt
$ git add .
$ git commit -m "戰(zhàn)國(guó)開端"
$ echo -n "這七個(gè)國(guó)家分別是:秦攘须、齊漆撞、楚、燕阻课、魏叫挟、趙、韓" >> zhanguo.txt
$ git add .
$ git commit -m "七個(gè)國(guó)家的名字"
$ echo -n "秦國(guó)地處西陲限煞,楚國(guó)位居南方抹恳,齊國(guó)傲居?xùn)|海,燕國(guó)地處北面署驻,魏國(guó)奋献、趙國(guó)健霹、韓國(guó)三國(guó)在中間" >> zhanguo.txt
$ git add .
$ git commit -m "七國(guó)的地理位置"
# 切換分支
$ git checkout -b qinguo
$ echo -n "戰(zhàn)國(guó)開始時(shí),秦國(guó)還比較貧弱瓶蚂,一直受東邊的魏國(guó)欺負(fù)糖埋,今天魏國(guó)奪占五城,明天秦國(guó)搶回三城窃这,一直處于這種狀態(tài)" > qinguo.txt
$ git add .
$ git commit -m "創(chuàng)建了qinguo.txt文件"
$ echo -n "當(dāng)時(shí)的魏國(guó)很強(qiáng)大瞳别,魏文侯時(shí)期,啟用李悝變法杭攻,使魏國(guó)經(jīng)濟(jì)富庶祟敛,后來有任用吳起訓(xùn)練魏武卒,一直不斷蠶食著秦國(guó)" >> qinguo.txt
$ git add .
$ git commit -m "秦國(guó)受魏國(guó)欺負(fù)"
$ echo -n "這種情況到秦孝公時(shí)開始有所好轉(zhuǎn)兆解,秦孝公啟動(dòng)商鞅馆铁,進(jìn)行變法,獎(jiǎng)勵(lì)農(nóng)耕锅睛,獎(jiǎng)勵(lì)軍功埠巨,經(jīng)過了二十年,秦國(guó)已不容小覷矣" >> qinguo.txt
$ git add .
$ git commit -m "秦孝公啟用商鞅變法"
4.添加大文件
生成一個(gè)有 1000000 行隨機(jī)字符串的大文本文件
# 大文件來襲
$ perl -le 'for (1..1000000) { print map { (0..9, "a".."z")[rand 36] } 1..80 }' > bigqin
$ git add .
$ git commit -m "商鞅變法现拒,秦國(guó)戰(zhàn)斗力直線上升"
查看大文件情況
$ du -sh .git
55M .git
可以看到.git的大小為 55M辣垒。
在該大文件的內(nèi)容后面,追加一些內(nèi)容:
$ perl -le 'print map { (0..9, "a".."z")[rand 36] } 1..80' >> bigqin
$ git add .
$ git commit -m "又過幾年具练,秦國(guó)的戰(zhàn)斗力又提高了很多"
查看此時(shí).git文件大小
$ du -sh .git
110M .git
可以看到乍构,此時(shí).git文件夾的大小變成了110M。
5.繼續(xù)常規(guī)提交
一百多年后扛点,秦國(guó)所向披靡哥遮,橫掃六合,一統(tǒng)天下陵究。
$ echo -n "秦國(guó)變法后一百年眠饮,國(guó)力不斷增強(qiáng),其他諸侯為之色變铜邮,王霸之業(yè)已不能滿足仪召,開始橫掃六合,一統(tǒng)天下松蒜,終于在嬴政時(shí)期得以實(shí)現(xiàn)" >> qinguo.txt
$ git add .
$ git commit -m "秦國(guó)一統(tǒng)天下"
把qinguo分支推送到遠(yuǎn)程:
$ git push origin qinguo
切換到master分支,并繼續(xù)提交代碼
$ git checkout master
$ echo -n "其他六國(guó)不斷被秦國(guó)攻打扔茅,聞之色變,但又沒有及時(shí)變法秸苗,尋求強(qiáng)大之道召娜,之后逐個(gè)被秦國(guó)所滅" >> zhanguo.txt
$ git add .
$ git commit -m "其他六國(guó)逐個(gè)被秦國(guó)所滅"
合并qinguo的內(nèi)容:
$ git merge qinguo
新建分支,并進(jìn)行一些提交
$ git checkout -b chuhan
$ echo -n "秦統(tǒng)一六國(guó)之后,苛捐雜稅不斷加重惊楼,大肆修建宮殿玖瘸,導(dǎo)致百姓痛苦不堪" > chuhan.txt
$ git add .
$ git commit -m "創(chuàng)建了chuhan.txt文件"
$ echo -n "隨著陳勝吳廣起義后秸讹,各國(guó)舊部開始起來反抗" >> chuhan.txt
$ git add .
$ git commit -m "各路人馬起義反秦"
$ echo -n "先入咸陽者稱王,劉邦先入了咸陽" >> chuhan.txt
$ git add .
$ git commit -m "劉邦入咸陽"
$ 推送到遠(yuǎn)程
$ git push origin chuhan
6.刪除大文件
此時(shí)雅倒,要?jiǎng)h除大文件bigqin
璃诀。
1.垃圾回收
先進(jìn)行垃圾回收,并壓縮一些文件
$ git gc --prune=now
Git最初向磁盤中存儲(chǔ)對(duì)象使用松散
的格式蔑匣,后續(xù)會(huì)將多個(gè)對(duì)象打包為一個(gè)二進(jìn)制的包文件
(packfile
)劣欢,以節(jié)省磁盤空間
.pack
文件存儲(chǔ)了對(duì)象的內(nèi)容
.idx
文件存儲(chǔ)了包文件
的偏移信息
,用于`索引具體的對(duì)象
打包對(duì)象時(shí)裁良,查找命名和大小相近的文件氧秘,保留文件不同版本之間的差異
(最新一版保存完整內(nèi)容
,訪問頻率最高)
2.查找大文件
使用git rev-list --objects —all
顯示所有commit及其所關(guān)聯(lián)的所有對(duì)象
:
$ git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -3 | awk '{print$1}')"
e9461c55c3b8807351909cf7bb46eb22a8df5533 README.md
b866711af76e7d7cc87d9828f8ddde8ea865d053 bigqin
8d675f2ad83219ad4ebe34fe4dcac3a7139002ea bigqin
verify-pack -v *.idx
:查看壓縮包內(nèi)容
3.刪除指定的大文件
$ git filter-branch --force --index-filter "git rm -rf --cached --ignore-unmatch bigqin" --prune-empty --tag-name-filter cat -- --all
Rewrite bf551acd0ca40a5671bcf2d4cad83a999b8baf52 (10/16) (0 seconds passed, remaining 0 predicted) rm 'bigqin'
Rewrite d1d31551ecb36d362fd1051152f1fc886e4df95c (11/16) (0 seconds passed, remaining 0 predicted) rm 'bigqin'
Rewrite da902adff9f3f890aa4c3304c5ac6d1e14f589da (12/16) (0 seconds passed, remaining 0 predicted) rm 'bigqin'
Rewrite c96090316c3600e6e190cb9f99cf14a58f6f09ca (13/16) (0 seconds passed, remaining 0 predicted) rm 'bigqin'
Rewrite 9d6c9194d76c5e0aa6c9119445681c9fbdf735a3 (14/16) (1 seconds passed, remaining 0 predicted) rm 'bigqin'
Rewrite b1e2f243a559547e3a0f5eaa18f6a31a35eaa57a (14/16) (1 seconds passed, remaining 0 predicted) rm 'bigqin'
Rewrite ac49fbb891648b73bbd31b4e82b56bbd71254384 (14/16) (1 seconds passed, remaining 0 predicted) rm 'bigqin'
.....
filter-branch
命令通過一個(gè)filter來重寫歷史提交趴久,這個(gè)filter針對(duì)指定的所有分支(rev-list
)運(yùn)行。
--index-filter
:過濾Git倉(cāng)庫(kù)的index搔确,該過濾命令作用于git rm -rf --cached --ignore-unmatch bigqin
彼棍。不checkout
到working directory
,只修改index
的文件膳算,速度快座硕。
--cached
會(huì)刪除index中的文件
--ignore-unmatch
:如果沒匹配到文件,不會(huì)報(bào)錯(cuò)涕蜂,會(huì)繼續(xù)執(zhí)行命令
最后一個(gè)參數(shù)file/directory
是要被刪除的文件的名字
--prune-empty
:指示git filter-branch
完全刪除所有的空commit华匾。
-–tag-name-filter
:將每個(gè)tag指向重寫后的commit。
cat
命令會(huì)在收到tag時(shí)返回tag名稱
–-
選項(xiàng)用來分割 rev-list 和 filter-branch 選項(xiàng)
--all
參數(shù)告訴Git我們需要重寫所有分支(或引用)机隙。
注意:git rm 這一行命令使用雙引號(hào)
"git rm -rf --cached --ignore-unmatch bigqin"
4.刪除緩存
移除本地倉(cāng)庫(kù)中指向舊提交的剩余refs蜘拉,git for-each-ref
會(huì)打印倉(cāng)庫(kù)中匹配refs/original
的所有refs,并使用delete
作為前綴有鹿,此命令通過管道傳送到 git update-ref
命令旭旭,該命令會(huì)移除所有指向舊commit的引用。
$ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
以下命令會(huì)使reflog到期葱跋,因?yàn)樗廊话鴮?duì)舊commit的引用持寄。使用--expire=now
參數(shù),確保它在目前為止到期了娱俺。如果沒有該參數(shù)稍味,只會(huì)移除超過90天的reflog。
$ git reflog expire --expire=now --all
現(xiàn)在本地倉(cāng)庫(kù)依然包含著所有舊commit的對(duì)象荠卷,但已經(jīng)沒有引用指向它們了模庐,這些對(duì)象需要被刪除掉。此時(shí)可以使用 git gc
命令僵朗,Git的垃圾回收器會(huì)刪除這些沒有引用指向的對(duì)象赖欣。
$ git gc --prune=now
gc
使用--prune
參數(shù)來清理特定時(shí)期的對(duì)象屑彻,默認(rèn)情況下為2周,指定now
將刪除所有這些對(duì)象而沒有時(shí)期限制顶吮。
$ du -sh .git
104K .git
此時(shí)社牲,.git文件的大小只有104k了。
7.提交重寫的歷史到遠(yuǎn)程
如果確認(rèn)所做的刪除大文件操作沒有問題悴了,就可以提交到遠(yuǎn)程倉(cāng)庫(kù)了搏恤,一旦提交,再也沒有辦法恢復(fù)到原來的狀態(tài)湃交,一定要小心謹(jǐn)慎熟空!一定要小心謹(jǐn)慎!一定要小心謹(jǐn)慎搞莺!
先進(jìn)行備份工作息罗,以免出現(xiàn)問題:
$ cd ~/Desktop/
$ mkdir gitthin_mirror && cd gitthin_mirror
$ git clone --mirror git@gitee.com:coderhony/gitthin.git
再回到剛才做的已經(jīng)瘦身的Git倉(cāng)庫(kù)
$ cd ~/Desktop/gitthin/gitthin
把已瘦身的倉(cāng)庫(kù)同步到遠(yuǎn)程倉(cāng)庫(kù),使用—mirror
參數(shù):
$ git push --mirror git@gitee.com:coderhony/gitthin.git
為了確保都已同步才沧,再執(zhí)行以下命令:
$ git push --all --force
Everything up-to-date
$ git push --tags --force
Everything up-to-date
8.更新其他的clone
在過濾存儲(chǔ)庫(kù)迈喉,并重寫提交歷史后,將更改強(qiáng)制推送到遠(yuǎn)程服務(wù)器之后∥略玻現(xiàn)在要更新該存儲(chǔ)庫(kù)的每一份clone挨摸,僅靠常用的pull
是無法做到這一點(diǎn)的。
第一步是從遠(yuǎn)程服務(wù)器獲取存儲(chǔ)庫(kù)岁歉,使用git reset
將存儲(chǔ)庫(kù)從 origin/master
切換到舊存儲(chǔ)庫(kù)狀態(tài)得运。
$ git fetch origin
$ git reset --hard origin/master
和上面的一樣,需要?jiǎng)h除舊提交锅移,清理本地倉(cāng)庫(kù)
$ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
$ git reflog expire --expire=now --all
$ git gc --prune=now