簡體中文 ▾ 主題 ▾ 最新版本 ▾ 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

一個樹(tree)可以引用一個或多個“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

這裡的“tree”物件指的是樹的新狀態。

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

而“parent”物件指的是前一個提交。

$ 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、tree 和 commit 之外,唯一剩下的物件型別是“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

但這次我們不立即進行提交,而是採取一箇中間步驟,並一路要求 diff,以跟蹤正在發生的事情。

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

最後一次 diff 是空的,但沒有建立新的提交,並且 HEAD 仍然不包含新行。

$ 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 正在與 HEAD 之外的東西進行比較。它正在比較的實際上是索引檔案,它以二進位制格式儲存在 .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 使用索引來建立提交,而不是工作樹;提交命令的“-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 的當前狀態快取在索引檔案中,因此它被列為“待提交的更改”(Changes to be committed)。由於 file.txt 在工作目錄中有更改但未反映在索引中,因此它被標記為“已更改但未更新”(changed but not updated)。此時,執行“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