Git
英文 ▾ 主題 ▾ 最新版本 ▾ gittutorial-2 最後更新於 2.23.0

名稱

gittutorial-2 - Git 入門教學:第二部分

概要

git *

描述

在閱讀本教學之前,您應該先完成 gittutorial[7] 的學習。

本教學的目標是介紹 Git 架構的兩個基本部分—物件資料庫和索引檔—並為讀者提供理解 Git 其他文件所需的一切知識。

Git 物件資料庫

讓我們開始一個新的專案並建立一小段歷史記錄

$ mkdir test-project
$ cd test-project
$ git init
Initialized empty Git repository in .git/
$ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
[master (root-commit) 54196cc] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
[master c4d59f3] add emphasis
 1 file changed, 1 insertion(+), 1 deletion(-)

Git 回應提交時所用的 7 個十六進位數字是什麼?

我們在教學的第一部分看到,提交會有像這樣的名稱。事實證明,Git 歷史中的每個物件都以 40 個十六進位數字的名稱儲存。該名稱是物件內容的 SHA-1 雜湊值;除其他事項外,這可確保 Git 永遠不會儲存相同的資料兩次 (因為相同的資料會給予相同的 SHA-1 名稱),且 Git 物件的內容永遠不會變更 (因為這也會變更物件的名稱)。此處的 7 個字元十六進位字串只是此類 40 個字元長字串的縮寫。只要沒有歧義,縮寫可以用在任何可以使用 40 個字元字串的地方。

當您依照上述範例建立提交物件時,其產生的 SHA-1 雜湊值預期會與上面顯示的不同,因為提交物件會記錄建立時間以及執行提交人員的姓名。

我們可以使用 cat-file 命令詢問 Git 有關此特定物件的資訊。請勿從此範例複製 40 個十六進位數字,而是使用您自己版本中的數字。請注意,您可以將其縮短為僅幾個字元,以節省您輸入所有 40 個十六進位數字的時間

$ git cat-file -t 54196cc2
commit
$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

樹狀結構可以參考一個或多個「blob」物件,每個物件對應一個檔案。此外,樹狀結構也可以參考其他樹狀物件,從而建立目錄階層。您可以使用 ls-tree 檢查任何樹狀結構的內容 (請記住,SHA-1 的初始部分夠長也會起作用)

$ git ls-tree 92b8b694
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt

因此,我們看到此樹狀結構中有一個檔案。SHA-1 雜湊值是對該檔案資料的參考

$ git cat-file -t 3b18e512
blob

「blob」只是檔案資料,我們也可以使用 cat-file 來檢查

$ git cat-file blob 3b18e512
hello world

請注意,這是舊的檔案資料;因此,Git 在回應初始樹狀結構時所命名的物件是一個包含第一個提交記錄之目錄狀態快照的樹狀結構。

所有這些物件都以其 SHA-1 名稱儲存在 Git 目錄中

$ find .git/objects/
.git/objects/
.git/objects/pack
.git/objects/info
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/92
.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
.git/objects/54
.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
.git/objects/a0
.git/objects/a0/423896973644771497bdc03eb99d5281615b51
.git/objects/d0
.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
.git/objects/c4
.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241

這些檔案的內容只是壓縮的資料,加上識別其長度和類型的標頭。類型為 blob、tree、commit 或 tag。

最容易找到的提交是 HEAD 提交,我們可以從 .git/HEAD 中找到它

$ cat .git/HEAD
ref: refs/heads/master

如您所見,這告訴我們目前所在的分支,它是透過命名 .git 目錄下的檔案來告訴我們,該檔案本身包含一個 SHA-1 名稱,該名稱參考一個提交物件,我們可以使用 cat-file 來檢查它

$ cat .git/refs/heads/master
c4d59f390b9cfd4318117afde11d601c1085f241
$ git cat-file -t c4d59f39
commit
$ git cat-file commit c4d59f39
tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500

add emphasis

此處的「樹狀結構」物件是指新的樹狀結構狀態

$ git ls-tree d0492b36
100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
$ git cat-file blob a0423896
hello world!

而「父」物件是指先前的提交

$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

樹狀結構物件是我們首先檢查的樹狀結構,而此提交不尋常之處在於它沒有任何父系。

大多數提交只有一個父系,但提交有多個父系也很常見。在這種情況下,提交表示合併,父系參考指向合併分支的頭。

除了 blob、樹狀結構和提交之外,剩下的唯一物件類型是「tag」,我們在這裡不討論;有關詳細資訊,請參閱 git-tag[1]

因此,現在我們知道 Git 如何使用物件資料庫來表示專案的歷史記錄

  • 「commit」物件會參考「tree」物件,表示歷史記錄中特定點的目錄樹快照,並參考「parent」提交,以顯示它們如何連接到專案歷史記錄中。

  • 「tree」物件表示單一目錄的狀態,將目錄名稱與包含檔案資料的「blob」物件以及包含子目錄資訊的「tree」物件建立關聯。

  • 「blob」物件包含檔案資料,而沒有任何其他結構。

  • 每個分支的頭部提交物件的參考儲存在 .git/refs/heads/ 下的檔案中。

  • 目前分支的名稱儲存在 .git/HEAD 中。

順便一提,請注意,許多命令都將樹狀結構作為參數。但是,正如我們在上面看到的,可以用許多不同的方式參考樹狀結構—透過該樹狀結構的 SHA-1 名稱、透過參考樹狀結構的提交名稱、透過其頭部參考該樹狀結構的分支名稱等等—大多數此類命令都可以接受任何這些名稱。

在命令概要中,有時會使用「tree-ish」一詞來指定此類參數。

索引檔

我們一直在用來建立提交的主要工具是 git-commit -a,它會建立一個提交,其中包含您對工作樹所做的所有變更。但是,如果您只想提交對某些檔案的變更,該怎麼辦?或者只提交對某些檔案的某些變更呢?

如果我們查看提交的建立方式,我們會發現有更彈性的方法來建立提交。

繼續我們的測試專案,讓我們再次修改 file.txt

$ echo "hello world, again" >>file.txt

但這次不要立即進行提交,而是採取一個中間步驟,並要求沿途顯示差異,以追蹤發生的情況

$ git diff
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again
$ git add file.txt
$ git diff

最後一個差異是空的,但沒有進行新的提交,且頭部仍然不包含新的一行

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

因此,git diff 是與頭部以外的其他內容進行比較。它正在比較的內容實際上是索引檔,該檔案以二進位格式儲存在 .git/index 中,但我們可以透過 ls-files 檢查其內容

$ git ls-files --stage
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
$ git cat-file -t 513feba2
blob
$ git cat-file blob 513feba2
hello world!
hello world, again

因此,我們的 git add 所做的就是儲存一個新的 blob,然後在索引檔中放入對它的參考。如果我們再次修改該檔案,我們會看到新的修改反映在 git diff 輸出中

$ echo 'again?' >>file.txt
$ git diff
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

透過正確的參數,git diff 也可以向我們顯示工作目錄與上次提交之間的差異,或者索引與上次提交之間的差異

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,3 @@
 hello world!
+hello world, again
+again?
$ git diff --cached
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

在任何時候,我們都可以使用 git commit (不帶 "-a" 選項) 建立新的提交,並確認提交的狀態僅包含儲存在索引檔中的變更,而不包含仍僅在我們工作樹中的其他變更

$ git commit -m "repeat"
$ git diff HEAD
diff --git a/file.txt b/file.txt
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

因此,預設情況下,git commit 會使用索引來建立提交,而不是工作樹;commit 的 "-a" 選項會告知它先使用工作樹中的所有變更更新索引。

最後,值得檢視一下 git add 對索引檔的影響

$ echo "goodbye, world" >closing.txt
$ git add closing.txt

git add 的作用是在索引檔中新增一個項目

$ git ls-files --stage
100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt

而且,正如您可以使用 cat-file 看到的,此新項目參考了檔案的目前內容

$ git cat-file blob 8b9743b2
goodbye, world

「status」命令是一種有用的方法,可以快速取得情況摘要

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

	new file:   closing.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)

	modified:   file.txt

由於 closing.txt 的目前狀態已快取在索引檔中,因此它被列為「待提交的變更」。由於 file.txt 在工作目錄中發生了變更,但未反映在索引中,因此它被標記為「已變更但未更新」。此時,執行「git commit」會建立一個新增 closing.txt (及其新內容) 的提交,但不會修改 file.txt。

此外,請注意,單純執行 git diff 會顯示 file.txt 的變更,但不會顯示新增的 closing.txt,因為索引檔中 closing.txt 的版本與工作目錄中的版本相同。

除了作為新提交的暫存區之外,索引檔在檢出分支時也會從物件資料庫載入資料,並用於保存合併操作中涉及的樹狀結構。詳情請參閱 gitcore-tutorial[7] 和相關的說明文件。

下一步?

現在您應該已了解閱讀任何 git 命令說明文件所需的一切知識;一個好的起點是從 giteveryday[7] 中提到的命令開始。您應該可以在 gitglossary[7] 中找到任何未知的術語。

Git 使用者手冊提供了更全面的 Git 介紹。

gitcvs-migration[7] 說明了如何將 CVS 儲存庫匯入 Git,並展示如何以類似 CVS 的方式使用 Git。

有關 Git 使用的一些有趣範例,請參閱 howtos

對於 Git 開發人員,gitcore-tutorial[7] 詳細介紹了例如建立新提交等較底層的 Git 機制。

GIT

屬於 git[1] 套件的一部分

scroll-to-top