你可以執行像 git log 1a410e 這樣的命令來查看完整的歷史,但是這樣你就要記得 1a410e 是你最后一次提交,這樣才能在提交歷史中找到這些對象。你需要一個文件來用一個簡單的名字來記錄這些 SHA-1 值,這樣你就可以用這些指針而不是原來的 SHA-1 值去檢索了。
在 Git 中,我們稱之為“引用”(references 或者 refs,譯者注)。你可以在 .git/refs 目錄下面找到這些包含 SHA-1 值的文件。在這個項目里,這個目錄還沒不包含任何文件,但是包含這樣一個簡單的結構:
~~~
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f
$
~~~
如果想要創建一個新的引用幫助你記住最后一次提交,技術上你可以這樣做:
`$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master`
現在,你就可以在 Git 命令中使用你剛才創建的引用而不是 SHA-1 值:
~~~
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
~~~
當然,我們并不鼓勵你直接修改這些引用文件。如果你確實需要更新一個引用,Git 提供了一個安全的命令 update-ref:
`$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9`
基本上 Git 中的一個分支其實就是一個指向某個工作版本一條 HEAD 記錄的指針或引用。你可以用這條命令創建一個指向第二次提交的分支:
`$ git update-ref refs/heads/test cac0ca`
這樣你的分支將會只包含那次提交以及之前的工作:
~~~
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
~~~
現在,你的 Git 數據庫應該看起來像圖 9-4 一樣。

圖 9-4. 包含分支引用的 Git 目錄對象
每當你執行 git branch (分支名稱) 這樣的命令,Git 基本上就是執行 update-ref 命令,把你現在所在分支中最后一次提交的 SHA-1 值,添加到你要創建的分支的引用。
## HEAD 標記
現在的問題是,當你執行` git branch` (分支名稱) 這條命令的時候,Git 怎么知道最后一次提交的 SHA-1 值呢?答案就是 HEAD 文件。HEAD 文件是一個指向你當前所在分支的引用標識符。這樣的引用標識符——它看起來并不像一個普通的引用——其實并不包含 SHA-1 值,而是一個指向另外一個引用的指針。如果你看一下這個文件,通常你將會看到這樣的內容:
~~~
$ cat .git/HEAD
ref: refs/heads/master
~~~
如果你執行 git checkout test,Git 就會更新這個文件,看起來像這樣:
~~~
$ cat .git/HEAD
ref: refs/heads/test
~~~
當你再執行 `git commit `命令,它就創建了一個 commit 對象,把這個 commit 對象的父級設置為 `HEAD `指向的引用的 SHA-1 值。
你也可以手動編輯這個文件,但是同樣有一個更安全的方法可以這樣做:symbolic-ref。你可以用下面這條命令讀取 HEAD 的值:
~~~
$ git symbolic-ref HEAD
refs/heads/master
~~~
你也可以設置 HEAD 的值:
~~~
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test
~~~
但是你不能設置成 refs 以外的形式:
~~~
$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/
Tags
~~~
你剛剛已經重溫過了 Git 的三個主要對象類型,現在這是第四種。Tag 對象非常像一個 commit 對象——包含一個標簽,一組數據,一個消息和一個指針。最主要的區別就是 Tag 對象指向一個 commit 而不是一個 tree。它就像是一個分支引用,但是不會變化——永遠指向同一個 commit,僅僅是提供一個更加友好的名字。
正如我們在第二章所討論的,Tag 有兩種類型:annotated 和 lightweight 。你可以類似下面這樣的命令建立一個 lightweight tag:
`$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d`
這就是 `lightweight tag` 的全部 —— 一個永遠不會發生變化的分支。 annotated tag 要更復雜一點。如果你創建一個 `annotated tag,Git `會創建一個 tag 對象,然后寫入一個指向指向它而不是直接指向 commit 的 reference。你可以這樣創建一個 `annotated tag`(-a 參數表明這是一個 annotated tag):
`$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'`
這是所創建對象的 SHA-1 值:
~~~
$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2
~~~
現在你可以運行 cat-file 命令檢查這個 SHA-1 值:
~~~
$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700
test tag
~~~
值得注意的是這個對象指向你所標記的 commit 對象的 SHA-1 值。同時需要注意的是它并不是必須要指向一個 commit 對象;你可以標記任何 Git 對象。例如,在 Git 的源代碼里,管理者添加了一個 GPG 公鑰(這是一個 blob 對象)對它做了一個標簽。你就可以運行:
`$ git cat-file blob junio-gpg-pub`
來查看 Git 源代碼倉庫中的公鑰. Linux kernel 也有一個不是指向 commit 對象的 tag —— 第一個 tag 是在導入源代碼的時候創建的,它指向初始 tree (initial tree,譯者注)。
## Remotes
你將會看到的第四種 `reference` 是` remote reference`(遠程引用,譯者注)。如果你添加了一個 remote 然后推送代碼過去,Git 會把你最后一次推送到這個 remote 的每個分支的值都記錄在 `refs/remotes` 目錄下。例如,你可以添加一個叫做 origin 的 remote 然后把你的 master 分支推送上去:
~~~
$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
~~~
然后查看` refs/remotes/origin/master` 這個文件,你就會發現 origin remote 中的 master 分支就是你最后一次和服務器的通信。
~~~
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
~~~
`Remote` 引用和分支主要區別在于他們是不能被 check out 的。Git 把他們當作是標記了這些分支在服務器上最后狀態的一種書簽。
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 安裝 Git
- 1.5 初次運行 Git 前的配置
- 1.6 獲取幫助
- 1.7 小結
- 2. Git基礎
- 2.1 取得項目的 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 技巧和竅門
- 2.8 小結
- 3. Git分支
- 3.1 何謂分支
- 3.2 分支的新建與合并
- 3.3 分支的管理
- 3.4 利用分支進行開發的工作流程
- 3.5 遠程分支
- 3.6 分支的衍合
- 3.7 小結
- 4. 服務器上的Git
- 4.1 協議
- 4.2 在服務器上部署 Git
- 4.3 生成 SSH 公鑰
- 4.4 架設服務器
- 4.5 公共訪問
- 4.6 GitWeb
- 4.7 Gitosis
- 4.8 Gitolite
- 4.9 Git 守護進程
- 4.10 Git 托管服務
- 4.11 小結
- 5. 分布式Git
- 5.1 分布式工作流程
- 5.2 為項目作貢獻
- 5.3 項目的管理
- 5.4 小結
- 6. Git工具
- 6.1 修訂版本(Revision)選擇
- 6.2 交互式暫存
- 6.3 儲藏(Stashing)
- 6.4 重寫歷史
- 6.5 使用 Git 調試
- 6.6 子模塊
- 6.7 子樹合并
- 6.8 總結
- 7. 自定義Git
- 7.1 配置 Git
- 7.2 Git屬性
- 7.3 Git掛鉤
- 7.4 Git 強制策略實例
- 7.5 總結
- 8. Git與其他系統
- 8.1 Git 與 Subversion
- 8.2 遷移到 Git
- 8.3 總結
- 9. Git 內部原理
- 9.2 Git 對象
- 9.3 Git References
- 9.4 Packfiles
- 9.5 The Refspec
- 9.6 傳輸協議
- 9.7 維護及數據恢復
- 9.8 總結
- 9.1 底層命令 (Plumbing) 和高層命令 (Porcelain)