日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

开源分布式版本控制工具 —— Git 之旅

發(fā)布時(shí)間:2023/12/15 综合教程 40 生活家
生活随笔 收集整理的這篇文章主要介紹了 开源分布式版本控制工具 —— Git 之旅 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Git 主張的分布式代碼庫(kù)與文件快照的設(shè)計(jì)思想,相對(duì)于傳統(tǒng) CVS、SVN 等集中式、文件差異式版本控制工具是一種挑戰(zhàn)與顛覆。Git 帶來了離線提交、輕量級(jí)分支等諸多便利。不過,也有人質(zhì)疑 Git 的復(fù)雜性,并由此拔高了學(xué)習(xí)成本,某種程序上影響了開發(fā)者使用或者遷移 Git 的項(xiàng)目進(jìn)度,筆者同樣感同身受,這也是本文的出發(fā)點(diǎn)。

不同于各種 Git 用法指南,本文在介紹 Git 安裝、使用的同時(shí),更加注重于 Git 的設(shè)計(jì)思想、體系架構(gòu)、以及各種實(shí)用功能,包括 Git 分支模型、Git 標(biāo)簽、Git 補(bǔ)丁提交、CVS 遷移 Git、SVN 遷移 Git 等。

背景

Git 是一個(gè)開源的分布式版本控制軟件。在英式英語中,Git 指一個(gè)愚笨或者不開心的人,恐怕與 Git 發(fā)明人——Linux 教父 Linus Torvalds 當(dāng)時(shí)的自嘲心理不無關(guān)系吧。2002 年之前,Linux 內(nèi)核維護(hù)工作的絕大部分時(shí)間都浪費(fèi)在提交補(bǔ)丁與保存歸檔等繁瑣事務(wù)上。啟用版本控制工具 BitKeeper 管理 Linux 內(nèi)核成了當(dāng)務(wù)之急。不過,BitKeeper 畢竟是一款商業(yè)軟件,在經(jīng)歷了 3 年免費(fèi)使用之后,Linux 社區(qū)不得不尋求它的替代品,以便繼續(xù)托管 Linux 內(nèi)核源代碼。2005 年,迫于無奈,Linus Torvalds 自行開發(fā)了一套開源版本控制工具,并命名為 Git。

自誕生以來,Git 就以其開源、簡(jiǎn)單、快捷、分布式、高效等特點(diǎn),應(yīng)付了類似 Linux 內(nèi)核源代碼等各種復(fù)雜的項(xiàng)目開發(fā)需求。如今,Git 已經(jīng)非常成熟,被廣泛接受與使用,越來越多的項(xiàng)目都遷移到 Git 倉(cāng)庫(kù)中進(jìn)行管理。以 Eclipse 社區(qū)為例。據(jù)稱,目前 80% 的 Eclipse 基金會(huì)項(xiàng)目已經(jīng)完全使用 Git 管理,CVS 訪問權(quán)限已經(jīng)切換成只讀狀態(tài)。并且,在 Eclipse 基金會(huì)官網(wǎng)中,針對(duì)項(xiàng)目管理的介紹中已將"CVS"三個(gè)字符劃掉,而且很萌地寫道,"Ding dong, the witch is dead.",意思是"叮咚,那個(gè)老巫婆已經(jīng)掛了"。

不僅如此,筆者最近也收到了全球最大開源代碼托管平臺(tái)——SourceForge 的升級(jí)通知。其中,筆者的一個(gè)較為簡(jiǎn)單的項(xiàng)目已經(jīng)從 CVS 被系統(tǒng)默認(rèn)自動(dòng)升級(jí)到了 Git。而對(duì)于另一個(gè)較為復(fù)雜的 CVS 項(xiàng)目Toolbox for Java/JTOpen,SourceForge 并沒有自動(dòng)升級(jí),估計(jì)是等待筆者做升級(jí)前的最后準(zhǔn)備工作。筆者希望通過分享自己的 Git 學(xué)習(xí)體驗(yàn)與實(shí)踐經(jīng)驗(yàn),對(duì) Git 初學(xué)者有所裨益,這也是本文之意義所在。

為什么選擇 Git

實(shí)際上,相對(duì)于 CVS、SVN 等主流版本控制軟件,Git 的學(xué)習(xí)成本甚至?xí)摺1热纾瑢?duì)于 Subversion 用戶而言,如果能理解什么是文件、工作目錄、資源庫(kù)、版本、分支和標(biāo)簽等概念,差不多就夠用了。而對(duì)于 Git 用戶,需要理解更多更復(fù)雜的概念,包括文件、快照、工作樹、索引、本地資源庫(kù)、遠(yuǎn)程資源庫(kù)、遠(yuǎn)程、提交、分支和 Stash 等。那么,為什么軟件開發(fā)者對(duì) Git 還是趨之若鶩呢?相比于 CVS 與 SVN,Git 的優(yōu)勢(shì)到底體現(xiàn)在哪里?

關(guān)于 Git 的各種優(yōu)勢(shì),互聯(lián)網(wǎng)以及各種 Git 書籍都給出了自己的答案。筆者認(rèn)為,存儲(chǔ)快照與分布式的設(shè)計(jì)思想是 Git 的 2 大看點(diǎn),理由如下:

第一,Git 底層自行維護(hù)的存儲(chǔ)文件系統(tǒng)是一大亮點(diǎn)。CVS、SVN 底層采用的為增量式文件系統(tǒng),如圖 1 所示。增量式文件系統(tǒng)的特點(diǎn)是:當(dāng)文件變動(dòng)發(fā)生提交時(shí),該文件系統(tǒng)存儲(chǔ)的是文件的差異信息。

圖 1. CVS、SVN 記錄文件內(nèi)容差異

同樣是文件變更提交,Git 底層文件系統(tǒng)存儲(chǔ)的則為文件快照,即整個(gè)文件內(nèi)容,并保存指向快照的索引,如圖 2 所示。考慮到性能因素,如果文件內(nèi)容沒有發(fā)生任何變化,該文件系統(tǒng)則不會(huì)重復(fù)保存文件,只是簡(jiǎn)單地保存文件的鏈接。

圖 2. Git 記錄整個(gè)文件快照

Git 之所以選擇這樣的底層存儲(chǔ)數(shù)據(jù)結(jié)構(gòu),主要是為了提高 Git 分支的使用效率。實(shí)際上,Git 分支本質(zhì)上是一個(gè)指向索引對(duì)象的可變指針,而每一個(gè)索引對(duì)象又指向文件快照,如圖 3 所示。

圖 3. Git 分支對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)

這樣一來,創(chuàng)建分支可以瞬間完成,幾乎不需要花費(fèi)太多代價(jià)。換句話說,Git 分支是廉價(jià)的、輕量級(jí)的。我們看看各種 CVS、SVN 項(xiàng)目,分支通常意味著源代碼的完整拷貝,其代價(jià)是昂貴的、重量級(jí)的。而對(duì)于大型項(xiàng)目來說,創(chuàng)建各種分支又是十分必要的,這與 Git 鼓勵(lì)頻繁創(chuàng)建與合并分支的理念相吻合。

第二,Git 版本控制系統(tǒng)的設(shè)計(jì)思想是"去中心化"。傳統(tǒng)的 CVS 、SVN 等工具采用的是 C/S 架構(gòu),只有一個(gè)中心代碼倉(cāng)庫(kù),位于服務(wù)器端。而一旦由于服務(wù)器系統(tǒng)宕機(jī)、網(wǎng)絡(luò)不通等各種原因造成中心倉(cāng)庫(kù)不可用,整個(gè) CVS 、SVN 系統(tǒng)的代碼檢入與檢出就癱瘓了。即便考慮到高可用性,通過遷移另一個(gè)中心倉(cāng)庫(kù)繼續(xù)代碼提交操作,相應(yīng)的運(yùn)營(yíng)維護(hù)成本也會(huì)隨之上升。

為了擺脫對(duì)中心倉(cāng)庫(kù)的依賴,Git 的初始設(shè)計(jì)目標(biāo)之一就是分布式控制管理。我們給出一個(gè)樣例,如圖 4 所示。假如我們成立一個(gè)項(xiàng)目組,開發(fā)者主要由 Alice、Bob、Clair、David 四名成員組成。其中,除了中心倉(cāng)庫(kù) origin(Git 默認(rèn)遠(yuǎn)程倉(cāng)庫(kù)名稱)之外,每一名成員各自負(fù)責(zé)一個(gè)本地倉(cāng)庫(kù)。從分布式的觀點(diǎn)來看,David 可看成是 Alice 的遠(yuǎn)程倉(cāng)庫(kù),反過來也是一樣。Git 分布式的設(shè)計(jì)理念有助于減少對(duì)中心倉(cāng)庫(kù)的依賴,從而有效降低中心倉(cāng)庫(kù)的負(fù)載,改善代碼提交的靈活性。

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

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

有關(guān) Git 更多詳細(xì)信息,請(qǐng)參考 Git 官方網(wǎng)站:http://git-scm.com/?。

工欲善其事,必先利其器。在理解 Git 靈活的快照存儲(chǔ)與分布式設(shè)計(jì)理念之后,我們介紹 Git 針對(duì)不同操作系統(tǒng)的安裝過程。需要指出的是,這里僅僅粗線條地介紹 Git 的安裝方法,至于 Git 安裝前提條件、安裝過程出現(xiàn)的問題診斷等更加詳細(xì)的內(nèi)容描述,均不在本文的討論范圍。

如何安裝 Git

總結(jié)起來,Git 安裝方式通常分為兩種:一種是選擇 Git 源碼編譯安裝;另一種使用針對(duì)特定平臺(tái)的二進(jìn)制安裝包,又可以細(xì)分為 Linux、Mac、Windows 等,其安裝說明如下。

1.源碼編譯安裝

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

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

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

$ 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 命令,檢驗(yàn)安裝是否成功,如圖 5 所示。可以看到,我們已經(jīng)成功安裝 Git 了。

圖 5. 通過源碼安裝 Git

2.在 Linux 上安裝

要在 Linux 上安裝預(yù)編譯好的 Git 二進(jìn)制安裝包,可選擇系統(tǒng)支持的軟件包管理器。對(duì)于紅帽 Linux,使用 yum 命令安裝:

$ yum install git-core

而對(duì)于 Ubuntu 這類 Debian 體系的 Linux 系統(tǒng),使用 apt-get 命令安裝:

$ apt-get install git-core

由于此種安裝方式非常簡(jiǎn)單,這里不做貼圖展示了。

3.在 Mac 上安裝

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

圖 6. 從 Mac 上安裝 Git

4.在 Windows 上安裝

與之前所述的 Mac 安裝 Git 一樣,在 Windows 上安裝 Git 也同樣輕松。根據(jù)用戶的使用習(xí)慣,我們又可以大致分為三類:

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

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

對(duì)于習(xí)慣 Tortoise 風(fēng)格的用戶,可以選擇 TortoiseGit 安裝包,安裝后的右鍵截圖如圖 8 所示。TortoiseGit 的下載地址為:http://code.google.com/p/tortoisegit/。

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

而對(duì)于習(xí)慣 Eclipse 風(fēng)格的用戶,可以選擇 Eclipse 插件——EGit 方式安裝,其 Git Repositories 視圖截圖如圖 9 所示。EGit 的下載地址為:http://download.eclipse.org/egit/updates。

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

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

$ git config --global user.name "Pi Guang Ming"
$ git config --global user.email piguangming@gmail.com

到此為止,我們已經(jīng)介紹了 Git 的分布式模型、快照模型、針對(duì)不同操作系統(tǒng)平臺(tái)的 Git 安裝之后,接下來是本文的主題內(nèi)容,即 Git 的使用。

如何使用 Git

前面提到,這一部分是本文的重點(diǎn)。我們將主要精力集中在 Git 底層的工作原理、以及實(shí)際工程中較為實(shí)用的 Git 分支、標(biāo)簽、補(bǔ)丁、CVS 與 SVN 針對(duì) Git 的遷移等,關(guān)于 Git 的各種基礎(chǔ)命令語法、解釋說明,以及本文沒有涉及到的內(nèi)容,均可參見 Git 相關(guān)使用指南。

創(chuàng)建 Git 項(xiàng)目倉(cāng)庫(kù)

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

針對(duì)第一類 Git 倉(cāng)庫(kù),我們可以使用 git init 命令創(chuàng)建一個(gè)嶄新的 Git 項(xiàng)目倉(cāng)庫(kù),如下:

$ git init

初始化 Git 后,在當(dāng)前目錄下會(huì)出現(xiàn)一個(gè)名為 .git 的隱藏目錄,如圖 10 所示。

圖 10. .git 目錄

之所以特意強(qiáng)調(diào) .git 目錄,是因?yàn)樗种匾?duì)于一個(gè) Git 倉(cāng)庫(kù)來說,其 .git 目錄保存了整個(gè) Git 項(xiàng)目的所有數(shù)據(jù)與資源。關(guān)于 .git 目錄中各種文件的簡(jiǎn)要解釋說明,如表 1 所示。如果需要了解詳細(xì)信息,請(qǐng)參見 Git 官方網(wǎng)站:http://git-scm.com/?。

表 1 .git 目錄簡(jiǎn)要說明
子目錄名 簡(jiǎn)要描述
branches Git 項(xiàng)目分支信息,新版 Git 已經(jīng)不再使用該目錄。
config Git 項(xiàng)目配置信息
description Git 項(xiàng)目描述信息
HEAD 指向 Git 項(xiàng)目當(dāng)前分支的頭指針
hooks 默認(rèn)的"hooks"腳本,被特定事件發(fā)生前后觸發(fā)。
info 里面含一個(gè) exclude 文件,指 Git 項(xiàng)目要忽略的文件。
objects Git 的數(shù)據(jù)對(duì)象,包括:commits, trees, blobs, tags。
refs 指向所有 Git 項(xiàng)目分支的指針

針對(duì)第二類 Git 倉(cāng)庫(kù),我們不需要 git init 初始化倉(cāng)庫(kù),取而代之的是,使用 git clone 直接將遠(yuǎn)程鏡像克隆到本地倉(cāng)庫(kù)。這里,我們以下載 Git 軟件本身的源代碼為例,其 git clone 命令如下:

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

通過 ls git 命令,我們可以查看 Git 倉(cāng)庫(kù)中的內(nèi)容,如圖 11 所示。需要說明的是,針對(duì)遠(yuǎn)程倉(cāng)庫(kù)的鏡像,實(shí)際拷貝的就是 .git 目錄下的數(shù)據(jù),然后根據(jù)元數(shù)據(jù)恢復(fù)成原來的整個(gè)項(xiàng)目結(jié)構(gòu),也即是圖 11 所示的內(nèi)容。

圖 11. 克隆 Git 源代碼

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

Git 對(duì)象模型

應(yīng)該說,Git 對(duì)象模型是整個(gè) Git 設(shè)計(jì)思想中最核心的部分。理解 Git 對(duì)象模型是理解整個(gè) Git 的關(guān)鍵。簡(jiǎn)單來說,每個(gè) Git 對(duì)象包含三部分:類型,大小和內(nèi)容。其中,對(duì)象的類型又分為 commits, trees, blobs, tags,其簡(jiǎn)要說明如下:

blob 對(duì)象:一塊二進(jìn)制數(shù)據(jù),用來存儲(chǔ)文件數(shù)據(jù),通常是一個(gè)文件。
tree 對(duì)象:指向 blob 對(duì)象或是其它 tree 對(duì)象的指針,一般用來表示內(nèi)容之間的目錄層次關(guān)系。
commit 對(duì)象:一個(gè) commit 對(duì)象只指向一個(gè) tree 對(duì)象,用來標(biāo)記項(xiàng)目某一個(gè)特定時(shí)間點(diǎn)的狀態(tài),如時(shí)間戳、父對(duì)象、作者、提交者等。
tag 對(duì)象:與 CVS、SVN 標(biāo)簽的概念類似。

接下來,我們結(jié)合一個(gè)示例來解釋不同 Git 對(duì)象之間的關(guān)系。圖 12 展示的是一個(gè)樣例 Ruby 項(xiàng)目,可以看出,這個(gè)例子非常簡(jiǎn)單,僅作示意。

圖 12. Ruby 項(xiàng)目的目錄層次結(jié)構(gòu)

如果我們把該項(xiàng)目提交到 Git 倉(cāng)庫(kù)中,那么它的 Git 對(duì)象關(guān)系就如圖 13 所示。其中,3 個(gè) blob 對(duì)象分別對(duì)應(yīng) README、mylib.rb、yourlib.rb 三個(gè)文件的內(nèi)容快照。而 3 個(gè) tree 對(duì)象指針則完整描述了項(xiàng)目的整個(gè)目錄結(jié)構(gòu),包括目錄樹內(nèi)容、文件與 blob 對(duì)象的對(duì)應(yīng)關(guān)系,各個(gè)文件對(duì)應(yīng) blob 對(duì)象索引等信息。而每一次提交都會(huì)生成一個(gè) commit 對(duì)象指針,指向 tree 對(duì)象樹的根節(jié)點(diǎn),不僅如此,commit 對(duì)象還包含作者、提交人等詳細(xì)信息。

圖 13. Ruby 項(xiàng)目的 Git 對(duì)象關(guān)系圖

不難看出,眾多 tree 對(duì)象與 blob 一起,作為內(nèi)容節(jié)點(diǎn)(目錄或文件),構(gòu)成了一個(gè)有向無環(huán)圖。在任何時(shí)候,通過與 commit 對(duì)象關(guān)聯(lián)的根節(jié)點(diǎn),就可以遍歷出整個(gè)項(xiàng)目在本次提交時(shí)的所有內(nèi)容。而前面提到,Git 分支本質(zhì)上是指向 commit 對(duì)象的指針。兩個(gè) Git 分支的合并,實(shí)質(zhì)上是等價(jià)于兩個(gè)有向無環(huán)圖的合并,而有向無環(huán)圖可以讓 Git 更加高效判斷分支共同的父節(jié)點(diǎn)。因此,Git 對(duì)象模型設(shè)計(jì)賦予了開發(fā)人員最大的靈活性來任意創(chuàng)建分支,并在自己的分支上進(jìn)行開發(fā)。

盡管以上幾種對(duì)象的類型不同,每一種對(duì)象都擁有同一長(zhǎng)度的唯一標(biāo)識(shí),以 40 位字符串表示。實(shí)際上,圖 13 的對(duì)象標(biāo)識(shí)均為簡(jiǎn)寫,其中,commit 對(duì)象完整的標(biāo)識(shí)如下 :

98ca9e0acb0be0321191a59e1d34ba5c867fa3

為保證對(duì)象標(biāo)識(shí)的唯一性,Git 采用了 SHA1 哈希算法。這樣做,起碼有三大好處:

Git 只要比較對(duì)象名,就可以很快的判斷兩個(gè)對(duì)象是否相同。
由于每個(gè)倉(cāng)庫(kù)中"對(duì)象名"的計(jì)算方法都完全一樣,因此,如果同樣的內(nèi)容存在兩個(gè)不同的倉(cāng)庫(kù)中,就會(huì)存在相同的"對(duì)象名"下。
Git 還可以通過檢查對(duì)象內(nèi)容的 SHA1 哈希值與"對(duì)象名"是否相同,來判斷對(duì)象內(nèi)容是否正確。

總結(jié)一下 Git 對(duì)象模型,blob 對(duì)象即項(xiàng)目中的所有實(shí)體文件,包括源代碼、圖片資源、xml 配置信息等內(nèi)容。特別需要強(qiáng)調(diào)的是,blob 對(duì)象記錄的僅僅是文件內(nèi)容,而關(guān)于文件所在目錄、名字大小等信息,則統(tǒng)統(tǒng)記錄在關(guān)聯(lián)它的 tree 對(duì)象上。我們每次提交文件,都會(huì)產(chǎn)生一個(gè) commit 對(duì)象,并更新改動(dòng)文件所關(guān)聯(lián)的 tree 對(duì)象。

Git 三種狀態(tài)

在理解 Git 對(duì)象模型之后,我們的焦點(diǎn)轉(zhuǎn)向 Git 文件的檢入與檢出。Git 倉(cāng)庫(kù)模型大致分為三個(gè)工作區(qū)域,分別為工作目錄(Working Directory),暫存區(qū)域(Stage 或 Index),以及本地倉(cāng)庫(kù)(History),相應(yīng)的檢入與檢出命令如圖 14 所示:

圖 14. Git 三種狀態(tài)之間的轉(zhuǎn)換(1)

相關(guān)命令的簡(jiǎn)要說明如下:

git add files:把當(dāng)前工作文件拷貝到暫存區(qū)域。
git commit:在暫存區(qū)域生成文件快照并提交到本地倉(cāng)庫(kù)。
git reset -- files:用來撤銷最后一次 git add files,也可以用 git reset 撤銷所有暫存區(qū)域文件。
git checkout -- files:把文件從暫存區(qū)域覆蓋到工作目錄,用來丟棄本地修改。

作為示例,圖 15 演示了如何通過 git add 與 git checkout 分別在工作目錄與暫存區(qū)之間來回復(fù)制,讀者可以自行嘗試 git commit 與 git reset 命令。首先,我們?cè)诠ぷ髂夸泟?chuàng)建一個(gè)內(nèi)容為"hello git"的 test 文件,通過 git add 命令將 test 文件復(fù)制到暫存區(qū)。然后,在工作目錄修改 test 文件,添加一行"hello git branch"。此時(shí),暫存區(qū)的內(nèi)容依然為"hello git",沒有改變。最后,通過 git checkout 將暫存區(qū)的 test 文件覆蓋工作目錄,即放棄了本地修改,最終文件內(nèi)容為"hello git"。

圖 15. git checkout -- files 示例

實(shí)際上,工作目錄與倉(cāng)庫(kù)之間的復(fù)制也可以一步到位,如圖 16 所示。

圖 16. Git 三種狀態(tài)之間的轉(zhuǎn)換(2)

其中,git commit -a等價(jià)于 git add 與 git commit,即先把文件從工作目錄復(fù)制到暫存區(qū),然后再?gòu)臅捍鎱^(qū)復(fù)制到倉(cāng)庫(kù)中。git checkout HEAD --files的過程剛好相反,即回滾到最后一次提交。

為了查看工作目錄,暫存區(qū)域,以及本地倉(cāng)庫(kù)的文件有哪些不同,可以使用 git diff 命令,如圖 17 所示:

圖 17. Git 三種狀態(tài)之間的比較

git diff 命令相關(guān)的簡(jiǎn)要說明如下:

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

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

圖 18. Git 三種狀態(tài)之間的比較——示例

以上是關(guān)于 Git 檢入與檢出操作的基礎(chǔ)用法,關(guān)于更詳細(xì)命令以及語法說明,可參見相關(guān) Git 學(xué)習(xí)指南。

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

Git 分支模型

前面提到,Git 中的分支本質(zhì)上是一個(gè)指向 commit 對(duì)象的可變指針。Git 會(huì)維護(hù)一個(gè)默認(rèn)分支——master。每一次提交之后,master 指針都會(huì)自動(dòng)向前移動(dòng)。而如果要?jiǎng)?chuàng)建一個(gè)新的分支,可以使用 git branch 命令:

$ git branch bugFix

這會(huì)在當(dāng)前 commit 對(duì)象上新建一個(gè)分支指針,如圖 19 所示。

圖 19. 新建分支 bugFix

那么,Git 是如何知道當(dāng)前在哪個(gè)分支上工作的呢?其實(shí)答案也很簡(jiǎn)單,它保存著一個(gè)名為 HEAD 的特別指針,它是一個(gè)指向當(dāng)前工作分支的指針。我們可以將 HEAD 想象為當(dāng)前分支的別名。在這一點(diǎn)上,它和 CVS、SVN 的 HEAD 概念大不相同。

運(yùn)行 git branch 命令,僅僅是建立了一個(gè)新的分支,但不會(huì)自動(dòng)切換到這個(gè)分支中去,所以在這個(gè)例子中,我們依然還在 master 分支里工作。要切換到其他分支,可以執(zhí)行 git checkout 命令。

$ git checkout bugFix

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

圖 20. 切換到 bugFix 分支

實(shí)際上,我們可以將分支的創(chuàng)建與切換兩步合二為一。要新建并切換到該分支,運(yùn)行 git checkout 并加上 -b 參數(shù):

$ git checkout -b bugFix

接下來,再提交一次:

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

圖 21 展示了提交后的結(jié)果。非常有趣,現(xiàn)在 bugFix 分支向前移動(dòng)了一格,而 master 分支仍然指向原先 git checkout 時(shí)所在的 commit 對(duì)象。

圖 21. 在 bugFix 分支提交文件

我們?cè)偾袚Q到 master 分支:

$ git checkout master

其結(jié)構(gòu)如圖 22 所示。這條命令做了兩件事。第一,它把 HEAD 指針移回到 master 分支;第二,把工作目錄中的文件替換成了 master 分支所指向的快照內(nèi)容。也就是說,從現(xiàn)在開始,基于該文件的一系列提交都將始于一個(gè)較老的版本。它的主要作用在于,可以將 bugFix 分支里作出的修改暫時(shí)取消,隔離 bugFix 分支對(duì) master 分支的影響。在實(shí)際項(xiàng)目中,我們經(jīng)常有這樣的需求,即采用 developer 分支開發(fā)主要版本,bugFix 分支負(fù)責(zé)修復(fù) bug,彼此互相隔離,最后合并。

圖 22. 切換成 master 分支

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

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

現(xiàn)在我們的項(xiàng)目提交歷史產(chǎn)生了分叉,如圖 23 所示,原因是剛才我們創(chuàng)建了一個(gè)分支,進(jìn)行了一些工作,然后又切換到主分支進(jìn)行了另一些工作。我們可以在不同分支里反復(fù)切換,并在時(shí)機(jī)成熟時(shí)將它們合并到一起。

圖 23. 在 master 分支提交文件

git merge 命令把不同分支合并起來。合并前,HEAD 必須指向當(dāng)前最新的提交。按使用場(chǎng)景不同,git merge 操作又分為三種情況:

如果另一個(gè)分支是當(dāng)前提交的祖父節(jié)點(diǎn),那么 git merge 命令將什么也不做。
反過來,如果當(dāng)前提交是另一個(gè)分支的祖父節(jié)點(diǎn),就導(dǎo)致 fast-forward 合并。指向只是簡(jiǎn)單的移動(dòng),并生成一個(gè)新的提交。
否則就是一次真正的合并。默認(rèn)把當(dāng)前提交 (ed489 如下所示 ) 和另一個(gè)提交 (33104) 以及他們的共同祖父節(jié)點(diǎn) (b325c) 進(jìn)行一次三方合并。結(jié)果是先保存當(dāng)前目錄和索引,然后和父節(jié)點(diǎn) 33104 一起做一次新提交,如圖 24 所示。

圖 24. 合并分支

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

圖 25. 衍合分支

作為示例,我們演示關(guān)于 git merge 與 git rebase 的區(qū)別,見圖 26 所示。

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

有時(shí)候合并操作并不會(huì)如此順利,如果在不同的分支中都修改了同一個(gè)文件的同一部分,會(huì)造成合并沖突,Git 就無法干凈地把兩者合到一起。此時(shí),Git 僅作合并,但不提交,它會(huì)停下來等人為地解決沖突,如下:

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

要查看哪些文件在合并時(shí)發(fā)生沖突,可以使用 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")

待修補(bǔ)發(fā)布以后,bugFix 分支已經(jīng)完成了歷史使命,我們可以使用 git branch 的 -d 選項(xiàng)執(zhí)行刪除操作:

$ git branch -d bugFix

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

Git 標(biāo)簽

與 CVS、SVN 等其它版本控制系統(tǒng)一樣,Git 也支持打 Git 標(biāo)簽。在程序開發(fā)到一個(gè)階段后,我們需要打個(gè)標(biāo)簽,發(fā)布一個(gè)版本,如0.1.2,v0.1.2 等。

Git 使用的標(biāo)簽有兩種類型:輕量級(jí)的(lightweight)和含附注的(annotated)。輕量級(jí)標(biāo)簽實(shí)際上就是個(gè)指向特定提交對(duì)象的引用;而含附注標(biāo)簽實(shí)際上是存儲(chǔ)在倉(cāng)庫(kù)中的一個(gè)獨(dú)立 Git 對(duì)象。相比之下,含附注標(biāo)簽包含信息更多,包括自身校驗(yàn)信息,標(biāo)簽名字,Email,標(biāo)簽日期,以及標(biāo)簽說明等。含附注標(biāo)簽本身也允許使用 GNU Privacy Guard (GPG) 來簽署或驗(yàn)證,因此我們推薦使用含附注的標(biāo)簽,以便保留相關(guān)信息。

要打上標(biāo)簽,可執(zhí)行以下 Git 命令:

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

相應(yīng)地,要查看標(biāo)簽,執(zhí)行下列 Git 命令:

$ git tag –l

當(dāng)然,也可采用 git show 命令查看標(biāo)簽版本與提交對(duì)象等詳細(xì)信息。

$ git show v0.1.2

刪除標(biāo)簽的 Git 命令如下:

git tag -d v0.1.2

如果我們有自己的私鑰,還可以用 GPG 來簽署標(biāo)簽,只需要把之前的 -a 改為 -s,如下

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

要驗(yàn)證已經(jīng)簽署的標(biāo)簽,可以先取到對(duì)應(yīng)的公鑰,然后使用 git tag –v 命令驗(yàn)證,如下:

$ git tag -v v0.1.2

需要注意的,默認(rèn)情況下,git push 并不會(huì)把標(biāo)簽傳送到遠(yuǎn)端倉(cāng)庫(kù)上。我們只能通過顯式命令才能分享標(biāo)簽。其命令格式如下:

$ git push origin v0.1.2

如果希望一次性推送所有本地新增的標(biāo)簽,可以使用 --tags 選項(xiàng):

$ git push origin --tags

如此一來,其他人克隆共享倉(cāng)庫(kù)或拉取數(shù)據(jù)同步后,也會(huì)看到這些標(biāo)簽。

Git 補(bǔ)丁

UNIX 世界中,補(bǔ)丁(Patch)的概念非常重要,幾乎所有大型 UNIX 項(xiàng)目的普通貢獻(xiàn)者,都是通過補(bǔ)丁來提交代碼。對(duì)于 Linux 內(nèi)核項(xiàng)目而言,普通開發(fā)者先從 Git 項(xiàng)目倉(cāng)庫(kù)克隆下代碼,然后寫入代碼,做一個(gè)補(bǔ)丁,最后用 E-mail 發(fā)給 Linux 內(nèi)核的維護(hù)者就可以了。

Git 提供了兩種簡(jiǎn)單的補(bǔ)丁生成方案。一是使用 git diff 生成的標(biāo)準(zhǔn)補(bǔ)丁,二是使用 git format-patch 生成的 Git 專用補(bǔ)丁。這里,我們重點(diǎn)介紹第二種方式,關(guān)于第一種 git diff 方式,比較簡(jiǎn)單,這里不做介紹。

假設(shè)我們有一個(gè)項(xiàng)目 myproj,其工作目錄里最初有一個(gè)文件 test,內(nèi)容是"hello git",默認(rèn)提交給 master 分支。這里,我們創(chuàng)建一個(gè)新分支 bugFix 用于代碼修改,如圖 27 所示:

圖 27. 創(chuàng)建分支

接下來,我們?cè)?test 文件里面追加一行"fix",并使用 git format-patch 生成一個(gè) patch,如圖 28 所示,其中,git format-patch 的 -M 選項(xiàng)表示這個(gè) patch 要和哪個(gè)分支比對(duì)。

圖 28. 生成補(bǔ)丁

可以看到,補(bǔ)丁文件 0001-fix.patch 包含各種信息,不僅有 diff 的信息,還有提交者,時(shí)間等等。仔細(xì)一看你會(huì)發(fā)現(xiàn),這是個(gè) E-mail 的文件,可以直接發(fā)送。

接下來,可以使用 git am 來應(yīng)用補(bǔ)丁,如圖 29 所示。可以看到,相比于原來的 test 文件,打上補(bǔ)丁后,多了一行"fix"。

圖 29. 應(yīng)用補(bǔ)丁

關(guān)于以上兩種生成補(bǔ)丁方式的比較,很明顯,相比于 git diff 生成的通用補(bǔ)丁,git format-patch 生成的 Git 專用補(bǔ)丁兼容性較弱。不過,Git 專用補(bǔ)丁中含有補(bǔ)丁開發(fā)者的名字,在應(yīng)用補(bǔ)丁時(shí),這個(gè)名字會(huì)被記錄進(jìn)版本庫(kù)。因此,目前使用 Git 的開源社區(qū)往往建議大家使用 format-patch 生成補(bǔ)丁。

Git 遠(yuǎn)程倉(cāng)庫(kù)操作

前面提到,Git 是分布式版本控制系統(tǒng)。對(duì)于一個(gè)分布式節(jié)點(diǎn)來說,其它節(jié)點(diǎn)的 Git 倉(cāng)庫(kù)都可以作為本地倉(cāng)庫(kù)的遠(yuǎn)程倉(cāng)庫(kù)。要查看當(dāng)前配置有哪些遠(yuǎn)程倉(cāng)庫(kù),可以使用以下命令:

$ git remote

在克隆完某個(gè)項(xiàng)目后,至少可以看到一個(gè)名為 origin 的遠(yuǎn)程庫(kù),Git 默認(rèn)使用這個(gè)名字來標(biāo)識(shí)你所克隆的原始倉(cāng)庫(kù)。

項(xiàng)目進(jìn)行到一個(gè)階段,要同別人分享目前的成果,可以使用 git push 命令將本地倉(cāng)庫(kù)中的數(shù)據(jù)推送到遠(yuǎn)程倉(cāng)庫(kù)。

$ git push origin master

而要將遠(yuǎn)程倉(cāng)庫(kù)抓取數(shù)據(jù)到本地,可以使用 git fetch 命令,從而獲取所有本地倉(cāng)庫(kù)中還沒有的數(shù)據(jù)。

$ git fetch [remote-name]

如果設(shè)置了某個(gè)分支用于跟蹤某個(gè)遠(yuǎn)端倉(cāng)庫(kù)的分支,可以使用 git pull 命令自動(dòng)抓取數(shù)據(jù)下來,然后將遠(yuǎn)端分支自動(dòng)合并到本地倉(cāng)庫(kù)中當(dāng)前分支。從這個(gè)角度,git pull 等價(jià)于 git fetch + git merge 的功能。

$ git pull [remote-name]

關(guān)于以上幾種 Git 遠(yuǎn)程倉(cāng)庫(kù)的相關(guān)操作,其關(guān)系見圖 30 所示。要了解 Git 遠(yuǎn)程倉(cāng)庫(kù)的更多命令,如刪除與重命名等,可參閱相關(guān) Git 操作指南。

圖 30. Git 遠(yuǎn)程倉(cāng)庫(kù)的操作

CVS 遷移到 Git

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

關(guān)于 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

作為示例,我們新建一個(gè)目錄 jt400.cvs,并將文章開頭提到的 SourceForge 托管的 CVS 項(xiàng)目 Toolbox for Java/JTOpen 的源碼導(dǎo)入到 Git 中來,操作過程如下:

$ mkdir jt400.cvs
$ cd jt400.cvs
$ export CVSROOT=:pserver:piguangming@jt400.cvs.sourceforge.net:/cvsroot/jt400
$ cvs login
$ git cvsimport -C src src

其中,-C src 是要往 git 倉(cāng)庫(kù)里創(chuàng)建的項(xiàng)目名稱,最后那個(gè) src 是 cvs 中要導(dǎo)入的模塊。

SVN 遷移到 Git

同樣,Git 也提供了 git svn 相關(guān)工具,提供 SVN 項(xiàng)目到 Git 的遷移,前提是安裝相關(guān)工具 subversion-perl。

$ yum install install subversion-perl

作為示例,我們新建一個(gè)目錄 photon-android.svn,并將 googlecode 托管的 SVN 項(xiàng)目 photon-android 導(dǎo)入到 Git 中來,操作過程如下:

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

總結(jié)

本文系統(tǒng)性地介紹了分布式版本控制工具——Git,包括為什么使用 Git,Git 的安裝,Git 的工作原理,Git 的使用方法,CVS 與 SVN 向 Git 遷移等。有關(guān) Git 更全面的使用方法,請(qǐng)參見文檔:https://github.com/progit/progit。

總結(jié)

以上是生活随笔為你收集整理的开源分布式版本控制工具 —— Git 之旅的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。