簡體中文 ▾ 主題 ▾ 最新版本 ▾ 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 在響應初始樹時命名的物件是一個樹,其中包含第一個提交所記錄的目錄狀態的快照。

所有這些物件都儲存在 Git 目錄內的 SHA-1 名稱下。

$ 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

tree 物件是我們最初檢查的 tree,而這個 commit 不同尋常,因為它沒有 parent。

大多數提交只有一個 parent,但一個提交有多個 parent 也很常見。在這種情況下,提交代表一個合併,parent 引用指向被合併分支的頭部。

除了 blob、tree 和 commit 之外,唯一剩餘的物件型別是“tag”,我們在這裡不討論它;有關詳細資訊,請參閱 git-tag[1]

現在我們知道了 Git 如何使用物件資料庫來表示專案的歷史。

  • “commit”物件引用“tree”物件,這些物件代表特定歷史時間點的目錄樹快照,並引用“parent”提交以顯示它們如何連線到專案歷史中。

  • “tree”物件代表單個目錄的狀態,將目錄名稱與包含檔案資料的“blob”物件以及包含子目錄資訊的“tree”物件關聯起來。

  • “blob”物件包含檔案資料,不包含其他結構。

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

  • .git/HEAD 中儲存了當前分支的名稱。

順便說一句,請注意,許多命令都接受一個 tree 作為引數。但正如我們上面看到的,一個 tree 可以透過多種方式引用——透過該 tree 的 SHA-1 名稱、透過引用該 tree 的提交的名稱、透過引用該 tree 的分支的名稱等等——而大多數此類命令都可以接受其中任何一種名稱。

在命令摘要中,“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

最後一個 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* 使用索引來建立提交,而不是工作樹;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] 和相關的 man 頁。

接下來是什麼?

此時,您應該知道閱讀任何 git 命令的 man 頁所需的一切;一個好的起點是 giteveryday[7] 中提到的命令。您應該能夠在 gitglossary[7] 中找到任何未知的術語。

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

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

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

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

GIT

Git[1] 套件的一部分