git學習 資源

【原文來源ref

http://www.ibm.com/developerworks/cn/opensource/os-cn-tourofgit/#ibm-pcon】


背景

Git 是一個開源的分佈式版本控制軟件。在英式英語中,Git 指一個愚笨或者不開心的人,恐怕與 Git 發明人——Linux 教父 Linus Torvalds 當時的自嘲心理不無關係吧。2002 年之前,Linux 內核維護工作的絕大部分時間都浪費在提交補丁與保存歸檔等繁瑣事務上。啓用版本控制工具 BitKeeper 管理 Linux 內核成了當務之急。不過,BitKeeper 畢竟是一款商業軟件,在經歷了 3 年免費使用之後,Linux 社區不得不尋求它的替代品,以便繼續託管 Linux 內核源代碼。2005 年,迫於無奈,Linus Torvalds 自行開發了一套開源版本控制工具,並命名爲 Git。

自誕生以來,Git 就以其開源、簡單、快捷、分佈式、高效等特點,應付了類似 Linux 內核源代碼等各種複雜的項目開發需求。如今,Git 已經非常成熟,被廣泛接受與使用,越來越多的項目都遷移到 Git 倉庫中進行管理。以 Eclipse 社區爲例。據稱,目前 80% 的 Eclipse 基金會項目已經完全使用 Git 管理,CVS 訪問權限已經切換成只讀狀態。並且,在 Eclipse 基金會官網中,針對項目管理的介紹中已將"CVS"三個字符劃掉,而且很萌地寫道,"Ding dong, the witch is dead.",意思是"叮咚,那個老巫婆已經掛了"。

不僅如此,筆者最近也收到了全球最大開源代碼託管平臺——SourceForge 的升級通知。其中,筆者的一個較爲簡單的項目已經從 CVS 被系統默認自動升級到了 Git。而對於另一個較爲複雜的 CVS 項目Toolbox for Java/JTOpen,SourceForge 並沒有自動升級,估計是等待筆者做升級前的最後準備工作。筆者希望通過分享自己的 Git 學習體驗與實踐經驗,對 Git 初學者有所裨益,這也是本文之意義所在。

爲什麼選擇 Git

實際上,相對於 CVS、SVN 等主流版本控制軟件,Git 的學習成本甚至會更高。比如,對於 Subversion 用戶而言,如果能理解什麼是文件、工作目錄、資源庫、版本、分支和標籤等概念,差不多就夠用了。而對於 Git 用戶,需要理解更多更復雜的概念,包括文件、快照、工作樹、索引、本地資源庫、遠程資源庫、遠程、提交、分支和 Stash 等。那麼,爲什麼軟件開發者對 Git 還是趨之若鶩呢?相比於 CVS 與 SVN,Git 的優勢到底體現在哪裏?

關於 Git 的各種優勢,互聯網以及各種 Git 書籍都給出了自己的答案。筆者認爲,存儲快照與分佈式的設計思想是 Git 的 2 大看點,理由如下:

第一,Git 底層自行維護的存儲文件系統是一大亮點。CVS、SVN 底層採用的爲增量式文件系統,如圖 1 所示。增量式文件系統的特點是:當文件變動發生提交時,該文件系統存儲的是文件的差異信息。

圖 1. CVS、SVN 記錄文件內容差異
圖 1. CVS、SVN 記錄文件內容差異

同樣是文件變更提交,Git 底層文件系統存儲的則爲文件快照,即整個文件內容,並保存指向快照的索引,如圖 2 所示。考慮到性能因素,如果文件內容沒有發生任何變化,該文件系統則不會重複保存文件,只是簡單地保存文件的鏈接。

圖 2. Git 記錄整個文件快照
圖 2. Git 記錄整個文件快照

Git 之所以選擇這樣的底層存儲數據結構,主要是爲了提高 Git 分支的使用效率。實際上,Git 分支本質上是一個指向索引對象的可變指針,而每一個索引對象又指向文件快照,如圖 3 所示。

圖 3. Git 分支對應的數據結構
圖 3. Git 分支對應的數據結構

這樣一來,創建分支可以瞬間完成,幾乎不需要花費太多代價。換句話說,Git 分支是廉價的、輕量級的。我們看看各種 CVS、SVN 項目,分支通常意味着源代碼的完整拷貝,其代價是昂貴的、重量級的。而對於大型項目來說,創建各種分支又是十分必要的,這與 Git 鼓勵頻繁創建與合併分支的理念相吻合。

第二,Git 版本控制系統的設計思想是"去中心化"。傳統的 CVS 、SVN 等工具採用的是 C/S 架構,只有一箇中心代碼倉庫,位於服務器端。而一旦由於服務器系統宕機、網絡不通等各種原因造成中心倉庫不可用,整個 CVS 、SVN 系統的代碼檢入與檢出就癱瘓了。即便考慮到高可用性,通過遷移另一箇中心倉庫繼續代碼提交操作,相應的運營維護成本也會隨之上升。

爲了擺脫對中心倉庫的依賴,Git 的初始設計目標之一就是分佈式控制管理。我們給出一個樣例,如圖 4 所示。假如我們成立一個項目組,開發者主要由 Alice、Bob、Clair、David 四名成員組成。其中,除了中心倉庫 origin(Git 默認遠程倉庫名稱)之外,每一名成員各自負責一個本地倉庫。從分佈式的觀點來看,David 可看成是 Alice 的遠程倉庫,反過來也是一樣。Git 分佈式的設計理念有助於減少對中心倉庫的依賴,從而有效降低中心倉庫的負載,改善代碼提交的靈活性。

圖 4. Git 分佈式工作示意圖
圖 4. Git 分佈式工作示意圖

Git 分佈式設計思想所帶來的另外一大好處是支持離線工作。離線工作的好處不言而喻,對於 CVS、SVN 這種嚴重依賴網絡的 C/S 工具而言,沒有了網絡或者 VPN ,就意味着失去了左膀右臂,代碼檢入與檢出操作就無法正常進行。而一旦使用 Git ,即便在沒有 WIFI 的飛機或者火車上,照樣可以頻繁地提交代碼,只不過先提交到本地倉庫,等到了網絡連通的時候,再上傳到遠程的鏡像倉庫。

有關 Git 更多詳細信息,請參考 Git 官方網站:http://git-scm.com/‎。

工欲善其事,必先利其器。在理解 Git 靈活的快照存儲與分佈式設計理念之後,我們介紹 Git 針對不同操作系統的安裝過程。需要指出的是,這裏僅僅粗線條地介紹 Git 的安裝方法,至於 Git 安裝前提條件、安裝過程出現的問題診斷等更加詳細的內容描述,均不在本文的討論範圍。

如何安裝 Git

總結起來,Git 安裝方式通常分爲兩種:一種是選擇 Git 源碼編譯安裝;另一種使用針對特定平臺的二進制安裝包,又可以細分爲 Linux、Mac、Windows 等,其安裝說明如下。

1. 源碼編譯安裝

從 Git 源碼安裝至少可以保證版本是最新的。在安裝 Git 之前,需要安裝其依賴的軟件包,包括 curl、zlib、openssl、expat、libiconv 等。根據不同類型的 Linux,讀者可以選擇不同的軟件包安裝工具,這裏以 yum 爲例,其安裝命令如下:

$ yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel

接下來,讀者可以從 Git 官方站點 http://git-scm.com/download 下載最新 Git 源代碼(由於時間的差異,筆者無法保證本文所述 Git 爲最新版本),執行以下命令編譯安裝。

$ tar -zxf git-1.7.6.tar.gz
$ cd git-1.7.6
$ make prefix=/usr/local all
$ sudo make prefix=/usr/local install

最後,敲入 git 命令,檢驗安裝是否成功,如圖 5 所示。可以看到,我們已經成功安裝 Git 了。

圖 5. 通過源碼安裝 Git
圖 5. 通過源碼安裝 Git

2. 在 Linux 上安裝

要在 Linux 上安裝預編譯好的 Git 二進制安裝包,可選擇系統支持的軟件包管理器。對於紅帽 Linux,使用 yum 命令安裝:

$ yum install git-core

而對於 Ubuntu 這類 Debian 體系的 Linux 系統,使用 apt-get 命令安裝:

$ apt-get install git-core

由於此種安裝方式非常簡單,這裏不做貼圖展示了。

3. 在 Mac 上安裝

Mac 系統支持 Git 安裝的方式分爲兩種:編譯安裝與圖形安裝。其命令行式的編譯安裝與 Linux 大同小異,這裏不再介紹。相比之下,Mac 圖形安裝 Git 更加簡單,其安裝截圖如圖 6 所示。讀者可去 http://code.google.com/p/git-osx-installer 下載最新支持 Mac 系統的 Git 版本。

圖 6. 從 Mac 上安裝 Git
圖 6. 從 Mac 上安裝 Git

4. 在 Windows 上安裝

與之前所述的 Mac 安裝 Git 一樣,在 Windows 上安裝 Git 也同樣輕鬆。根據用戶的使用習慣,我們又可以大致分爲三類:

習慣命令行的用戶,可選擇 msysGit 安裝包,安裝截圖如 7 所示。msysGit 的官方下載地址爲:http://code.google.com/p/msysgit

圖 7. 從 Windows 上安裝命令行 Git 工具——msysGit
圖 7. 從 Windows 上安裝命令行 Git 工具——msysGit

對於習慣 Tortoise 風格的用戶,可以選擇 TortoiseGit 安裝包,安裝後的右鍵截圖如圖 8 所示。TortoiseGit 的下載地址爲:http://code.google.com/p/tortoisegit/。

圖 8. 從 Windows 上安裝“右鍵”Git 工具——TortoiseGit
圖 8. 從 Windows 上安裝“右鍵”Git 工具——TortoiseGit

而對於習慣 Eclipse 風格的用戶,可以選擇 Eclipse 插件——EGit 方式安裝,其 Git Repositories 視圖截圖如圖 9 所示。EGit 的下載地址爲:http://download.eclipse.org/egit/updates。

圖 9. 從 Windows 上安裝 Eclipse 的 Git 插件——EGit
圖 9. 從 Windows 上安裝 Eclipse 的 Git 插件——EGit

無論是哪一種安裝方式,如果是第一次使用 Git,均需要配置用戶信息,包括用戶名與 Email(如下所示),以便以後每次 Git 提交時都可以自動引用這兩條信息,說明是誰更新與提交了代碼。

$ git config --global user.name "Pi Guang Ming"
$ git config --global user.email [email protected]

到此爲止,我們已經介紹了 Git 的分佈式模型、快照模型、針對不同操作系統平臺的 Git 安裝之後,接下來是本文的主題內容,即 Git 的使用。

如何使用 Git

前面提到,這一部分是本文的重點。我們將主要精力集中在 Git 底層的工作原理、以及實際工程中較爲實用的 Git 分支、標籤、補丁、CVS 與 SVN 針對 Git 的遷移等,關於 Git 的各種基礎命令語法、解釋說明,以及本文沒有涉及到的內容,均可參見 Git 相關使用指南。

創建 Git 項目倉庫

在正式使用 Git 之前,我們至少需要創建一個 Git 代碼倉庫(簡稱 Git 倉庫)。通常而言,取得一個 Git 倉庫的方法有兩種。第一種是在現存的目錄下,通過導入所有文件來創建新的 Git 倉庫;第二種是從遠程 Git 鏡像倉庫直接克隆到本地倉庫。

針對第一類 Git 倉庫,我們可以使用 git init 命令創建一個嶄新的 Git 項目倉庫,如下:

$ git init

初始化 Git 後,在當前目錄下會出現一個名爲 .git 的隱藏目錄,如圖 10 所示。

圖 10. .git 目錄
圖 10. .git 目錄

之所以特意強調 .git 目錄,是因爲它十分重要。對於一個 Git 倉庫來說,其 .git 目錄保存了整個 Git 項目的所有數據與資源。關於 .git 目錄中各種文件的簡要解釋說明,如表 1 所示。如果需要了解詳細信息,請參見 Git 官方網站:http://git-scm.com/‎。

表 1 .git 目錄簡要說明
子目錄名 簡要描述
branches Git 項目分支信息,新版 Git 已經不再使用該目錄。
config Git 項目配置信息
description Git 項目描述信息
HEAD 指向 Git 項目當前分支的頭指針
hooks 默認的"hooks"腳本,被特定事件發生前後觸發。
info 裏面含一個 exclude 文件,指 Git 項目要忽略的文件。
objects Git 的數據對象,包括:commits, trees, blobs, tags。
refs 指向所有 Git 項目分支的指針

針對第二類 Git 倉庫,我們不需要 git init 初始化倉庫,取而代之的是,使用 git clone 直接將遠程鏡像克隆到本地倉庫。這裏,我們以下載 Git 軟件本身的源代碼爲例,其 git clone 命令如下:

git clone git://git.kernel.org/pub/scm/git/git.git

通過 ls git 命令,我們可以查看 Git 倉庫中的內容,如圖 11 所示。需要說明的是,針對遠程倉庫的鏡像,實際拷貝的就是 .git 目錄下的數據,然後根據元數據恢復成原來的整個項目結構,也即是圖 11 所示的內容。

圖 11. 克隆 Git 源代碼
圖 11. 克隆 Git 源代碼

此外,除了 git:// 協議,針對不同的使用場景,git clone 還支持 ssh://、http(s):// 等各種不同協議。

Git 對象模型

應該說,Git 對象模型是整個 Git 設計思想中最核心的部分。理解 Git 對象模型是理解整個 Git 的關鍵。簡單來說,每個 Git 對象包含三部分:類型,大小和內容。其中,對象的類型又分爲 commits, trees, blobs, tags,其簡要說明如下:

  • blob 對象:一塊二進制數據,用來存儲文件數據,通常是一個文件。
  • tree 對象:指向 blob 對象或是其它 tree 對象的指針,一般用來表示內容之間的目錄層次關係。
  • commit 對象:一個 commit 對象只指向一個 tree 對象,用來標記項目某一個特定時間點的狀態,如時間戳、父對象、作者、提交者等。
  • tag 對象:與 CVS、SVN 標籤的概念類似。

接下來,我們結合一個示例來解釋不同 Git 對象之間的關係。圖 12 展示的是一個樣例 Ruby 項目,可以看出,這個例子非常簡單,僅作示意。

圖 12. Ruby 項目的目錄層次結構
圖 12. Ruby 項目的目錄層次結構

如果我們把該項目提交到 Git 倉庫中,那麼它的 Git 對象關係就如圖 13 所示。其中,3 個 blob 對象分別對應 README、mylib.rb、yourlib.rb 三個文件的內容快照。而 3 個 tree 對象指針則完整描述了項目的整個目錄結構,包括目錄樹內容、文件與 blob 對象的對應關係,各個文件對應 blob 對象索引等信息。而每一次提交都會生成一個 commit 對象指針,指向 tree 對象樹的根節點,不僅如此,commit 對象還包含作者、提交人等詳細信息。

圖 13. Ruby 項目的 Git 對象關係圖
圖 13. Ruby 項目的 Git 對象關係圖

不難看出,衆多 tree 對象與 blob 一起,作爲內容節點(目錄或文件),構成了一個有向無環圖。在任何時候,通過與 commit 對象關聯的根節點,就可以遍歷出整個項目在本次提交時的所有內容。而前面提到,Git 分支本質上是指向 commit 對象的指針。兩個 Git 分支的合併,實質上是等價於兩個有向無環圖的合併,而有向無環圖可以讓 Git 更加高效判斷分支共同的父節點。因此,Git 對象模型設計賦予了開發人員最大的靈活性來任意創建分支,並在自己的分支上進行開發。

儘管以上幾種對象的類型不同,每一種對象都擁有同一長度的唯一標識,以 40 位字符串表示。實際上,圖 13 的對象標識均爲簡寫,其中,commit 對象完整的標識如下 :

98ca9e0acb0be0321191a59e1d34ba5c867fa3

爲保證對象標識的唯一性,Git 採用了 SHA1 哈希算法。這樣做,起碼有三大好處:

  • Git 只要比較對象名,就可以很快的判斷兩個對象是否相同。
  • 由於每個倉庫中"對象名"的計算方法都完全一樣,因此,如果同樣的內容存在兩個不同的倉庫中,就會存在相同的"對象名"下。
  • Git 還可以通過檢查對象內容的 SHA1 哈希值與"對象名"是否相同,來判斷對象內容是否正確。

總結一下 Git 對象模型,blob 對象即項目中的所有實體文件,包括源代碼、圖片資源、xml 配置信息等內容。特別需要強調的是,blob 對象記錄的僅僅是文件內容,而關於文件所在目錄、名字大小等信息,則統統記錄在關聯它的 tree 對象上。我們每次提交文件,都會產生一個 commit 對象,並更新改動文件所關聯的 tree 對象。

Git 三種狀態

在理解 Git 對象模型之後,我們的焦點轉向 Git 文件的檢入與檢出。Git 倉庫模型大致分爲三個工作區域,分別爲工作目錄(Working Directory),暫存區域(Stage 或 Index),以及本地倉庫(History),相應的檢入與檢出命令如圖 14 所示:

圖 14. Git 三種狀態之間的轉換(1)
圖 14. Git 三種狀態之間的轉換(1)

相關命令的簡要說明如下:

  • git add files:把當前工作文件拷貝到暫存區域。
  • git commit:在暫存區域生成文件快照並提交到本地倉庫。
  • git reset -- files:用來撤銷最後一次 git add files,也可以用 git reset 撤銷所有暫存區域文件。
  • git checkout -- files:把文件從暫存區域覆蓋到工作目錄,用來丟棄本地修改。

作爲示例,圖 15 演示瞭如何通過 git add 與 git checkout 分別在工作目錄與暫存區之間來回複製,讀者可以自行嘗試 git commit 與 git reset 命令。首先,我們在工作目錄創建一個內容爲"hello git"的 test 文件,通過 git add 命令將 test 文件複製到暫存區。然後,在工作目錄修改 test 文件,添加一行"hello git branch"。此時,暫存區的內容依然爲"hello git",沒有改變。最後,通過 git checkout 將暫存區的 test 文件覆蓋工作目錄,即放棄了本地修改,最終文件內容爲"hello git"。

圖 15. git checkout -- files 示例
圖 15. git checkout -- files 示例

實際上,工作目錄與倉庫之間的複製也可以一步到位,如圖 16 所示。

圖 16. Git 三種狀態之間的轉換(2)
圖 16. Git 三種狀態之間的轉換(2)

其中,git commit -a 等價於 git add 與 git commit,即先把文件從工作目錄複製到暫存區,然後再從暫存區複製到倉庫中。git checkout HEAD -- files 的過程剛好相反,即回滾到最後一次提交。

爲了查看工作目錄,暫存區域,以及本地倉庫的文件有哪些不同,可以使用 git diff 命令,如圖 17 所示:

圖 17. Git 三種狀態之間的比較
圖 17. Git 三種狀態之間的比較

git diff 命令相關的簡要說明如下:

  • git diff:查看尚未暫存的文件更新了哪些部分。
  • git diff --cached:查看已暫存文件和上次提交時的快照之間的差異。
  • git diff HEAD:查看未暫存文件與最新提交文件快照的區別。
  • git diff <index1> <index2>:查看不同快照之間的區別。

作爲示例,圖 18 演示了 git diff 的用法。可以看到,通過 git diff 比較,知道工作目錄比暫存區多了一行"hello git tag";而通過 git diff HEAD 比較,知道工作目錄又比倉庫最新提交文件多了兩行,分別是"hello git branch"與"hello git tag"。由此推斷,暫存區比倉庫多了一行"hello git branch",而這恰好與 git diff –cached 的結論相吻合。

圖 18. Git 三種狀態之間的比較——示例
圖 18. Git 三種狀態之間的比較——示例

以上是關於 Git 檢入與檢出操作的基礎用法,關於更詳細命令以及語法說明,可參見相關 Git 學習指南。

接下來,我們介紹 Git 更加高級的功能與特性。

Git 分支模型

前面提到,Git 中的分支本質上是一個指向 commit 對象的可變指針。Git 會維護一個默認分支——master。每一次提交之後,master 指針都會自動向前移動。而如果要創建一個新的分支,可以使用 git branch 命令:

$ git branch bugFix

這會在當前 commit 對象上新建一個分支指針,如圖 19 所示。

圖 19. 新建分支 bugFix
圖 19. 新建分支 bugFix

那麼,Git 是如何知道當前在哪個分支上工作的呢?其實答案也很簡單,它保存着一個名爲 HEAD 的特別指針,它是一個指向當前工作分支的指針。我們可以將 HEAD 想象爲當前分支的別名。在這一點上,它和 CVS、SVN 的 HEAD 概念大不相同。

運行 git branch 命令,僅僅是建立了一個新的分支,但不會自動切換到這個分支中去,所以在這個例子中,我們依然還在 master 分支裏工作。要切換到其他分支,可以執行 git checkout 命令。

$ git checkout bugFix

這樣 HEAD 就指向了 bugFix 分支,見圖 20 所示。

圖 20. 切換到 bugFix 分支
圖 20. 切換到 bugFix 分支

實際上,我們可以將分支的創建與切換兩步合二爲一。要新建並切換到該分支,運行 git checkout 並加上 -b 參數:

$ git checkout -b bugFix

接下來,再提交一次:

$ vi test.rb
$ git commit -a -m 'update copyright'

圖 21 展示了提交後的結果。非常有趣,現在 bugFix 分支向前移動了一格,而 master 分支仍然指向原先 git checkout 時所在的 commit 對象。

圖 21. 在 bugFix 分支提交文件
圖 21. 在 bugFix 分支提交文件

我們再切換到 master 分支:

$ git checkout master

其結構如圖 22 所示。這條命令做了兩件事。第一,它把 HEAD 指針移回到 master 分支;第二,把工作目錄中的文件替換成了 master 分支所指向的快照內容。也就是說,從現在開始,基於該文件的一系列提交都將始於一個較老的版本。它的主要作用在於,可以將 bugFix 分支裏作出的修改暫時取消,隔離 bugFix 分支對 master 分支的影響。在實際項目中,我們經常有這樣的需求,即採用 developer 分支開發主要版本,bugFix 分支負責修復 bug,彼此互相隔離,最後合併。

圖 22. 切換成 master 分支
圖 22. 切換成 master 分支

我們作些修改後再次提交:

$ vi test.rb
$ git commit -a -m 'made other changes'

現在我們的項目提交歷史產生了分叉,如圖 23 所示,原因是剛纔我們創建了一個分支,進行了一些工作,然後又切換到主分支進行了另一些工作。我們可以在不同分支裏反覆切換,並在時機成熟時將它們合併到一起。

圖 23. 在 master 分支提交文件
圖 23. 在 master 分支提交文件

git merge 命令把不同分支合併起來。合併前,HEAD 必須指向當前最新的提交。按使用場景不同,git merge 操作又分爲三種情況:

  • 如果另一個分支是當前提交的祖父節點,那麼 git merge 命令將什麼也不做。
  • 反過來,如果當前提交是另一個分支的祖父節點,就導致 fast-forward 合併。指向只是簡單的移動,並生成一個新的提交。
  • 否則就是一次真正的合併。默認把當前提交 (ed489 如下所示 ) 和另一個提交 (33104) 以及他們的共同祖父節點 (b325c) 進行一次三方合併。結果是先保存當前目錄和索引,然後和父節點 33104 一起做一次新提交,如圖 24 所示。
圖 24. 合併分支
圖 24. 合併分支

可以看到,git merge 命令把兩個父分支合併進行一次提交,但提交歷史不是線性的。相比之下,分支衍合命令 git rebase 在當前分支上重演另一個分支的歷史,從而保證提交歷史是線性的,如圖 25 所示。

圖 25. 衍合分支
圖 25. 衍合分支

作爲示例,我們演示關於 git merge 與 git rebase 的區別,見圖 26 所示。

圖 26. 合併分支 vs 衍合分支
圖 26. 合併分支 vs 衍合分支

有時候合併操作並不會如此順利,如果在不同的分支中都修改了同一個文件的同一部分,會造成合並衝突,Git 就無法乾淨地把兩者合到一起。此時,Git 僅作合併,但不提交,它會停下來等人爲地解決衝突,如下:

$ cat test.rb
init
master update1
master update2
bugFix update1
<<<<<<< HEAD
master updated3
=======
bugFix update2
>>>>>>> bugFix

要查看哪些文件在合併時發生衝突,可以使用 git status :

$ git status
# On branch master
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: test.rb
#
no changes added to commit (use "git add" and/or "git commit -a")

待修補發佈以後,bugFix 分支已經完成了歷史使命,我們可以使用 git branch 的 -d 選項執行刪除操作:

$ git branch -d bugFix

以上,我們介紹了 Git 分支的創建,切換,合併(線性與非線性),衝突,以及刪除。

Git 標籤

與 CVS、SVN 等其它版本控制系統一樣,Git 也支持打 Git 標籤 。在程序開發到一個階段後,我們需要打個標籤,發佈一個版本,如 0.1.2,v0.1.2 等。

Git 使用的標籤有兩種類型:輕量級的(lightweight)和含附註的(annotated)。輕量級標籤實際上就是個指向特定提交對象的引用;而含附註標籤實際上是存儲在倉庫中的一個獨立 Git 對象。相比之下,含附註標籤包含信息更多,包括自身校驗信息,標籤名字,Email,標籤日期,以及標籤說明等。含附註標籤本身也允許使用 GNU Privacy Guard (GPG) 來簽署或驗證,因此我們推薦使用含附註的標籤,以便保留相關信息。

要打上標籤,可執行以下 Git 命令:

$ git tag -a v0.1.2 -m "Release version 0.1.2"

相應地,要查看標籤,執行下列 Git 命令:

$ git tag –l

當然,也可採用 git show 命令查看標籤版本與提交對象等詳細信息。

$ git show v0.1.2

刪除標籤的 Git 命令如下:

git tag -d v0.1.2

如果我們有自己的私鑰,還可以用 GPG 來簽署標籤,只需要把之前的 -a 改爲 -s,如下

$ git tag -s v0.1.2 -m "My signed 0.1.2 tag"

要驗證已經簽署的標籤,可以先取到對應的公鑰,然後使用 git tag –v 命令驗證,如下:

$ git tag -v v0.1.2

需要注意的,默認情況下,git push 並不會把標籤傳送到遠端倉庫上。我們只能通過顯式命令才能分享標籤。其命令格式如下:

$ git push origin v0.1.2

如果希望一次性推送所有本地新增的標籤,可以使用 --tags 選項:

$ git push origin --tags

如此一來,其他人克隆共享倉庫或拉取數據同步後,也會看到這些標籤。

Git 補丁

UNIX 世界中,補丁(Patch)的概念非常重要,幾乎所有大型 UNIX 項目的普通貢獻者,都是通過補丁來提交代碼。對於 Linux 內核項目而言,普通開發者先從 Git 項目倉庫克隆下代碼,然後寫入代碼,做一個補丁,最後用 E-mail 發給 Linux 內核的維護者就可以了。

Git 提供了兩種簡單的補丁生成方案。一是使用 git diff 生成的標準補丁,二是使用 git format-patch 生成的 Git 專用補丁。這裏,我們重點介紹第二種方式,關於第一種 git diff 方式,比較簡單,這裏不做介紹。

假設我們有一個項目 myproj,其工作目錄裏最初有一個文件 test,內容是"hello git",默認提交給 master 分支。這裏,我們創建一個新分支 bugFix 用於代碼修改,如圖 27 所示:

圖 27. 創建分支
圖 27. 創建分支

接下來,我們在 test 文件裏面追加一行"fix",並使用 git format-patch 生成一個 patch,如圖 28 所示,其中,git format-patch 的 -M 選項表示這個 patch 要和哪個分支比對。

圖 28. 生成補丁
圖 28. 生成補丁

可以看到,補丁文件 0001-fix.patch 包含各種信息,不僅有 diff 的信息,還有提交者,時間等等。仔細一看你會發現,這是個 E-mail 的文件,可以直接發送。

接下來,可以使用 git am 來應用補丁,如圖 29 所示。可以看到,相比於原來的 test 文件,打上補丁後,多了一行"fix"。

圖 29. 應用補丁
圖 29. 應用補丁

關於以上兩種生成補丁方式的比較,很明顯,相比於 git diff 生成的通用補丁,git format-patch 生成的 Git 專用補丁兼容性較弱。不過,Git 專用補丁中含有補丁開發者的名字,在應用補丁時,這個名字會被記錄進版本庫。因此,目前使用 Git 的開源社區往往建議大家使用 format-patch 生成補丁。

Git 遠程倉庫操作

前面提到,Git 是分佈式版本控制系統。對於一個分佈式節點來說,其它節點的 Git 倉庫都可以作爲本地倉庫的遠程倉庫。要查看當前配置有哪些遠程倉庫,可以使用以下命令:

$ git remote

在克隆完某個項目後,至少可以看到一個名爲 origin 的遠程庫,Git 默認使用這個名字來標識你所克隆的原始倉庫。

項目進行到一個階段,要同別人分享目前的成果,可以使用 git push 命令將本地倉庫中的數據推送到遠程倉庫。

$ git push origin master

而要將遠程倉庫抓取數據到本地,可以使用 git fetch 命令,從而獲取所有本地倉庫中還沒有的數據。

$ git fetch [remote-name]

如果設置了某個分支用於跟蹤某個遠端倉庫的分支,可以使用 git pull 命令自動抓取數據下來,然後將遠端分支自動合併到本地倉庫中當前分支。從這個角度,git pull 等價於 git fetch + git merge 的功能。

$ git pull [remote-name]

關於以上幾種 Git 遠程倉庫的相關操作,其關係見圖 30 所示。要了解 Git 遠程倉庫的更多命令,如刪除與重命名等,可參閱相關 Git 操作指南。

圖 30. Git 遠程倉庫的操作
圖 30. Git 遠程倉庫的操作

CVS 遷移到 Git

對於想要從 CVS 遷移到 Git 的用戶,可以使用 git cvsimport 工具解決遷移問題,前提是安裝相關工具 git-cvs 或 cvsps。

關於 git-cvs 工具,可以使用 yum 或者 apt-get 命令安裝。以 yum 爲例,其安裝命令如下:

$ yum install git-cvs

如果是源碼編譯安裝 Git,則需要安裝 cvsps,下載地址:http://www.cobite.com/cvsps/

$ tar -zxvf cvsps-2.1.tar.gz
$ cd cvsps-2.1
$ make && make install

作爲示例,我們新建一個目錄 jt400.cvs,並將文章開頭提到的 SourceForge 託管的 CVS 項目 Toolbox for Java/JTOpen 的源碼導入到 Git 中來,操作過程如下:

$ mkdir jt400.cvs
$ cd jt400.cvs
$ export CVSROOT=:pserver:[email protected]:/cvsroot/jt400
$ cvs login
$ git cvsimport -C src src

其中,-C src 是要往 git 倉庫裏創建的項目名稱,最後那個 src 是 cvs 中要導入的模塊。

SVN 遷移到 Git

同樣,Git 也提供了 git svn 相關工具,提供 SVN 項目到 Git 的遷移,前提是安裝相關工具 subversion-perl。

$ yum install install subversion-perl

作爲示例,我們新建一個目錄 photon-android.svn,並將 googlecode 託管的 SVN 項目 photon-android 導入到 Git 中來,操作過程如下:

$ mkdir photon-android.svn
$ cd photon-android.svn
$ git svn clone http://photon-android.googlecode.com/svn/

總結

本文系統性地介紹了分佈式版本控制工具——Git,包括爲什麼使用 Git,Git 的安裝,Git 的工作原理,Git 的使用方法,CVS 與 SVN 向 Git 遷移等。有關 Git 更全面的使用方法,請參見文檔:https://github.com/progit/progit

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章