使用plumbing命令來深入理解git add和git commit的工作原理
前言: 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個步驟:
- 根據文件內容計算SHA-1值
- 將文件內容存儲到倉庫的數據庫中(.git/objects)
- 將文件內容註冊到.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
在底層實際上完成的步驟:
- 根據當前的工作樹生成一個tree對象(tree對象記錄了當前工作目錄的結構,保存有對當前版本的文件的引用),將tree對象保存到.git/objects下
- 由上述tree對象生成一個commit對象,(可選)並指明該commit對象的parent、message等信息
- 移動分支到新生成的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的工作原理