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