當前位置:
首頁 > 知識 > 好文推薦,20 分鐘教你搞懂 Git!

好文推薦,20 分鐘教你搞懂 Git!


Linux編程

點擊右側關注,免費入門到精通!

譯者丨Alex


https://www.tutorialdocs.com/article/how-git-works.html

儘管每天你都會用到Git,常用的命令可能不到5個,但你可能現在還搞不懂它的工作原理。為什麼Git可以管理版本?基本命令git add和git commit到底在幹什麼?

在這篇文章中,我將用一個例子來解釋Git的運行過程,幫助你理解Git的工作原理。

1. 初始化

讓我們創建一個項目的目錄,然後進入該目錄。


$

 mkdir git-demo-project

$

 

cd

 git-demo-project


如果想管理項目的版本,那麼我們應該做的第一件事情就是通過git init初始化。


$ git init



git init只做了一件事情,那就是在項目的根目錄下創建.git子目錄來保存版本信息。

$ ls .git

branches/
config
description
HEAD
hooks/
info/
objects/
refs/



上述命令顯示了.git子目錄中的內容。

2. 保存對象



接下來讓我們創建一個新的空文件test.txt。

$ touch test.txt



然後把這個文件添加到Git代碼庫中,這一步將創建test.txt現有內容的一個副本。


$

 git 

hash

-object -w test.txt

e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

在上述代碼中,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/

9

de29bb2d1d6434b8b29ae775ad8c2e48c5391



上述代碼輸出的文件內容是一些二進位字元。你可能會問既然test.txt是空文件,又怎麼會有這些內容呢?這是因為該二進位對象中還存儲了一些元數據。



如果你想看看該文件原始的文本內容,那麼應該使用git cat-file。

$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

因為原文件為空,所以上述命令什麼都沒有顯示。現在我們往test.txt文件中寫點東西。


$

 

echo

 

"hello world"

 > test.txt



這個文件的內容已經改變了,所以你需要再次把它保存為Git對象。


$

 git 

hash

-object -w test.txt

3b18e512dba79e4c8300dd08aeb37f8e728b8dad

如上述代碼所示,test.txt的哈希值已經隨著文件內容的改變而發生了變化。同時還生成了新文件


git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad。現在你可以看到這個文件的內容了。


$ git cat-file -p 

3

b18e512dba79e4c8300dd08aeb37f8e728b8dad

hello world



3. 更新索引



當文件保存成二進位對象以後,你需要告訴Git哪個文件發生了變化。Git會在一個名叫「索引」(或階段)的區域記錄所有發生了變化的文件。然後等到所有的變更都結束後,將索引中的這些文件一起寫入正式的版本歷史記錄中。


$ git 

update

-

index

 

--add --cacheinfo 100644 


3

b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt



上述命令記錄了文件名test.txt、二進位對象名(哈希值)以及索引中文件的訪問許可權。



git ls-files命令可以顯示索引中當前的內容。


$ git ls-files --stage

100644

 

3

b18e512dba79e4c8300dd08aeb37f8e728b8dad 

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對象。


$ git 

write

-tree

c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

在上述代碼中,目錄結構保存成了二進位對象,而對象的名字是哈希值。它也保存在.git/objects目錄中。



讓我們來看看該文件的內容。


$ git cat-file -p c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

100644

 blob 

3

b18e512dba79e4c8300dd08aeb37f8e728b8dad    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 jam  

1538889134

 +

0800


committer jam  

1538889134

 +

0800

first commit



在上述代碼中,第一行輸出是對應於該快照的目錄樹對象,而第二行和第三行是有關作者和提交者的信息,最後一行內容是提交的描述。



通過git log命令我們還可以查看某個快照的信息。


$ git log 

--stat c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

commit

 c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: jam 

Date

:   Sun 

Oct

 

7

 

13

:

12

:

14

 

2018

 +

0800

    

first

 

commit

 test.txt | 

1

 +
 

1

 

file

 

changed

1

 insertion(+)

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

1552

fd52bc14497c11313aa91547255c95728f37



最後,提交目錄結構,並生成一個快照。


$

 

echo

 

"second commit"

 | git commit-tree 1552fd52bc14497c11313aa91547255c95728f37 -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

785f188674ef3c6ddc5b516307884e1d551f53ca



在上述代碼中,我們可以通過git commit-tree命令的參數-p來指定父節點,即以哪個快照為基礎。



下面我們把快照的哈希值寫入到.git/refs/heads/master文件中,並讓master指針指向該快照。


$

 

echo

 785f188674ef3c6ddc5b516307884e1d551f53ca > .git/refs/heads/master




現在,通過git log命令你可以看到兩個快照了。


$ git log

commit

 

785

f188674ef3c6ddc5b516307884e1d551f53ca (

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]:當前分支的指針將被複位到某個指定的快照上。



 推薦↓↓↓ 






??

16個技術公眾號

】都在這裡!


涵蓋:程序員大咖、源碼共讀、程序員共讀、數據結構與演算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、資料庫研發、幽默程序員等。

萬水千山總是情,點個 「

好看

」 行不行

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發 的精彩文章:

不要拿你34碼的腳挑戰我8碼的臉!
使用Python進行面部合成

TAG:Python開發 |