1. 程式人生 > >使用plumbing命令來深入理解git add和git commit的工作原理

使用plumbing命令來深入理解git add和git commit的工作原理

clean 結果 write 文件的 repos 倉庫 head 根據 acc

前言: plumbing命令 和 porcelain命令

git中的命令分為plumbing命令和porcelain命令:

  • porcelain命令就是我們常用的git add,git commit等命令
  • plumbing命令可以理解為更底層的命令,實際上一個porcelain命令可以由若幹個plumbing命令完成(見下文),plumbing命令可以幫助我們了解git底層的工作原理

閱讀本文還需要了解.git目錄的結構功能,以及git中的對象(commit對象、tree對象、blob對象等等)等概念,請自行參考其他文檔。

1. git add

git add file在底層實際上完成了3個步驟:

  1. 根據文件內容計算SHA-1值
  2. 將文件內容存儲到倉庫的數據庫中(.git/objects)
  3. 將文件內容註冊到.git/index文件中

下面用plumbing命令完成git add的功能:

------------------------------------------------------------
~ ? git init demo                                                   
Initialized empty Git repository in /home/lvhao/demo/.git/
------------------------------------------------------------
~ ? cd demo                                                         
------------------------------------------------------------
~/demo(master) ? echo "nihao" >> nihao.txt                          
------------------------------------------------------------
~/demo(master*) ? git status                                        
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    nihao.txt

nothing added to commit but untracked files present (use "git add" to track)
~/demo(master*) ? git hash-object -w nihao.txt     # 對應於步驟1和2,-w表示寫         
ba9c1ad3d3b761e84cd8f9e9a18404f6f2552fcf

這時候查看.git/objects目錄:

~/demo(master*) ? tree .git/objects                               
.git/objects
├── ba
│   └── 9c1ad3d3b761e84cd8f9e9a18404f6f2552fcf
├── info
└── pack

3 directories, 1 file
~/demo(master*) ? git update-index --add --info-only nihao.txt # 對應於步驟3(關於兩個參數請參見git help update-index)

上面的update-index,就是更新.git/index文件,有關該文件的詳細格式請參考https://stackoverflow.com/questions/4084921/what-does-the-git-index-contain-exactly。

該index文件主要包含了存在於暫存區(即index)中文件條目,可以通過git ls-files --stage來查看該文件的主要內容:

~/demo(master*) ? git ls-files --stage                             
100644 ba9c1ad3d3b761e84cd8f9e9a18404f6f2552fcf 0   nihao.txt

這時候,nihao.txt已經存在於暫存區(即index),所以說上面的三個步驟相當於git add nihao.txt

~/demo(master*) ? git status                             
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   nihao.txt

總結:

porcelain命令:

git add file

等同於plumbing命令:

git hash-object -w file
git update-index --add --info-only file

2. git commit

commit所做的工作實際上是對當前的工作樹(working tree)拍一個”快照“然後保存起來,git commit在底層實際上完成的步驟:

  1. 根據當前的工作樹生成一個tree對象(tree對象記錄了當前工作目錄的結構,保存有對當前版本的文件的引用),將tree對象保存到.git/objects下
  2. 由上述tree對象生成一個commit對象,(可選)並指明該commit對象的parent、message等信息
  3. 移動分支到新生成的commit對象
# 繼續上面的代碼
~/demo(master*) ? git write-tree   # 即步驟1                            
8e335a3e0ffa15ff97acc7f4d97e03d63612ec7a

讓我們查看一下這個tree對象,可見它保存了對當前工作目錄下文件/目錄的引用:

~/demo(master*) ? git cat-file -p 8e335a                          
100644 blob ba9c1ad3d3b761e84cd8f9e9a18404f6f2552fcf    nihao.txt
~/demo(master*) ? git commit-tree -m "Initial Commit" 8e335a    # 即步驟2
1f1fbcf8ff46d8d2548372c38cf3f1eabf8bfbc8 # 新生成的commit的SHA-1

這時候我們查看狀態,還是待commit狀態,這是因為commit-tree僅僅是生成了一個新的commit對象,並不會導致HEAD及分支的移動:

~/demo(master*) ? git status                                    
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   nihao.txt

為了利用當前的分支,我們需要修改分支的引用,git update-ref refs/heads/分支名 commit-hash命令來更新分支的位置到剛剛創建的commit:

~/demo(master*) ? git update-ref refs/heads/master 1f1fbc  # 即步驟3         

由於.git/HEAD默認就是指向master分支,所以得到如下結果:

~/demo(master) ? git status                                    
On branch master
nothing to commit, working tree clean

查看一下提交日誌:

~/demo(master) ? git log --graph --oneline                        
* 1f1fbcf (HEAD -> master) Initial Commit

總結:

porcelain命令的:

git commit -m "xxx" file

等同於plumbing命令的:

git write-tree # 得到tree的hash: tree-hash
git commit-tree -m "xxx" (-p parent-hash) tree-hash # 得到commit的hash,commit-hash
git update-ref refs/heads/分支名 commit-hash

使用plumbing命令來深入理解git add和git commit的工作原理