Git 中文教程

介紹

Git --- The stupid content tracker, 傻瓜內容跟蹤器。Linus 是這樣給我們介紹 Git 的。

Git 是用於 Linux 內核開發的版本控制工具。與常用的版本控制工具 CVS, Subversion 等不同, 它採用了分佈式版本庫的方式,不必服務器端軟件支持,使源代碼的發佈和交流極其方便。 Git 的速度很快,這對於諸如 Linux kernel 這樣的大項目來說自然很重要。 Git 最爲出色的是它的合併跟蹤(merge tracing)能力。

實際上內核開發團隊決定開始開發和使用 Git 來作爲內核開發的版本控制系統的時候, 世界開源社羣的反對聲音不少,最大的理由是 Git 太艱澀難懂,從 Git 的內部工作機制來說,的確是這樣。 但是隨着開發的深入,Git 的正常使用都由一些友好的腳本命令來執行,使 Git 變得非常好用, 即使是用來管理我們自己的開發項目,Git 都是一個友好,有力的工具。 現在,越來越多的著名項目採用 Git 來管理項目開發,例如:wine, U-boot 等,詳情看 http://www.kernel.org/git

作爲開源自由原教旨主義項目,Git 沒有對版本庫的瀏覽和修改做任何的權限限制。 它只適用於 Linux / Unix 平臺,沒有 Windows 版本,目前也沒有這樣的開發計劃。

本文將以 Git 官方文檔 Tutorial, core-tutorial 和 Everyday GIT 作爲藍本翻譯整理,但是暫時去掉了對 Git 內部工作機制的闡述, 力求簡明扼要,並加入了作者使用 Git 的過程中的一些心得體會,注意事項,以及更多的例子。 建議你最好通過你所使用的 Unix / Linux 發行版的安裝包來安裝 Git, 你可以在線瀏覽本文 ,也可以通過下面的命令來得到本文最新的版本庫,並且通過後面的學習用 Git 作爲工具參加到本文的創作中來。

$ git-clone http://www.bitsun.com/git/gittutorcn.git

創建一個版本庫:git-init-db

創建一個 Git 版本庫是很容易的,只要用命令 git-init-db 就可以了。 現在我們來爲本文的寫作創建一個版本庫:

$ mkdir gittutorcn
$ cd gittutorcn
$ git-init-db

git 將會作出以下的迴應

defaulting to local storage area

這樣,一個空的版本庫就創建好了,並在當前目錄中創建一個叫 .git 的子目錄。 你可以用 ls -a 查看一下,並請注意其中的三項內容:

  • 一個叫 HEAD 的文件,我們現在來查看一下它的內容:

    $ cat .git/HEAD

    現在 HEAD 的內容應該是這樣:

    ref: refs/heads/master

    我們可以看到,HEAD 文件中的內容其實只是包含了一個索引信息, 並且,這個索引將總是指向你的項目中的當前開發分支。

  • 一個叫 objects 的子目錄,它包含了你的項目中的所有對象,我們不必直接地瞭解到這些對象內容, 我們應該關心是存放在這些對象中的項目的數據。

    Note
    關於 git 對象的分類,以及 git 對象數據庫的說明, 請參看 [Discussion]
  • 一個叫 refs 的子目錄,它用來保存指向對象的索引。

具體地說,子目錄 refs 包含着兩個子目錄叫 heads 和 tags, 就像他們的名字所表達的意味一樣:他們存放了不同的開發分支的的索引, 或者是你用來標定版本的標籤的索引。

請注意:master 是默認的分支,這也是爲什麼 .git/HEAD 創建的時候就指向 master 的原因,儘管目前它其實並不存在。 git 將假設你會在 master 上開始並展開你以後的工作,除非你自己創建你自己的分支。

另外,這只是一個約定俗成的習慣而已,實際上你可以將你的工作分支叫任何名字, 而不必在版本庫中一定要有一個叫 master 的分支,儘管很多 git 工具都認爲master 分支是存在的。

現在已經創建好了一個 git 版本庫,但是它是空的,還不能做任何事情,下一步就是怎麼向版本庫植入數據了。

植入內容跟蹤信息:git-add

爲了簡明起見,我們創建兩個文件作爲練習:

$ echo "Hello world" > hello
$ echo "Silly example" > example

我們再用 git-add 命令將這兩個文件加入到版本庫文件索引當中:

$ git-add hello example

git-add 實際上是個腳本命令,它是對 git 內核命令 git-update-index 的調用。 因此上面的命令和下面的命令其實是等價的:

$ git-update-index --add hello example

如果你要將某個文件從 git 的目錄跟蹤系統中清除出去,同樣可以用 git-update-index 命令。例如:

$ git-update-index --force-remove foo.c
Note
git-add 可以將某個目錄下的所有內容全都納入內容跟蹤之下,例如: git-add ./path/to/your/wanted 。但是在這樣做之前, 應該注意先將一些我們不希望跟蹤的文件清理掉, 例如,gcc 編譯出來的 *.o 文件,vim 的交換文件 .*.swp 之類。

應該建立一個清晰的概念就是,git-add 和 git-update-index 只是刷新了 git 的跟蹤信息,hello 和 example 這兩個文件中的內容並沒有提交到 git 的內容跟蹤範疇之內。

提交內容到版本庫:git-commit

既然我們刷新了 Git 的跟蹤信息,現在我們看看版本庫的狀態:

$ git-status

我們能看到 git 的狀態提示:

#
# Initial commit
#
#
# Updated but not checked in:
#   (will commit)
#
#       new file: example
#       new file: hello
#

提示信息告訴我們版本庫中加入了兩個新的文件,並且 git 提示我們提交這些文件, 我們可以通過 git-commit 命令來提交:

$ git-commit -m "Initial commit of gittutor reposistory"

查看當前的工作:git-diff

git-diff 命令將比較當前的工作目錄和版本庫數據庫中的差異。 現在我們編輯一些文件來體驗一下 git 的跟蹤功能。

$ echo "It's a new day for git" >> hello

我們再來比較一下,當前的工作目錄和版本庫中的數據的差別。

$ git-diff

差異將以典型的 patch 方式表示出來:

diff --git a/hello b/hello
index a5c1966..bd9212c 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
 Hello, world
+It's a new day for git

此時,我們可以再次使用組合命令 git-update-index 和 git-commit 將我們的工作提交到版本庫中。

$ git-update-index hello
$ git-commit -m "new day for git"

實際上,如果要提交的文件都是已經納入 git 版本庫的文件,那麼不必爲這些文件都應用 git-update-index 命令之後再進行提交,下面的命令更簡捷並且和上面的命令是等價的。

$ git-commit -a -m "new day for git"

管理分支:git-branch

直至現在爲止,我們的項目版本庫一直都是隻有一個分支 master。 在 git 版本庫中創建分支的成本幾乎爲零,所以,不必吝嗇多創建幾個分支。 下面列舉一些常見的分支策略,僅供大家參考:

  • 創建一個屬於自己的個人工作分支,以避免對主分支 master 造成太多的干擾, 也方便與他人交流協作。

  • 當進行高風險的工作時,創建一個試驗性的分支,扔掉一個爛攤子總比收拾一個爛攤子好得多。

  • 合併別人的工作的時候,最好是創建一個臨時的分支,關於如何用臨時分支合併別人的工作的技巧, 將會在後面講述。

創建分支

下面的命令將創建我自己的工作分支,名叫 robin,並且將以後的工作轉移到這個分支上開展。

$ git-branch robin
$ git-checkout robin

刪除分支

要刪除版本庫中的某個分支,使用 git-branch -D 命令就可以了,例如:

$ git-branch -D branch-name

查看項目的發展變化和比較差異

這一節介紹幾個查看項目的版本庫的發展變化以及比較差異的很有用的命令:

git-show-branch

git-diff

git-whatchanged


我們現在爲 robinmaster 兩個分支都增加一些內容。

$ git-checkout robin
$ echo "Work, work, workd" >> hello
$ git-commit -m "Some workd" -i hello
$ git-checkout master
$ echo "Play, play, play" >> hello
$ echo "Lots of fun" >> example
$ git-commit -m "Some fun" -i hello example

git-show-branch 命令可以使我們看到版本庫中每個分支的世系發展狀態, 並且可以看到每次提交的內容是否已進入每個分支。

$ git-show-branch 

這個命令讓我們看到版本庫的發展記錄。

* [master] Some fun
 ! [robin] some work
--
*  [master] Some fun
 + [robin] some work
*+ [master^] a new day for git

譬如我們要查看世系標號爲 master^ 和 robin 的版本的差異情況, 我們可以使用這樣的命令:

$ git-diff master^ robin

我們可以看到這兩個版本的差異:

diff --git a/hello b/hello
index 263414f..cc44c73 100644
--- a/hello
+++ b/hello
@@ -1,2 +1,3 @@
 Hello World
 It's a new day for git
+Work, work, work
Note
關於 GIT 版本世系編號的定義,請參看 git-rev-parse 

我們現在再用 git-whatchanged 命令來看看 master 分支是怎麼發展的。

$ git-checkout master
$ git-whatchanged 
diff-tree 1d2fa05... (from 3ecebc0...)
Author: Vortune.Robin 
Date:   Tue Mar 21 02:24:31 2006 +0800

    Some fun

:100644 100644 f24c74a... 7f8b141... M  example
:100644 100644 263414f... 06fa6a2... M  hello

diff-tree 3ecebc0... (from 895f09a...)
Author: Vortune.Robin 
Date:   Tue Mar 21 02:17:23 2006 +0800

    a new day for git

:100644 100644 557db03... 263414f... M  hello

從上面的內容中我們可以看到,在 robin 分支中的日誌爲 "Some work" 的內容, 並沒有在 master 分支中出現。

合併兩個分支:git-merge

既然我們爲項目創建了不同的分支, 那麼我們就要經常地將自己或者是別人在一個分支上的工作合併到其他的分支上去。 現在我們看看怎麼將 robin 分支上的工作合併到 master 分支中。 現在轉移我們當前的工作分支到 master,並且將 robin 分支上的工作合並進來。

$ git-checkout master
$ git-merge "Merge work in robin" HEAD robin

合併兩個分支,還有一個更簡便的方式,下面的命令和上面的命令是等價的。
$ git-checkout master
$ git-pull . robin

但是,此時 git 會出現合併衝突提示:

Trying really trivial in-index merge...
fatal: Merge requires file-level merging
Nope.
Merging HEAD with d2659fcf690ec693c04c82b03202fc5530d50960
Merging:
1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fun
d2659fcf690ec693c04c82b03202fc5530d50960 some work
found 1 common ancestor(s):
3ecebc0cb4894a33208dfa7c7c6fc8b5f9da0eda a new day for git
Auto-merging hello
CONFLICT (content): Merge conflict in hello

Automatic merge failed; fix up by hand

git 的提示指出,在合併作用於文件 hello 的 'Some fun' 和 'some work' 這兩個對象時有衝突, 具體通俗點說,就是在 masterrobin 這兩個分支中的 hello 文件的某些相同的行中的內容不一樣。 我們需要手動解決這些衝突,現在先讓我們看看現在的 hello 文件中的內容。

$ cat hello

此時的 hello 文件應是這樣的,用過其他的版本控制系統的朋友應該很容易看出這個典型的衝突表示格式:

Hello World
It's a new day for git
<<<<<<< HEAD/hello
Play, play, play
=======
Work, work, work
>>>>>>> d2659fcf690ec693c04c82b03202fc5530d50960/hello

我們用編輯器將 hello 文件改爲:

Hello World
It's a new day for git
Play, play, play
Work, work, work

現在可以將手動解決了衝突的文件提交了。

$ git-commit -i hello

以上是典型的兩路合併(2-way merge)算法,絕大多數情況下已經夠用。 但是還有更復雜的三路合併和多內容樹合併的情況。詳情可參看: git-read-treegit-merge 等文檔。

逆轉與恢復:git-reset

項目跟蹤工具的一個重要任務之一,就是使我們能夠隨時逆轉(Undo)和恢復(Redo)某一階段的工作。

git-reset 命令就是爲這樣的任務準備的。 它將當前的工作分支的  定位到以前提交的任何版本中,它有三個重置的算法選項。

命令形式:

git-reset [--mixed | --soft | --hard] [<commit-ish>]

命令的選項:

--mixed
僅是重置索引的位置,而不改變你的工作樹中的任何東西(即,文件中的所有變化都會被保留, 也不標記他們爲待提交狀態),並且提示什麼內容還沒有被更新了。這個是默認的選項。
--soft
既不觸動索引的位置,也不改變工作樹中的任何內容,我們只是要求這些內容成爲一份好的內容 (之後才成爲真正的提交內容)。 這個選項使你可以將已經提交的東西重新逆轉至“已更新但未提交(Updated but not Check in)”的狀態。 就像已經執行過 git-update-index 命令,但是還沒有執行 git-commit 命令一樣。
--hard
將工作樹中的內容和頭索引都切換至指定的版本位置中,也就是說自 <commit-ish> 之後的所有的跟蹤內容和工作樹中的內容都會全部丟失。 因此,這個選項要慎用,除非你已經非常確定你的確不想再看到那些東西了。

一個重要技巧--逆轉提交與恢復

可能有人會問,--soft 選項既不重置頭索引的位置,也不改變工作樹中的內容, 那麼它有什麼用呢?現在我們介紹一個 --soft 選項的使用技巧。 下面我們用例子來說明:

$ git-checkout master
$ git-checkout -b softreset
$ git-show-branch

這裏我們創建了一個 master 的拷貝分支 softreset, 現在我們可以看到兩個分支是在同一起跑線上的。

! [master] Merge branch 'robin'
 ! [robin] some work
  * [softreset] Merge branch 'robin'
---
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work

我們爲 文件增加一些內容並提交。

$ echo "Botch, botch, botch" >> hello
$ git-commit -a -m "some botch"
$ git-show-branch

我們可以看到此時 softreset 比 master 推進了一個版本 "some botch" 。

! [master] Merge branch 'robin'
 ! [robin] some work
  * [softreset] some botch
---
  * [softreset] some botch
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work

現在讓我們來考慮這樣的一種情況,假如我們現在對剛剛提交的內容不滿意, 那麼我們再編輯項目的內容,再提交的話,那麼 "some botch" 的內容就會留在版本庫中了。 我們當然不希望將有明顯問題的內容留在版本庫中,這個時候 --soft 選項就很有用了。 爲了深入瞭解 --soft 的機制,我們看看現在 softreset 分支的頭和 ORIG_HEAD 保存的索引。

$ cat .git/refs/heads/softreset .git/ORIG_HEAD

結果如下:

5e7cf906233e052bdca8c598cad2cb5478f9540a
7bbd1370e2c667d955b6f6652bf8274efdc1fbd3

現在用 --soft 選項逆轉剛纔提交的內容:

git-reset --soft HEAD^

現在讓我們再看看 .git/ORIG_HEAD 的中保存了什麼?

$ cat .git/ORIG_HEAD

結果如下:

5e7cf906233e052bdca8c598cad2cb5478f9540a

看!現在的 .git/ORIG_HEAD 等於逆轉前的 .git/refs/heads/softreset 。 也就是說,git-reset --soft HEAD^ 命令逆轉了剛纔提交的版本進度, 但是它將那次提交的對象的索引拷貝到了 .git/ORIG_HEAD 中。

我們再編輯 hello 文件成爲下面的內容:

Hello World
It's a new day for git
Play, play, play
Work, work, work
Nice, nice, nice

我們甚至可以比較一下現在的工作樹中的內容和被取消了的那次提交的內容有什麼差異:

$ git-diff ORIG_HEAD

結果如下:

diff --git a/hello b/hello
index f978676..dd02c32 100644
--- a/hello
+++ b/hello
@@ -2,4 +2,4 @@ Hello World
 It's a new day for git
 Play, play, play
 Work, work, work
-Botch, botch, botch
+Nice, nice, nice

接着,我們可以恢復剛纔被取消了的那次提交了。

$ git-commit -a -c ORIG_HEAD

注意,這個命令會打開默認的文本編輯器以編輯原來提交的版本日誌信息,我們改爲 "nice work" 。 大家可以自行用 git-show-branch 命令來查看一下現在的分支狀態。 並且我們還可以不斷地重複上述的步驟,一直修改到你對這個版本進度滿意爲止。

git-reset 命令還有很多的用途和技巧,請參考 git-reset ,以及 Everyday GIT with 20 commands or So 

提取版本庫中的數據

這是個很有用的小技巧,如果你對你現在的工作目錄下的東西已經不耐煩了, 隨時可以取出你提交過的東西覆蓋掉當前的文件,譬如:

$ git-checkout -f foo.c

標定版本

在 git 中,有兩種類型的標籤,“輕標籤”和“署名標籤”。

技術上說,一個“輕標籤”和一個分支沒有任何區別,只不過我們將它放在了 .git/refs/tags/ 目錄, 而不是 heads 目錄。因此,打一個“輕標籤”再簡單不過了。

$ git-tag my-first-tag

“署名標籤”是一個真正的 git 對象,它不但包含指向你想標記的狀態的指針,還有一個標記名和信息, 可選的 PGP 簽名。你可以通過 -a 或者是 -s 選項來創建“署名標籤”。

$ git-tag -s <tag-name>

合併外部工作

通常的情況下,合併其他的人的工作的情況會比合並自己的分支的情況要多, 這在 git 中是非常容易的事情,和你運行 git-merge 命令沒有什麼區別。 事實上,遠程合併的無非就是“抓取(fetch)一個遠程的版本庫中的工作到一個臨時的標籤中”, 然後再使用 git-merge 命令。

可以通過下面的命令來抓取遠程版本庫:

$ git-fetch <remote-repository>

根據不同的遠程版本庫所使用的通訊協議的路徑來替代上面的 remoted-repository 就可以了。

Rsync

rsync://remote.machine/patch/to/repo.git/

SSH

remote.machine:/path/to/repo.git
or
ssh://remote.machine/patch/to/repo.git/

這是可以上傳和下載的雙向傳輸協議,當然,你要有通過 ssh 協議登錄遠程機器的權限。 它可以找出兩端的機器提交過的對象集之中相互缺少了那些對象,從而得到需要傳輸的最小對象集。 這是最高效地交換兩個版本庫之間的對象的方式(在 git 兼容的所有傳輸協議當中)。

下面是個取得 SSH 遠程版本庫的命令例子:

$ git-fetch [email protected]:/path/to/gittutorcn.git  (1)

(1) 這裏 robin 是登錄的用戶名,192.168.1.168 是保存着主版本庫的機器的 IP 地址。
Local directory

/path/to/repo.git/

本地目錄的情況和 SSH 情況是一樣的。

git Native

git://remote.machine/path/to/repo.git/

git 自然協議是設計來用於匿名下載的,它的工作方式類似於 SSH 協議的交換方式。

HTTP(S)

http://remote.machine/path/to/repo.git/

到這裏可能有些朋友已經想到, 實際上,我們可以通過 Rsync, SSH 之類的雙向傳輸方式來建立類似 CVS,SVN 這樣的中心版本庫模式的開發組織形式。

通過電子郵件交換工作

讀過上一節之後,有的朋友可能要問,如果版本庫是通過單向的下載協議發佈的,如 HTTP, 我們就無法將工作上傳到公共的版本庫中。別人也不能訪問我的機器來抓取我的工作,那怎麼辦呢?

不必擔心,我們還有 email !別忘了 git 本來就是爲了管理 Linux 的內核開發而設計的。 所以,它非常適合像 Linux Kernel 這樣的開發組織形式高度分散,嚴重依賴 email 來進行交流的項目。

下面模擬你參加到《Git 中文教程》的編寫工作中來,看看我們可以怎麼通過 email 進行工作交流。 你可以通過下面的命令下載這個項目的版本庫。

$ git-clone http://www.bitsun.com/git/gittutorcn.git
之後,你會在當前目錄下得到一個叫 gittutorcn 的目錄, 這就是你的項目的工作目錄了。默認地,它會有兩個分支: master 和 origin,你可以直接在 master 下展開工作, 也可以創建你自己的工作分支,但是千萬不要修改 origin 分支,切記! 因爲它是公共版本庫的鏡像,如果你修改了它, 那麼就不能生成正確的對公共版本庫的 patch 文件了。
Note
如果你的確修改過 origin 分支的內容,那麼在生成 patch 文件之前, 請用 git-reset --hard 命令將它逆轉到最原始的,沒經過任何修改的狀態。

你可以直接在 master 下開展工作,也可以創建你自己的工作分支。 當你對項目做了一定的工作,並提交到庫中。我們用 git-show-branch 命令先看下庫的狀態。

* [master] your buddy's contribution
 ! [origin] degining of git-format-patch example
--
*  [master] your buddy's contribution
*+ [origin] degining of git-format-patch example

上面就假設你已經提交了一個叫 "your buddy's contribution" 的工作。 現在我們來看看怎麼通過 email 來交流工作了。

$ git-fetch origin    (1)
$ git-rebase origin    (2)
$ git-format-patch origin     (3)

(1)更新 origin 分支,防止 origin 分支不是最新的公共版本,產生錯誤的補丁文件;
(2)將你在 master 上提交的工作遷移到新的源版本庫的狀態的基礎上;
(3)生成補丁文件;

上面的幾個命令,會在當前目錄下生成一個大概名爲 0001-your-buddy-s-contribution.txt 補丁文件, 建議你用文本工具查看一下這個文件的具體形式,然後將這個文件以附件的形式發送到項目維護者的郵箱: [email protected]

當項目的維護者收到你的郵件後,只需要用 git-am 命令,就可以將你的工作合併到項目中來。

$ git-checkout -b buddy-incomming
$ git-am /path/to/0001-your-buddy-s-contribution.txt

用 Git 協同工作

假設 Alice 在一部機器上自己的個人目錄中創建了一個項目 /home/alice/project, Bob 想在同一部機器自己的個人目錄中爲這個項目做點什麼。

Bob 首先這樣開始:

$ git-clone /home/alice/project myrepo

這樣就創建了一個保存着 Alice 的版本庫的鏡像的新目錄 "myrepo"。 這個鏡像保存着原始項目的起點和它的發展歷程。

接着 Bob 對項目做了些更改並提交了這些更改:

(編輯一些文件)

$ git-commit -a 

(如果需要的話再重複這個步驟)

當他搞定之後,他告訴 Alice 將他的東西從 /home/bob/myrepo 中引入,她只需要這樣:

$ cd /home/alice/project
$ git pull /home/bob/myrepo

這樣就將 Bob 的版本庫中的 "master" 分支的變化引入了。 Alice 也可以通過在 pull 命令的後面加入參數的方式來引入其他的分支。

在導入了 Bob 的工作之後,用 "git-whatchanged" 命令可以查看有什麼信的提交對象。 如果這段時間裏以來,Alice 也對項目做過自己的修改,當 Bob 的修改被合並進來的時候, 那麼她需要手動修復所有的合併衝突。

謹慎的 Alice 在導入 Bob 的工作之前,希望先檢查一下。 那麼她可以先將 Bob 的工作導入到一個新創建的臨時分支中, 以方便研究 Bob 的工作:

$ git fetch /home/bob/myrepo master:bob-incoming

這個命令將 Bob 的 master 分支的導入到名爲 bob-incoming 的分支中( 不同於 git-pull 命令,git-fetch 命令只是取得 Bob 的開發工作的拷貝, 而不是合併經來)。接着:

$ git whatchanged -p master..bob-incoming

這會列出 Bob 自取得 Alice 的 master 分支之後開始工作的所有變化。 檢查過這些工作,並做過必須的調整之後, Alice 就可以將變化導入到她的 master 分支中:

$ git-checkout master
$git-pull . bob-incoming

最後的命令就是將 "bob-incoming" 分支的東西導入到 Alice 自己的版本庫中的, 稍後,Bob 就可以通過下面的命令同步 Alice 的最新變化。

$ git-pull

注意不需爲這個命令加入 Alice 的版本庫的路徑,因爲當 Bob 克隆 Alice 的版本庫的時候, git 已經將這個路徑保存到 .git/remote/origin 文件中,它將會是所以的導入操作的默認路徑。

Bob 可能已經注意到他並沒有在他的版本庫中創建過分支(但是分支已經存在了):

$ git branch
* master
  origin

"origin" 分支,它是運行 "git-clone" 的時候自動創建的,他是 Alice 的 master 分支的原始鏡像, Bob 應該永遠不要向這個分支提交任何東西。

如果 Bob 以後決定在另外一部主機上開展工作,那麼他仍然需要通過 SSH 協議從新克隆和導入( Alice 的版本庫):

$ git-clone alice.org:/home/alice/project/ myrepo

我們可以使用 git 自然協議,或者是 rsync, http 等協議的任何一種,詳情請參考 git-pull

Git 同樣可以建立類似 CVS 那樣的開發模式,也就是所有開發者都向中心版本庫提交工作的方式, 詳情參考 git_push 和 git for CVS users 。

爲版本庫打包

在前面,我們已經看到在 .git/objects/??/ 目錄中保存着我們創建的每一個 git 對象。 這樣的方式對於自動和安全地創建對象很有效,但是對於網絡傳輸則不方便。 git 對象一旦創建了,就不能被改變,但有一個方法可以優化對象的存儲,就是將他們“打包到一起”。

$ git repack

上面的命令讓你做到這點,如果你一直是做着我們的例子過來的, 你現在大約會在 .git/objects/??/ 目錄下積累了17個對象。 git-repack 會告訴你有幾個對象被打包了, 並且將他們保存在 .git/objects/pack 目錄當中。

Note
你將會看到兩個文件,pack-*.pack and pack-*.idx 在 .git/objects/pack 目錄。他們的關係是很密切的, 如果你手動將他們拷貝到別的版本庫中的話,你要決定將他們一起拷貝。 前者是保存着所有被打包的數據的文件,後者是隨機訪問的索引。

如果你是個偏執狂,就運行一下 git-verity-pack 命令來檢查一下有缺陷的包吧, 不過,其實你無須太多擔心,我們的程序非常出色 ;-).

一旦你已經對那些對象打包了,那麼那些已經被打過包的原始的對象,就沒有必要保留了。

$ git prune-packed

會幫你清楚他們。

如果你好奇的話,你可以在執行 git-prune-repacked 命令之前和之後, 都運行一下 find .git/objects -type f,這樣你就能看到有多少沒有打包的對象, 以及節省了多少磁盤空間。

Note
git pull git-pull 對於 HTTP 傳輸來說,一個打包過的版本庫會將一定數量的 相關聯的對象放進一個有關聯性的打包中。如果你設想多次從 HTTP 公共版本庫中導入數據, 你也許要頻繁地 reapck & prune,要麼就乾脆從不這樣做。

如果你此時再次運行 git-repack,它就會說 "Nothing to pack"。 要是你繼續開發,並且積累了一定數量的變遷,再運行 git-repack 將會創建一個新的包, 它會包含你自上次對庫打包以來創建的對象。我們建議你儘快在初始化提交之後打包一下你的版本庫( 除非你現在的項目是個塗鴉式的草稿項目),並且在項目經歷過一段很活躍的時期時, 再運行 git-repack 一下。

當一個版本庫通過 git-push 和 git-pull 命令來同步源版本庫中打包過的對像的時候, 通常保存到目標版本庫中的是解包了的對象,除非你使用的是 rsync(遠程同步協議)協議的傳輸方式。 正是這種容許你在兩頭的版本庫中有不同的打包策略的方式,他意味着你也許在過一段時間之後, 需要在兩頭的版本庫中都重新打包一下。

將工作捆綁到一起

通過 git 的分支功能,你可以非常容易地做到好像在同一時間進行許多“相關-或-無關”的工作一樣。

我們已經通過前面的 "fun and work" 使用兩個分支的例子,看到分支是怎麼工作的。 這樣的思想在多於兩個的分支的時候也是一樣的,比方說,你現在在 master 的頭, 並有些新的代碼在 master 中,另外還有兩個互不相關的補丁分別在 "commit-fix" 和 "diff-fix" 兩個分支中。

$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Release candidate #1
---
 +  [diff-fix] Fix rename detection.
 +  [diff-fix~1] Better common substring algorithm.
+   [commit-fix] Fix commit message normalization.
  * [master] Release candidate #1
++* [diff-fix~2] Pretty-print messages.

兩個補丁我們都測試好了,到這裏,你想將他們倆合併起來, 於是你可以先合併 diff-fix ,然後再合併 commit-fix,像這樣:

$ git merge 'Merge fix in diff-fix' master diff-fix
$ git merge 'Merge fix in commit-fix' master commit-fix

結果如下:

$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Merge fix in commit-fix
---
  - [master] Merge fix in commit-fix
+ * [commit-fix] Fix commit message normalization.
  - [master~1] Merge fix in diff-fix
 +* [diff-fix] Fix rename detection.
 +* [diff-fix~1] Better common substring algorithm.
  * [master~2] Release candidate #1
++* [master~3] Pretty-print messages.

然而,當你確信你手頭上的確是一堆互不相關的項目變化時,就沒有任何理由將這堆東西一個個地合併( 假如他們的先後順序很重要,那麼他們就不應該被定以爲無關的變化), 你可以一次性將那兩個分支合併到當前的分支中,首先我們將我們剛剛做過的事情逆轉一下, 我們需要通過將 master 分支重置到 master~2 位置的方法來將它逆轉到合併那兩個分支之前的狀態。

$ git reset --hard master~2

你可以用 git-show-branch 來確認一下的確是回到了兩次 git-merge 的狀態了。 現在你可以用一行命令將那兩個分支導入的方式來替代兩次運行( 也就是所謂的 炮製章魚 -- making an Octopusgit-merge :

$ git pull . commit-fix diff-fix
$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
---
  - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+ * [commit-fix] Fix commit message normalization.
 +* [diff-fix] Fix rename detection.
 +* [diff-fix~1] Better common substring algorithm.
  * [master~1] Release candidate #1
++* [master~2] Pretty-print messages.

注意那些不適合製作章魚的場合,儘管你可以那樣做。一隻“章魚”往往可以使項目的提交歷史更具可讀性, 前提是你在同一時間導入的兩份以上的變更是互不關聯的。 然而,如果你在合併任何分支的過程中出現合併衝突,並且需要手工解決的話, 那意味着這些分支當中有相互干涉的開發工作在進行,那麼你就應該將這個兩個衝突先合併, 並且記錄下你是如何解決這個衝突,以及你首先處理他們的理由。(譯者按:處理完衝突之後, 你就可以放心製作“章魚”了) 否則的話將會造成項目的發展歷史很難跟蹤。

管理版本庫

版本庫的管理員可以用下面的工具來建立和維護版本庫。

  • git-daemon(1) 容許匿名下載版本庫。

  • git-shell(1) 面向中心版本庫模式 的用戶的類似 受限的 shell 的命令。

update hook howto 一個很好的管理中心版本庫的例子。

例子

在 /pub/scm 上運行 git 守護進程
$ grep git /etc/inet.conf
git     stream  tcp     nowait  nobody \
  /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm

這個配置行應該在配置文件中用一行來寫完。

僅給開發者 push/pull 的訪問權限。
$ grep git /etc/passwd (1)
alice:x:1000:1000::/home/alice:/usr/bin/git-shell
bob:x:1001:1001::/home/bob:/usr/bin/git-shell
cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
david:x:1003:1003::/home/david:/usr/bin/git-shell
$ grep git /etc/shells (2)
/usr/bin/git-shell

 
(1) 將用戶的登錄 shell 設定爲 /usr/bin/git-shell,
它除了運行 "git-push" 和 "git-pull" 不能做任何事。
這樣用戶就可以通過 ssh 來訪問機器。
(2) 許多的發行版需要在 /etc/shells 配置文件中列明要用什麼 shell 來作爲登錄 shell。
CVS - 模式的公共庫。
$ grep git /etc/group (1)
git:x:9418:alice,bob,cindy,david
$ cd /home/devo.git
$ ls -l (2)
  lrwxrwxrwx   1 david git    17 Dec  4 22:40 HEAD -> refs/heads/master
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 branches
  -rw-rw-r--   1 david git    84 Dec  4 22:40 config
  -rw-rw-r--   1 david git    58 Dec  4 22:40 description
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 hooks
  -rw-rw-r--   1 david git 37504 Dec  4 22:40 index
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 info
  drwxrwsr-x   4 david git  4096 Dec  4 22:40 objects
  drwxrwsr-x   4 david git  4096 Nov  7 14:58 refs
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 remotes
$ ls -l hooks/update (3)
  -r-xr-xr-x   1 david git  3536 Dec  4 22:40 update
$ cat info/allowed-users (4)
refs/heads/master       alice\|cindy
refs/heads/doc-update   bob
refs/tags/v[0-9]*       david

(1) 將所有的開發人員都作爲 git 組的成員。
(2) 並且給予他們公共版本庫的寫權限。
(3) 用一個在 Documentation/howto/ 中的 Carl 寫的例子來實現版本庫的分支控制策略。
(4) Alice 和 Cindy 可以提交入 master 分支,只有 Bob 能提交入 doc-update 分支,
David 則是發行經理只有他能創建並且 push 版本標籤。
支持默協議傳輸的 HTTP 服務器。
dev$ git update-server-info (1)
dev$ ftp [email protected] (2)
ftp> cp -r .git /home/user/myproject.git

(1) 保證 info/refs 和 object/info/packs 是最新的。
(2) 上傳到你的 HTTP 服務器主機。

項目開發的模式推介

儘管 git 是一個正式項目發佈系統,它卻可以方便地將你的項目建立在鬆散的開發人員組織形式上。 Linux 內核的開發,就是按這樣的模式進行的。在 Randy Dunlap 的著作中("Merge to Mainline" 第17頁) 就有很好的介紹(http://tinyurl.com/a2jdg)。

需要強調的是正真的非常規的開發組織形式, git 這種組織形式,意味着對於工作流程的約束,沒有任何強迫性的原則。 你不必從唯一一個遠程版本庫中導入(工作目錄)。

項目領導人(project lead)的工作推介

  1. 在你自己的本地機器上準備好主版本庫。你的所有工作都在這裏完成。

  2. 準備一個能讓大家訪問的公共版本庫。

    如果其他人是通過默協議的方式(http)來導入版本庫的,那麼你有必要保持這個 默協議的友好性。 git-init-db 之後,複製自標準模板庫的$GIT_DIR/hooks/post-update 將包含一個對 git-update-server-info 的調用,但是 post-update 默認是不能喚起它自身的。 通過 chmod +x post-update 命令使能它。這樣讓 git-update-server-info 保證那些必要的文件是最新的。

  3. 將你的主版本庫推入公共版本庫。

  4. git-repack 公共版本庫。這將建立一個包含初始化提交對象集的打包作爲項目的起始線, 可能的話,執行一下 git-prune,要是你的公共庫是通過 pull 操作來從你打包過的版本庫中導入的。

  5. 在你的主版本庫中開展工作,這些工作可能是你自己的最項目的編輯, 可能是你由 email 收到的一個補丁,也可能是你從這個項目的“子系統負責人” 的公共庫中導入的工作等等。

    你可以在任何你喜歡的時候重新打包你的這個私人的版本庫。

  6. 將項目的進度推入公共庫中,並給大家公佈一下。

  7. 儘管一段時間以後,"git-repack" 公共庫。並回到第5步繼續工作。

項目的子系統負責人(subsystem maintainer)也有自己的公共庫,工作流程大致如下:

  1. 準被一個你自己的工作目錄,它通過 git-clone 克隆自項目領導人的公共庫。 原始的克隆地址(URL)將被保存在 .git/remotes/origin 中。

  2. 準備一個可以給大家訪問的公共庫,就像項目領導人所做的那樣。

  3. 複製項目領導人的公共庫中的打包文件到你的公共庫中, 除非你的公共庫和項目領導人的公共庫是在同一部主機上。 以後你就可以通過objects/info/alternates 文件的指向來 瀏覽它所指向的版本庫了。

  4. 將你的主版本庫推入你的公共版本庫,並運行 git-repack, 如果你的公共庫是通過的公共庫是通過 pull 來導入的數據的話, 再執行一下 git-prune 。
  5. 在你的主版本庫中開展工作。這些工作可能包括你自己的編輯,來自 email 的補丁, 從項目領導人,“下一級子項目負責人”的公共庫哪裏導入的工作等等。

    你可以在任何時候重新打包你的私人版本庫。

  6. 將你的變更推入公共庫中,並且請“項目領導人”和“下級子系統負責人”導入這些變更。

  7. 每隔一段時間之後,git-repack 公共庫。回到第 5 步繼續工作。

“一般開發人員”無須自己的公共庫,大致的工作方式是:

  1. 準備你的工作庫,它應該用 git-clone 克隆自“項目領導人”的公共庫( 如果你只是開發子項目,那麼就克隆“子項目負責人”的)。 克隆的源地址(URL)會被保存到 .git/remotes/origin 中。

  2. 在你的個人版本庫中的 master 分支中開展工作。

  3. 每隔一段時間,向上遊的版本庫運行一下 git-fetch origin 。 這樣只會做 git-pull 一半的操作,即只克隆不合並。 公共版本庫的新的頭就會被保存到.git/refs/heads/origins 。

  4. 用 git-cherry origin 命令,看一下你有什麼補丁被接納了。 並用 git-rebase origin 命令將你以往的變更遷移到最新的上游版本庫的狀態中。 (關於 git-rebase 命令,請參考 git-rebase

  5. 用 git-format-patch origin 生成 email 形式的補丁併發給上游的維護者。 回到第二步接着工作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章