Git内部原理之深入解析维护与数据恢复
生活随笔
收集整理的這篇文章主要介紹了
Git内部原理之深入解析维护与数据恢复
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
一、維護(hù)
- Git 會(huì)不定時(shí)地自動(dòng)運(yùn)行一個(gè)叫做 “auto gc” 的命令,大多數(shù)時(shí)候,這個(gè)命令并不會(huì)產(chǎn)生效果。然而,如果有太多松散對(duì)象(不在包文件中的對(duì)象)或者太多包文件,Git 會(huì)運(yùn)行一個(gè)完整的 git gc 命令。“gc” 代表垃圾回收,這個(gè)命令會(huì)做以下事情:收集所有松散對(duì)象并將它們放置到包文件中,將多個(gè)包文件合并為一個(gè)大的包文件,移除與任何提交都不相關(guān)的陳舊對(duì)象。
- 可以像下面一樣手動(dòng)執(zhí)行自動(dòng)垃圾回收:
- 就像上面提到的,這個(gè)命令通常并不會(huì)產(chǎn)生效果,大約需要 7000 個(gè)以上的松散對(duì)象或超過(guò) 50 個(gè)的包文件才能讓 Git 啟動(dòng)一次真正的 gc 命令,可以通過(guò)修改 gc.auto 與 gc.autopacklimit 的設(shè)置來(lái)改動(dòng)這些數(shù)值。
- gc 將會(huì)做的另一件事是打包你的引用到一個(gè)單獨(dú)的文件,假設(shè)倉(cāng)庫(kù)包含以下分支與標(biāo)簽:
- 如果執(zhí)行了 git gc 命令,refs 目錄中將不會(huì)再有這些文件。為了保證效率 Git 會(huì)將它們移動(dòng)到名為 .git/packed-refs 的文件中,就像這樣:
- 如果更新了引用,Git 并不會(huì)修改這個(gè)文件,而是向 refs/heads 創(chuàng)建一個(gè)新的文件,為了獲得指定引用的正確 SHA-1 值,Git 會(huì)首先在 refs 目錄中查找指定的引用,然后再到 packed-refs 文件中查找。所以,如果在 refs 目錄中找不到一個(gè)引用,那么它或許在 packed-refs 文件中。
- 注意:這個(gè)文件的最后一行,它會(huì)以 ^ 開(kāi)頭,這個(gè)符號(hào)表示它上一行的標(biāo)簽是附注標(biāo)簽,^ 所在的那一行是附注標(biāo)簽指向的那個(gè)提交。
二、數(shù)據(jù)恢復(fù)
- 在使用 Git 的時(shí)候,可能會(huì)出現(xiàn)意外丟失一次提交的情況,通常這是因?yàn)閺?qiáng)制刪除了正在工作的分支,但是最后卻發(fā)現(xiàn)還需要這個(gè)分支,亦或者硬重置了一個(gè)分支,放棄了想要的提交,如果這些事情已經(jīng)發(fā)生,該如何找回相應(yīng)的提交呢?
- 如下所示,將硬重置測(cè)試倉(cāng)庫(kù)中的 master 分支到一個(gè)舊的提交,以此來(lái)恢復(fù)丟失的提交。首先,來(lái)看看倉(cāng)庫(kù)現(xiàn)在在什么地方:
- 現(xiàn)在,將 master 分支硬重置到第三次提交:
- 現(xiàn)在頂部的兩個(gè)提交已經(jīng)丟失了,沒(méi)有分支指向這些提交,需要找出最后一次提交的 SHA-1 然后增加一個(gè)指向它的分支,竅門就是找到最后一次的提交的 SHA-1,但是如果記不起來(lái)了,怎么辦呢?
- 最方便,也是最常用的方法,是使用一個(gè)名叫 git reflog 的工具,當(dāng)正在工作時(shí),Git 會(huì)默默地記錄每一次改變 HEAD 時(shí)它的值,每一次提交或改變分支,引用日志都會(huì)被更新,引用日志(reflog)也可以通過(guò) git update-ref 命令更新,我們?cè)?Git 引用 有提到使用這個(gè)命令而不是是直接將 SHA-1 的值寫(xiě)入引用文件中的原因,可以在任何時(shí)候通過(guò)執(zhí)行 git reflog 命令來(lái)了解曾經(jīng)做過(guò)什么:
- 這里可以看到我們已經(jīng)檢出的兩次提交,然而并沒(méi)有足夠多的信息,為了使顯示的信息更加有用,可以執(zhí)行 git log -g,這個(gè)命令會(huì)以標(biāo)準(zhǔn)日志的格式輸出引用日志:
- 看起來(lái)下面的那個(gè)就是丟失的提交,可以通過(guò)創(chuàng)建一個(gè)新的分支指向這個(gè)提交來(lái)恢復(fù)它。例如,可以創(chuàng)建一個(gè)名為 recover-branch 的分支指向這個(gè)提交(ab1afef):
- 不錯(cuò),現(xiàn)在有一個(gè)名為 recover-branch 的分支是 master 分支曾經(jīng)指向的地方,再一次使得前兩次提交可到達(dá)了。接下來(lái),假設(shè)丟失的提交因?yàn)槟承┰虿辉谝萌罩局?#xff0c;那么我們可以通過(guò)移除 recover-branch 分支并刪除引用日志來(lái)模擬這種情況,現(xiàn)在前兩次提交又不被任何分支指向了:
- 由于引用日志數(shù)據(jù)存放在 .git/logs/ 目錄中,現(xiàn)在已經(jīng)沒(méi)有引用日志了,這時(shí)該如何恢復(fù)那次提交? 一種方式是使用 git fsck 實(shí)用工具,將會(huì)檢查數(shù)據(jù)庫(kù)的完整性,如果使用一個(gè) --full 選項(xiàng)運(yùn)行它,它會(huì)顯示出所有沒(méi)有被其他對(duì)象指向的對(duì)象:
- 本例中,可以在 “dangling commit” 后看到丟失的提交,現(xiàn)在可以用和之前相同的方法恢復(fù)這個(gè)提交,也就是添加一個(gè)指向這個(gè)提交的分支。
三、移除對(duì)象
- Git 有很多很棒的功能,但是其中一個(gè)特性會(huì)導(dǎo)致問(wèn)題,git clone 會(huì)下載整個(gè)項(xiàng)目的歷史,包括每一個(gè)文件的每一個(gè)版本。如果所有的東西都是源代碼那么這很好,因?yàn)?Git 被高度優(yōu)化來(lái)有效地存儲(chǔ)這種數(shù)據(jù)。然而,如果某個(gè)人在之前向項(xiàng)目添加了一個(gè)大小特別大的文件,即使將這個(gè)文件從項(xiàng)目中移除了,每次克隆還是都要強(qiáng)制的下載這個(gè)大文件,之所以會(huì)產(chǎn)生這個(gè)問(wèn)題,是因?yàn)檫@個(gè)文件在歷史中是存在的,它會(huì)永遠(yuǎn)在那里。
- 當(dāng)遷移 Subversion 或 Perforce 倉(cāng)庫(kù)到 Git 的時(shí)候,這會(huì)是一個(gè)嚴(yán)重的問(wèn)題,因?yàn)檫@些版本控制系統(tǒng)并不下載所有的歷史文件,所以這種文件所帶來(lái)的問(wèn)題比較少。如果從其他的版本控制系統(tǒng)遷移到 Git 時(shí)發(fā)現(xiàn)倉(cāng)庫(kù)比預(yù)期的大得多,那么就需要找到并移除這些大文件。
- 警告:這個(gè)操作對(duì)提交歷史的修改是破壞性的,它會(huì)從必須修改或移除一個(gè)大文件引用最早的樹(shù)對(duì)象開(kāi)始重寫(xiě)每一次提交,如果在導(dǎo)入倉(cāng)庫(kù)后,在任何人開(kāi)始基于這些提交工作前執(zhí)行這個(gè)操作,那么將不會(huì)有任何問(wèn)題。否則, 必須通知所有的貢獻(xiàn)者他們需要將他們的成果變基到新提交上。
- 為了演示,將添加一個(gè)大文件到測(cè)試倉(cāng)庫(kù)中,并在下一次提交中刪除它,現(xiàn)在我們需要找到它,并將它從倉(cāng)庫(kù)中永久刪除。首先,添加一個(gè)大文件到倉(cāng)庫(kù)中:
- 其實(shí)這個(gè)項(xiàng)目并不需要這個(gè)巨大的壓縮文件,現(xiàn)在將它移除:
- 執(zhí)行 gc 來(lái)查看數(shù)據(jù)庫(kù)占用了多少空間:
- 也可以執(zhí)行 count-objects 命令來(lái)快速的查看占用空間大小:
- size-pack 的數(shù)值指的是包文件以 KB 為單位計(jì)算的大小,所以大約占用了 5MB 的空間。在最后一次提交前,使用了不到 2KB,顯然,從之前的提交中移除文件并不能從歷史中移除它。每一次有人克隆這個(gè)倉(cāng)庫(kù)時(shí),他們將必須克隆所有的 5MB 來(lái)獲得這個(gè)微型項(xiàng)目,只因?yàn)橐馔獾靥砑恿艘粋€(gè)大文件,現(xiàn)在來(lái)徹底的移除這個(gè)文件。
- 首先必須找到它,在本例中,已經(jīng)知道是哪個(gè)文件了,但是如果不知道,該如何找出哪個(gè)文件或哪些文件占用了如此多的空間? 如果執(zhí)行 git gc 命令,所有的對(duì)象將被放入一個(gè)包文件中,可以通過(guò)運(yùn)行 git verify-pack 命令,然后對(duì)輸出內(nèi)容的第三列(即文件大小)進(jìn)行排序,從而找出這個(gè)大文件,也可以將這個(gè)命令的執(zhí)行結(jié)果通過(guò)管道傳送給 tail 命令,因?yàn)橹恍枰业搅性谧詈蟮膸讉€(gè)大對(duì)象:
- 可以看到這個(gè)大對(duì)象出現(xiàn)在返回結(jié)果的最底部占用 5MB 空間。為了找出具體是哪個(gè)文件,可以使用 rev-list 命令,如果傳遞 --objects 參數(shù)給 rev-list 命令,它就會(huì)列出所有提交的 SHA-1、數(shù)據(jù)對(duì)象的 SHA-1 和與它們相關(guān)聯(lián)的文件路徑。可以使用以下命令來(lái)找出數(shù)據(jù)對(duì)象的名字:
- 現(xiàn)在,只需要從過(guò)去所有的樹(shù)中移除這個(gè)文件。使用以下命令可以輕松地查看哪些提交對(duì)這個(gè)文件產(chǎn)生改動(dòng):
- 必須重寫(xiě) 7b30847 提交之后的所有提交來(lái)從 Git 歷史中完全移除這個(gè)文件。為了執(zhí)行這個(gè)操作,要使用 filter-branch 命令:
- –index-filter 選項(xiàng)類似于在Git之深入解析如何重寫(xiě)提交歷史 中提到的的 --tree-filter 選項(xiàng),不過(guò)這個(gè)選項(xiàng)并不會(huì)讓命令將修改在硬盤上檢出的文件,而只是修改在暫存區(qū)或索引中的文件。
- 必須使用 git rm --cached 命令來(lái)移除文件,而不是通過(guò)類似 rm file 的命令,因?yàn)樾枰獜乃饕幸瞥?#xff0c;而不是磁盤中。還有一個(gè)原因是速度,Git 在運(yùn)行過(guò)濾器時(shí),并不會(huì)檢出每個(gè)修訂版本到磁盤中,所以這個(gè)過(guò)程會(huì)非常快。如果愿意的話,也可以通過(guò) --tree-filter 選項(xiàng)來(lái)完成同樣的任務(wù),git rm 命令的 --ignore-unmatch 選項(xiàng)告訴命令:如果嘗試刪除的模式不存在時(shí),不提示錯(cuò)誤。最后,使用 filter-branch 選項(xiàng)來(lái)重寫(xiě)自 7b30847 提交以來(lái)的歷史,也就是這個(gè)問(wèn)題產(chǎn)生的地方。否則,這個(gè)命令會(huì)從最舊的提交開(kāi)始,這將會(huì)花費(fèi)許多不必要的時(shí)間。
- 歷史中將不再包含對(duì)那個(gè)文件的引用,不過(guò),引用日志和你在 .git/refs/original 通過(guò) filter-branch 選項(xiàng)添加的新引用中還存有對(duì)這個(gè)文件的引用,所以必須移除它們?nèi)缓笾匦麓虬鼣?shù)據(jù)庫(kù)。在重新打包前需要移除任何包含指向那些舊提交的指針的文件:
- 來(lái)看看省了多少空間:
- 打包的倉(cāng)庫(kù)大小下降到了 8K,比 5MB 好很多,可以從 size 的值看出,這個(gè)大文件還在松散對(duì)象中,并沒(méi)有消失;但是它不會(huì)在推送或接下來(lái)的克隆中出現(xiàn),這才是最重要的。如果真的想要?jiǎng)h除它,可以通過(guò)有 --expire 選項(xiàng)的 git prune 命令來(lái)完全地移除那個(gè)對(duì)象:
總結(jié)
以上是生活随笔為你收集整理的Git内部原理之深入解析维护与数据恢复的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Git内部原理之深入解析传输协议
- 下一篇: Git之深入解析如何在应用中嵌入Git