Linux內核開發的版本控制工具Git中文教程

 

本文將以 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 br />#   (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

 

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

 

$ 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 br />1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fun
d2659fcf690ec693c04c82b03202fc5530d50960 some work
found 1 common ancestor(s) br />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' 這兩個對象時有衝突, 具體通俗點說,就是在 master, robin 這兩個分支中的 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-tree, git-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 。

發佈了81 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章