# Git 引用
我們可以借助類似于?`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 分支的本質:一個指向某一系列提交之首的指針或引用。 若想在第二個提交上創建一個分支,可以這么做:
~~~
$ git update-ref refs/heads/test cac0ca
~~~
這個分支將只包含從第二個提交開始往前追溯的記錄:
~~~
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
~~~
至此,我們的 Git 數據庫從概念上看起來像這樣:

Figure 10-4.?包含分支引用的 Git 目錄對象。
當運行類似于?`git branch (branchname)`?這樣的命令時,Git 實際上會運行?`update-ref`命令,取得當前所在分支最新提交對應的 SHA-1 值,并將其加入你想要創建的任何新引用中。
## HEAD 引用
現在的問題是,當你執行?`git branch (branchname)`?時,Git 如何知道最新提交的 SHA-1 值呢? 答案是 HEAD 文件。
HEAD 文件是一個符號引用(symbolic reference),指向目前所在的分支。 所謂符號引用,意味著它并不像普通引用那樣包含一個 SHA-1 值——它是一個指向其他引用的指針。 如果查看 HEAD 文件的內容,一般而言我們看到的類似這樣:
~~~
$ cat .git/HEAD
ref: refs/heads/master
~~~
如果執行?`git checkout test`,Git 會像這樣更新 HEAD 文件:
~~~
$ cat .git/HEAD
ref: refs/heads/test
~~~
當我們執行?`git 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
~~~
不能把符號引用設置為一個不符合引用格式的值:
~~~
$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/
~~~
## 標簽引用
前文我們剛討論過 Git 的三種主要對象類型,事實上還有第四種。 標簽對象(tag object)非常類似于一個提交對象——它包含一個標簽創建者信息、一個日期、一段注釋信息,以及一個指針。 主要的區別在于,標簽對象通常指向一個提交對象,而不是一個樹對象。 它像是一個永不移動的分支引用——永遠指向同一個提交對象,只不過給這個提交對象加上一個更友好的名字罷了。
正如?[Git 基礎](http://git-scm.com/book/zh/v2/1-git-basics/_git_basics_chapter)?中所討論的那樣,存在兩種類型的標簽:附注標簽和輕量標簽。 可以像這樣創建一個輕量標簽:
~~~
$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
~~~
這就是輕量標簽的全部內容——一個固定的引用。 然而,一個附注標簽則更復雜一些。 若要創建一個附注標簽,Git 會創建一個標簽對象,并記錄一個引用來指向該標簽對象,而不是直接指向提交對象。 可以通過創建一個附注標簽來驗證這個過程(`-a`?選項指定了要創建的是一個附注標簽):
~~~
$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'
~~~
下面是上述過程所建標簽對象的 SHA-1 值:
~~~
$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2
~~~
現在對該 SHA-1 值運行?`cat-file`?命令:
~~~
$ 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
~~~
我們注意到,object 條目指向我們打了標簽的那個提交對象的 SHA-1 值。 另外要注意的是,標簽對象并非必須指向某個提交對象;你可以對任意類型的 Git 對象打標簽。 例如,在 Git 源碼中,項目維護者將他們的 GPG 公鑰添加為一個數據對象,然后對這個對象打了一個標簽。 可以克隆一個 Git 版本庫,然后通過執行下面的命令來在這個版本庫中查看上述公鑰:
~~~
$ git cat-file blob junio-gpg-pub
~~~
Linux 內核版本庫同樣有一個不指向提交對象的標簽對象——首個被創建的標簽對象所指向的是最初被引入版本庫的那份內核源碼所對應的樹對象。
## 遠程引用
我們將看到的第三種引用類型是遠程引用(remote reference)。 如果你添加了一個遠程版本庫并對其執行過推送操作,Git 會記錄下最近一次推送操作時每一個分支所對應的值,并保存在`refs/remotes`?目錄下。 例如,你可以添加一個叫做?`origin`?的遠程版本庫,然后把?`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`?遠程版本庫的`master`?分支所對應的 SHA-1 值,就是最近一次與服務器通信時本地?`master`?分支所對應的 SHA-1 值:
~~~
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
~~~
遠程引用和分支(位于?`refs/heads`?目錄下的引用)之間最主要的區別在于,遠程引用是只讀的。 雖然可以?`git checkout`?到某個遠程引用,但是 Git 并不會將 HEAD 引用指向該遠程引用。因此,你永遠不能通過?`commit`?命令來更新遠程引用。 Git 將這些遠程引用作為記錄遠程服務器上各分支最后已知位置狀態的書簽來管理。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 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 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令