10分鐘幫你搞懂Git
儘管每天你都會用到Git,常用的命令可能不到5個,但你可能現在還搞不懂它的工作原理。為什麼Git可以管理版本?基本命令git add和git commit到底在幹什麼?
在這篇文章中,我將用一個例子來解釋Git的執行過程,幫助你理解Git的工作原理。
1. 初始化
讓我們建立一個專案的目錄,然後進入該目錄。
$ mkdir git-demo-project
$cdgit-demo-project
如果想管理專案的版本,那麼我們應該做的第一件事情就是通過git init初始化。
$ git init
git init只做了一件事情,那就是在專案的根目錄下建立.git子目錄來儲存版本資訊。
$ ls .gitbranches/configdescriptionHEADhooks/info/objects/refs/
上述命令顯示了.git子目錄中的內容。
2. 儲存物件
接下來讓我們建立一個新的空檔案test.txt。
$ touch test.txt
然後把這個檔案新增到Git程式碼庫中,這一步將建立test.txt現有內容的一個副本。
$githash-object -w test.txte6
9de29bb2d1d6434b8b29ae775ad8c2e48c5391
在上述程式碼中,git hash-object命令將test.txt現有的內容壓縮成二進位制檔案,並儲存到Git中。該壓縮檔案叫做Git物件,儲存在.git/objects目錄中。
我們可以通過這個命令根據物件的檔名獲取當前內容,並計算成SHA1 雜湊(長度為40的字串)。讓我們看看下列新生成的Git物件檔案。
$ ls -R .git/objects
.git/objects/e6:
9de29bb2d1d6434b8b29ae775ad8c2e48c5391
如上述程式碼所示,.git/objects目錄下又多出了一個子目錄,而且這個子目錄名是上述雜湊值的前兩個字元。在這個子目錄下有一個檔案,檔名是上述雜湊值中其餘的38個字元。
讓我們再來看看檔案內容。
$ cat .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
上述程式碼輸出的檔案內容是一些二進位制字元。你可能會問既然test.txt是空檔案,又怎麼會有這些內容呢?這是因為該二進位制物件中還儲存了一些後設資料。
如果你想看看該檔案原始的文字內容,那麼應該使用git cat-file。
$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
因為原檔案為空,所以上述命令什麼都沒有顯示。現在我們往test.txt檔案中寫點東西。
$echo'hello world'> test.txt
這個檔案的內容已經改變了,所以你需要再次把它儲存為Git物件。
$githash-object -w test.txt
3b18e512dba79e4c8300dd08aeb37f8e728b8dad
如上述程式碼所示,test.txt的雜湊值已經隨著檔案內容的改變而發生了變化。同時還生成了新檔案.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad。現在你可以看到這個檔案的內容了。
$ git cat-file -p3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world
3. 更新索引
當檔案儲存成二進位制物件以後,你需要告訴Git哪個檔案發生了變化。Git會在一個名叫“索引”(或階段)的區域記錄所有發生了變化的檔案。然後等到所有的變更都結束後,將索引中的這些檔案一起寫入正式的版本歷史記錄中。
$ gitupdate-index--add --cacheinfo 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
上述命令記錄了檔名test.txt、二進位制物件名(雜湊值)以及索引中檔案的訪問許可權。
git ls-files命令可以顯示索引中當前的內容。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt
上述程式碼顯示索引中只有一個test.txt檔案,還顯示了該檔案的二進位制物件名和訪問該檔案的許可權。如果你知道該二進位制物件名,就可以檢視.git/objects子目錄中該檔案的內容。
git status命令可以輸出更多可讀的結果。
$ git status
Changes to submit:
The new file: test.txt
上述程式碼顯示索引中只有一個新檔案test.txt,該檔案正在等候寫入版本的歷史記錄中。
4. git add命令
針對每個檔案執行上述兩個步驟非常繁瑣。所以Git提供了git add命令來簡化這些操作。
$ git add --all
上述命令相當於針對當前專案中所有發生了變化的檔案執行上述兩個步驟。
5. 提交(Commit)
索引儲存發生了變化的檔案資訊。等到修改完成,所有這些資訊都會被寫入版本的歷史記錄中,這相當於生成一個當前專案的快照。
專案的歷史記錄由不同時間點的專案快照組成。Git可以將專案恢復成任何一個快照。在Git中“快照”有一個專門的術語,即“提交”(commit)。所以生成快照也可以稱之為完成提交。
下列所有“快照”的引用指的都是提交。
6. 完成提交
首先,我們需要設定使用者名稱和郵件地址。在你儲存快照的時候,Git需要記錄是誰執行的提交。
$ git config user.name"username"
$ git config user.email"Email address"
接下來,儲存現有的目錄結構。在本文的前面我們討論了儲存物件只會儲存一個檔案,並不會記錄檔案之間的目錄結構。
git write-tree命令可以根據當前目錄結構生成一個Git物件。
$ gitwrite-tree
c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
在上述程式碼中,目錄結構儲存成了二進位制物件,而物件的名字是雜湊值。它也儲存在.git/objects目錄中。
讓我們來看看該檔案的內容。
$ git cat-file -p c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
100644blob3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
可以看到,當前目錄中只有一個檔案test.txt。
這個所謂的快照就是儲存當前的目錄結構,以及每個檔案相對應的二進位制物件。之前的操作已經儲存了檔案結構,所以現在你需要把這個目錄結構和一些後設資料一起寫入版本的歷史記錄中。
git commit-tree可以將目錄樹物件寫入到版本的歷史記錄中。
$ echo "first commit" | git commit-tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
在上述程式碼中,在提交時你需要提供提交的描述,而且你可以通過echo "first commit"提供提交描述。git commit-tree命令會根據後設資料以及目錄樹生成一個Git物件。現在,讓我們來看看該物件的內容。
$ git cat-file -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
author jam1538889134+0800
committer jam1538889134+0800
first commit
在上述程式碼中,第一行輸出是對應於該快照的目錄樹物件,而第二行和第三行是有關作者和提交者的資訊,最後一行內容是提交的描述。
通過git log命令我們還可以檢視某個快照的資訊。
$ git log--stat c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
commitc9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: jam
Date: SunOct713:12:142018+0800
firstcommit
test.txt |1+
1filechanged,1insertion(+)
7. git commit命令
Git提供了git commit來簡化上述提交操作。在儲存到索引後,你只需要執行git commit命令,就可以同時提交目錄結構和描述,並生成快照。
$ git commit -m"first commit"
另外,還有兩個命令也非常實用。
通過git checkout命令,我們可以切換到某個快照。
$ git checkout c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
通過git show命令,我們可以顯示某個快照的所有程式碼變更。
$ git show c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
8. 分支(branch)
然而,如果你使用git log命令來檢視整個版本的歷史記錄時,卻無法看到剛剛生成的快照。
$git log
上述命令輸出為空。這是為什麼?這個快照剛剛不是寫入到歷史記錄中了嗎?
真相是:git log命令只可以顯示當前分支上的變化。儘管我們已經提交了這個快照,但是還沒有記錄這個快照屬於哪個分支。
分支是快照的指標,分支的名字就是該指標的名字。雖然雜湊值不可讀,但是分支允許使用者給快照起別名。另外,分支還會自動更新,如果當前分支是一個新的快照,那麼這個指標會自動指向它。例如,主分支(master branch)有一個名為master的指標指向主分支當前的快照。
使用者可以為任何快照建立新指標。例如,如果你想建立一個新的fix-typo分支,那麼只需建立一個名為fix-typo的指標,並指向一個快照。因此,在Git中建立一個新分支非常容易,而且開銷非常低。
Git有一個特殊的指標HEAD,它始終指向當前分支中最新的那個快照。另外,Git還提供了快捷方式。例如,HEAD^指向HEAD之前的快照(父節點),而HEAD~6指向HEAD之前的第六個快照。
每個分支的指標都是一個文字檔案,儲存在.git/refs/heads/目錄中。檔案的內容是它指向的快照的二進位制檔名(雜湊值)。
9. 更新分支
下面我們將演示如何更新分支。首先,修改test.txt。
$ echo "hello world again" > test.txt
然後儲存二進位制物件。
$ git hash-object -w test.txt
c90c5155ccd6661aed956510f5bd57828eec9ddb
接下來,將該物件寫入索引,並儲存目錄結構。
$ git update-index test.txt
$ git write-tree
1552fd52bc14497c11313aa91547255c95728f37
最後,提交目錄結構,並生成一個快照。
$ echo "second commit" | git commit-tree 1552fd52bc14497c11313aa91547255c95728f37 -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
在上述程式碼中,我們可以通過git commit-tree命令的引數-p來指定父節點,即以哪個快照為基礎。
下面我們把快照的雜湊值寫入到.git/refs/heads/master檔案中,並讓master指標指向該快照。
$ echo 785f188674ef3c6ddc5b516307884e1d551f53ca > .git/refs/heads/master
現在,通過git log命令你可以看到兩個快照了。
$ git log
kk
commit 785f188674ef3c6ddc5b516307884e1d551f53ca (HEAD -> master)
Author: jam
Date: Sun Oct 7 13:38:00 2018 +0800
second commit
commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: jam
Date: Sun Oct 7 13:12:14 2018 +0800
first commit
git log命令的執行過程大致如下:
找到HEAD指標對應的分支。在上述示例中為master。
找到master指標指向的快照。在上述示例中為785f188674ef3c6ddc5b516307884e1d551f53ca。
找到父節點(即前一個快照)c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa。
等等,最後顯示當前分支中所有的快照。
另外,上述我們曾提到分支指標是動態的,下述三個命令會自動覆蓋分支指標。
Git commit:當前分支的指標將移動到新建立的快照上。
Git pull:在當前分支和遠端分支合併後,指標會指向新建立的快照。
Git reset [commit_sha]:當前分支的指標將被複位到某個指定的快照上。
-----------------------end--------------------------
相關文章
- 好文推薦,20 分鐘教你搞懂 Git!Git
- 一文幫你搞懂 Android 檔案描述符Android
- 五分鐘搞懂HTTPSHTTP
- 30分鐘讓你掌握Git的黑魔法Git
- 徹底搞懂 Git-RebaseGit
- 幫你徹底搞懂JS中的prototype、__proto__與constructor(圖解)JSStruct圖解
- 三分鐘搞懂桶排序排序
- 3分鐘搞懂Cookie與SessionCookieSession
- 全棧工程師很難找?JNPF幫你分分鐘搞定!全棧工程師
- 幫你徹底搞懂JS中的prototype、__proto__與constructor(圖解)(轉)JSStruct圖解
- 面試不再怕,20行Python程式碼幫你搞懂LRU演算法面試Python演算法
- 五分鐘搞懂POM設計模式設計模式
- 五分鐘搞懂MySQL索引下推MySql索引
- 五分鐘搞懂spring-cloud-squareSpringCloud
- 五分鐘搞懂摘要演算法演算法
- 三分鐘搞懂CSS 權重CSS
- 7分鐘用事例帶你掌握工作常用的 git 命令Git
- Idea+Git+GitHub圖文教程,一篇教程幫你搞定IdeaGithub
- Git提交錯了不用慌,這三招幫你修改記錄Git
- 【乾貨!!】十分鐘帶你搞懂 Java AQS 核心設計與實現!!!JavaAQS
- 五分鐘學Java:一篇文章帶你搞懂spring全家桶套餐JavaSpring
- 3分鐘搞懂協作機器人機器人
- 十分鐘搞懂分散式爬蟲分散式爬蟲
- 十分鐘搞懂Lombok使用與原理Lombok
- 10分鐘搞懂遺傳演算法演算法
- 10分鐘搞懂蟻群演算法演算法
- 【老男孩Linux技術分享】5分鐘帶你搞懂日誌採集利器Filebeat!Linux
- 病毒四處告白,《2021安全事件響應觀察報告》幫你搞懂那個“TA”事件
- Git檢視本地幫助文件Git
- 5分鐘搞懂Kubernetes:輕鬆理解所有元件元件
- 8分鐘搞懂Java中的各種鎖Java
- 3分鐘快速搞懂Java的橋接方法Java橋接
- 說3分鐘好像太狂了,5分鐘幫你開啟機器學習的大門吧!機器學習
- 5分鐘搞懂 Golang 資料庫連線管理Golang資料庫
- 5分鐘搞懂多執行緒安全問題執行緒
- 三十分鐘拿Git幹活Git
- 一文帶你搞懂 SSR
- 一文帶你搞懂RPCRPC