精讀《When You 「Git」 in Trouble- a Version Control Story》
本期精讀的文章是:When You 「Git」 in Trouble- a Version Control Story
1 引言
git 作為目前最流行的版本控制系統,它擁有眾多的用戶並管理著數量龐大的實際軟體項目。
本文主要通過一個實際的例子來描述,當項目(代碼)倉庫出現問題時如何使用 git 進行有效的維護,並分享一些 git 使用經驗以及分析 git 的內部實現機制。
2 內容概要
我們在管理項目代碼倉庫時,經常會碰到一些棘手的問題,比如:在使用 git 的過程中,有時會不小心丟失 commit 信息。如果實際場景中發生了類似的問題,該如何使用 git 找回丟失的 commit 呢?
首先,在 git 中想要找回丟失的 commit,就需要找出那些 commit 的 SHA,然後添加一個指向它的分支。
由於 git 會記錄下每次修改 HEAD 的操作,當執行提交或修改分支的命令時 reflog 就會更新(執行 git update-ref 命令也可以更新 reflog),因此可以執行 git reflog 命令來查看當前的狀態。
$ git reflognn1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEADnab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEADn
執行 git log -g 命令可以查看更加詳細 reflog 的日誌。
$ git log -gnncommit 1a410efbd13591db07496601ebc7a059dd55cfe9nReflog: HEAD@{0}nReflog message: updating HEADnn third commitnncommit ab1afef80fac8e34258ff41fc1b867c702daa24bnReflog: HEAD@{1}nReflog message: updating HEADnn modified repo a bitn
確定丟失的 commit 後,就可以在這個 commit 上創建一個新分支將其恢復過來。比如,在 commit (ab1afef) 上創建 new-branch 分支,即可找回丟失的 commit 數據。
$ git branch new-branch ab1afefnn$ git log --pretty=oneline new-branchnnab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bitn484a59275031909e19aadb7c92262719cfcdf19a added repo.rbn1a410efbd13591db07496601ebc7a059dd55cfe9 third commitncac0cab538b970a37ea1e769cbbde608743bc96d second commitnfdf4fc3344e67ab068f836878b6c4951e3b15f3d first commitn
如果引起 commit 丟失的原因並沒有記錄在 reflog 中,即沒有在 .git/logs/ 中(因為 reflog 數據是保存在 .git/logs/ 目錄下的),這樣就會導致丟失的 commit 不會被任何東西引用。這種情況應該如何恢復 commit 數據呢?
這裡可以執行 git fsck --full 命令,該命令會檢查倉庫的數據完整性,會顯示所有未被其他對象引用的所有對象。
$ git fsck --fullnndangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4ndangling commit ab1afef80fac8e34258ff41fc1b867c702daa24bndangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9ndangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293n
這樣就可以從 dangling commit 中找到丟失的 commit 信息,確定丟失的 commit 後,就可以在這個 commit 上創建一個新分支將其恢復過來。
至此,文章分享了一個在實際工作中維護項目倉庫時經常會遇到的難題的解決思路和方法。
3 精讀
上述文章中執行 git fsck 命令時出現了 blob、tree、commit 等關鍵詞,雖然我們經常會看到此類的關鍵詞,但可能並不清楚其真正含義,因此,下面內容將從 blob、tree、commit 這三個內部對象去深入分析 git 內部數據結構管理的機制。
git 的版本控制實際就是對文件進行管理和控制,其管理方法就是為每個文件生成(key, object)的結構,並利用 sha-1 加密演算法,對每一個文件生成唯一的字元序列作為 hash_key,且文件改變就會生成新的 (key, object)。
執行 git init 初始化一個本地倉庫,查看隱藏目錄 .git 中的目錄結構。其中,objects 目錄下只有 info 和 pack 兩個空文件夾,沒有任何其他文件被記錄下來。
blob 對象
在當前項目倉庫中添加文件 file1.js,執行 git hash-object file1.js,生成一個 40 字元長度的 hash-key 序列:08219db9b0969fa29cf16fd04df4a63964da0b69。
執行 git add file_1.txt,objects 中多了一個 08 對象。
其實是 40位 hash-key 的前兩位作為目錄名,後 38位 作為文件名,這個對象裡面的內容就是 file1.js 的內容,可以查看該對象的內容和類型。
執行 git cat-file -p [hash-key] 可以查看已經存在的 object 對象內容;
執行 git cat-file -t [hash-key] 可以查看已經存在的 object 對象類型;
git object 有四種類型,第一種類型 blob,用來儲存文件內容。
tree 對象
blob 對象用於存儲對應文件的內容,tree 對象可以理解為存儲目錄,其樹節點信息包含:文件名,hash-key,文件類型、許可權等,這樣就可以組織整個需要控制文件的結構。
在當前項目倉庫中添加文件夾 dir1,在 dir1 中添加文件 file2.js。執行 git add 將內容加入到暫存區,執行 git hash-object dir1/file2.js 查看生成的 hash-key:30d67d4672d5c05833b7192cc77a79eaafb5c7ad。
查看 objects 目錄,只新增了一個 30 目錄,即 30d67d4672d5c05833b7192cc77a79eaafb5c7ad 對應的 file2.js 文件。
說明 file2.js 文件生成了 hash-key,但 dir1 目錄並沒有生成 tree 對象,tree 對象是在 commit 的過程中生成的,其生成會根據 .git 目錄下的 index 文件的內容來創建。git add 的操作就是將文件的信息保存到 index 文件中,在 commit 時,根據 index 的內容來生成 tree 對象。
執行 git ls-files --stage 命令,可以看到 index 中包含了創建 tree 對象的信息:文件類型(100644)、hash-key、目錄結構以及文件名。
進行一次 commit,生成 commit 對象和 tree 對象。其中,master^{tree} 表示 master 分支所指向的 tree 對象。
該 tree 對象是當前對應的目錄,目錄下有一個名為 dir1 的 tree 對象和 file1.js 的 blob 對象。
查看 dir1 對應的 tree 對象的內容,該 tree 對象只包含 file2.js 的信息。
當前項目的 git 倉庫內部結構圖如下:
commit 對象
只有在執行 git commit 時,才會根據 index 記錄的內容生成 tree 對象,則 commit 對象中會有兩個內容:
- 代表項目目錄的 tree 對象的 key
- 上一個 commit 的 key
項目中 objects 目錄的內容:
目前每個目錄里有一個對象,共5個對象,之前的總體 tree 圖只包含了4個對象,執行 git log 查看 commit 記錄。
objects 中 65 對應的文件夾裡面的文件就是 commit 對象,它指向項目目錄 tree 以及上一次的 commit,由於是第一個 commit,因此不存在上一個 commit。
commit 對象內容指向項目目錄 tree,所以能獲取到一個 commit,可以得到當前完整的文件狀況,objects 結構圖如下:
再新增一個目錄 dir2,該目錄下新增文件 file3.js,執行 git add 和 git commit 後查看新的 commit 信息。
新的 commit 指向了上一個 commit,還指向了一個新生成的 tree,該 tree 表示了新的項目目錄情況,查看該 tree 的內容。
這個 tree 包含了當前的文件目錄和內容,目前對象完整的圖如下:
可以看到 commit 對象指向了工作目錄 tree,因此只要切換 commit,就可以隨意切換對應的版本內容,當前 .git/objects 目錄內容如下:
git 版本控制住要就是圍繞這三類內部對象展開,分別為 blob(記錄文件內容),tree(目錄結構),commit(工作目錄 tree 以及提交歷史)。
4 總結
本文從一個實際問題即如何使用 git 維護丟失的 commit 入手,並給出相應的解決思路和方案,以及通過git 內部三種對象來分析其內部工作機制,希望能過解決讀者們對 git 存在困惑的地方,同時也希望讀者們積极參与每周精度的討論,各抒己見,分享自身在實際工作中遇到的問題及其解決思路。
討論地址是:精讀《When You 「Git」 in Trouble- a Version Control Story》 · Issue #49 · dt-fe/weekly
如果你想參與討論,請點擊這裡,每周都有新的主題,每周五發布。
推薦閱讀:
※喜歡用 Git 做的一些小事
※git圖解(3):分支操作
※github for windows安裝失敗了,怎麼辦?
TAG:Git |