特點介紹
分布式相比于集中式的最大區别在于開發者可以提交到本地,每個開發者通過克隆(git clone),在本地機器上拷貝一個完整的Git倉庫。
如圖1所示是經典的git開發過程。
Git的功能特性:
從一般開發者的角度來看,git有以下功能:
1、從服務器上克隆完整的Git倉庫(包括代碼和版本信息)到單機上。
2、在自己的機器上根據不同的開發目的,創建分支,修改代碼。
3、在單機上自己創建的分支上提交代碼。
4、在單機上合并分支。
5、把服務器上最新版的代碼fetch下來,然後跟自己的主分支合并。
6、生成補丁(patch),把補丁發送給主開發者。
7、看主開發者的反饋,如果主開發者發現兩個一般開發者之間有沖突(他們之間可以合作解決的沖突),就會要求他們先解決沖突,然後再由其中一個人提交。如果主開發者可以自己解決,或者沒有沖突,就通過。
8、一般開發者之間解決沖突的方法,開發者之間可以使用pull 命令解決沖突,解決完沖突之後再向主開發者提交補丁。
從主開發者的角度(假設主開發者不用開發代碼)看,git有以下功能:
1、查看郵件或者通過其它方式查看一般開發者的提交狀态。
2、打上補丁,解決沖突(可以自己解決,也可以要求開發者之間解決以後再重新提交,如果是開源項目,還要決定哪些補丁有用,哪些不用)。
3、向公共服務器提交結果,然後通知所有開發人員。
優點:
适合分布式開發,強調個體。
公共服務器壓力和數據量都不會太大。
速度快、靈活。
任意兩個開發者之間可以很容易的解決沖突。
離線工作。
缺點:
資料少(起碼中文資料很少)。
學習周期相對而言比較長。
不符合常規思維。
代碼保密性差,一旦開發者把整個庫克隆下來就可以完全公開所有代碼和版本信息。
基本信息
Git --- The stupid content tracker, 傻瓜内容跟蹤器。Linus Torvalds 是這樣給我們介紹 Git 的。
Git 是基于 Linux内核開發的版本控制工具。與常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本庫的方式,不必服務器端軟件支持(wingeddevil注:這得分是用什麼樣的服務端,使用http協議或者git協議等不太一樣。并且在push和pull的時候和服務器端還是有交互的。),使源代碼的發布和交流極其方便。 Git 的速度很快,這對于諸如 Linux kernel 這樣的大項目來說自然很重要。 Git 最為出色的是它的合并跟蹤(merge tracing)能力。
實際上内核開發團隊決定開始開發和使用 Git 來作為内核開發的版本控制系統的時候,世界開源社群的反對聲音不少,最大的理由是 Git 太艱澀難懂,從 Git 的内部工作機制來說,的确是這樣。但是随着開發的深入,Git 的正常使用都由一些友好的腳本命令來執行,使 Git 變得非常好用,即使是用來管理我們自己的開發項目,Git 都是一個友好,有力的工具。現在,越來越多的著名項目采用 Git 來管理項目開發.
作為開源自由原教旨主義項目,Git 沒有對版本庫的浏覽和修改做任何的權限限制。
目前GIT已經可以在windows下使用,主要方法有二:msysgit和Cygwin。Cygwin和Linux使用方法類似,Windows版本的GIT提供了友好的GUI(圖形界面),安裝後很快可以上手,不在此做大篇幅介紹。
本文将以 Git 官方文檔 Tutorial, core-tutorial 和 Everyday GIT 作為藍本翻譯整理,但是暫時去掉了對 Git 内部工作機制的闡述,力求簡明扼要,并加入了作者使用 Git 的過程中的一些心得體會,注意事項,以及更多的例子。建議你最好通過你所使用的 Unix / Linux 發行版的安裝包來安裝 Git, 你可以在線浏覽本文 ,也可以通過下面的命令來得到本文最新的版本庫,并且通過後面的學習用 Git 作為工具參加到本文的創作中來。
(Snake.Zero 注:以下假設環境為Unix/Linux,本次修正主要是版本問題,git-add git-init-db等命令都改為了類似git add形式的,以免誤導新手。)
版本庫
創建一個版本庫:
git init
( kwydwuf注: 新版 git 中應該用 git init ,不要再用 init-db 命令,具體可以通過命令 git help init 查看)
可以了。現在我們來為本文的寫作創建一個版本庫:
$ mkdir gittutorcn
$ cd gittutorcn
$ git init
git 将會作出以下的回應
Initialized empty Git repository in/[yourpath]/.git或
(Initialized empty Git repository in /Users/1a/gittutorcn/.git/
)
這樣,一個空的版本庫就創建好了,并在當前目錄中創建一個叫 .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"Helloworld">hello
$echo"SnakeZero">snake
我們再用 git add 命令将這兩個文件加入到版本庫文件索引當中:
$git add hello snake
git add 實際上是個腳本命令,它是對 git 内核命令 git update-index 的調用。因此上面的命令和下面的命令其實是等價的:
$git update-index --add hello snake
如果你要将某個文件從 git 的目錄跟蹤系統中清除出去,同樣可以用 git update-index 命令。例如:
$git update-index --force-remove foo.c
注意:
git add 可以将某個目錄下的所有内容全都納入内容跟蹤之下,例如: git add ./path/to/your/wanted 。但是在這樣做之前,應該注意先将一些我們不希望跟蹤的文件清理掉,例如,gcc 編譯出來的 *.o 文件,vim 的交換文件 .*.swp 之類。
應該建立一個清晰的概念就是,git add 和 git update-index 隻是刷新了 git 的跟蹤信息,hello 和 snake 這兩個文件中的内容并沒有提交到 git 的内容跟蹤範疇之内。
普通用戶總是應該使用 git add, 而不要使用上面提到的 update-index内部命令。
添加所有未跟蹤文件用 git add -A, 添加所有未跟蹤文件并且提交用 git commit -a。(注意大小寫)
從當前跟蹤文件中删除用 git reset HEAD 。事實上也就是用當前 HEAD(commited) 中的内容替換掉 index(staging) 的内容。
提交内容
提交内容到版本庫:
git commit
既然我們刷新了 Git 的跟蹤信息,現在我們看看版本庫的狀态:
git status
我們能看到 git 的狀态提示:
#
#Initial commit
#
#
#Updated but not checkedin:
#(willcommit)
#
#newfile:example
#newfile:hello
#
提示信息告訴我們版本庫中加入了兩個新的文件,并且 git 提示我們提交這些文件,我們可以通過 git commit 命令來提交:
$git commit -m "Initial commit of git tutor reposistory"
查看當前的工作:git diff
git diff 命令将比較當前的工作目錄和版本庫數據庫中的差異。現在我們編輯一些文件來體驗一下 git 的跟蹤功能。
$echo'這段是後來加的'>snake
我們再來比較一下,當前的工作目錄和版本庫中的數據的差别。
$gitdiff
差異将以典型的 patch 方式表示出來:
diff--gita/snakeb/snake
index3b85043..d79f20a100644
---a/snake
+++b/snake
@@-1+1@@
-snakezero
+這段是後來加的
此時,我們可以再次使用組合命令 git add 和 git commit 将我們的工作提交到版本庫中。
$git add snake
$git commit -m "new day for git"
實際上,如果要提交的文件都是已經納入 git 版本庫的文件,那麼不必為這些文件都應用 git add 命令之後再進行提交,下面的命令更簡捷并且和上面的命令是等價的。
$git commit -a -m"new day for git"
管理分支
管理分支:
git branch
直至現在為止,我們的項目版本庫一直都是隻有一個分支 master。在 git 版本庫中創建分支的成本幾乎為零,所以,不必吝啬多創建幾個分支。下面列舉一些常見的分支策略,僅供大家參考:
* 創建一個屬于自己的個人工作分支,以避免對主分支 master 造成太多的幹擾,也方便與他人交流協作。
* 當進行高風險的工作時,創建一個試驗性的分支,扔掉一個爛攤子總比收拾一個爛攤子好得多。
* 合并别人的工作的時候,最好是創建一個臨時的分支,關于如何用臨時分支合并别人的工作的技巧,将會在後面講述。
創建分支
下面的命令将創建我自己的工作分支,名叫 robin,并且将以後的工作轉移到這個分支上開展。
$ git branch robin
$ git checkout robin
更簡單和常用的方法是直接通過 checkout 命令來一次性創建并轉移到新建分支上,命令如下:
$ git checkout -b robin [start_point]
其中 start_point 是一個可選參數,指定新建分支 robin 是基于哪個節點,默認為當前所在分支的節點。
删除分支
要删除版本庫中的某個分支,使用 git branch -d 命令就可以了,例如:
$ git branch -d branch-name
但是需要注意的是,如果删除的分支還沒有被 merge 到其他分支,删除這樣的分支會導緻這個分支上所做的改動丢失,因此 git branch -d 命令會失敗,提示你這樣做會丢失信息。如果你的确想删除這樣的分支,不怕信息丢失,那麼可以使用 git branch -D 命令,這個命令不會去判斷分支的merge狀态,例如:
$ git branch -D branch-name
通常建議使用 -d 參數來删除分支,以防無意的信息丢失。
查看分支運行下面的命令可以得到你當前工作目錄的分支列表:
$ git branch
在你正在工作的分支的名字前面,會有 * 号标示,比如:
$ git branch
bugfix
* master
說明有兩個本地分支 bugfix 和 master, 其中當前的工作分支為 master。
查看項目的發展變化和比較差異
這一節介紹幾個查看項目的版本庫的發展變化以及比較差異的很有用的命令:
git show-branchgit diffgit 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/helloindex 263414f..cc44c73 100644--- a/hello+++ b/hello@@ -1,2 +1,3 @@Hello WorldIt's a new day for git+Work, work, workNote
關于 GIT 版本世系編号的定義,請參看 git help rev-parse 。
我們現在再用 git whatchanged 命令來看看 master 分支是怎麼發展的。
$ git checkout master
$ git whatchangeddiff-tree 1d2fa05... (from 3ecebc0...)Author: Vortune.RobinDate: Tue Mar 21 02:24:31 2006 +0800Some fun:100644 100644 f24c74a... 7f8b141... M example:100644 100644 263414f... 06fa6a2... M hellodiff-tree 3ecebc0... (from 895f09a...)Author: Vortune.RobinDate: Tue Mar 21 02:17:23 2006 +0800a 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 -m "Merge from robin" robin
上面的命令會将 robin 分支的改動 merge 到 master,并生成一個新的 commit 節點,這個 commit 的注釋信息為 "Merge from robin"
(kwydwuf注: $ git merge "Merge work in robin" HEAD robin 是老版本的用法,應該廢棄 )
合并兩個分支,還有一個更簡便的方式,下面的命令和上面的命令是等價的 (kwydwuf注:git pull 的本意是用來 merge 遠端版本庫中的某個分支,用在此處沒有任何簡便之處,可以廢棄)。
$ git checkout master
$ git pull . robin
但是,此時 git 會出現合并沖突提示:
Trying really trivial in-index merge...fatal: Merge requires file-level mergingNope.Merging HEAD with d2659fcf690ec693c04c82b03202fc5530d50960Merging:1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fund2659fcf690ec693c04c82b03202fc5530d50960 some workfound 1 common ancestor(s):3ecebc0cb4894a33208dfa7c7c6fc8b5f9da0eda a new day for gitAuto-merging helloCONFLICT (content): Merge conflict in helloAutomatic merge failed; fix up by hand
git 的提示指出,在合并作用于文件 hello 的 'Some fun' 和 'some work' 這兩個對象時有沖突,具體通俗點說,就是在 master, robin 這兩個分支中的 hello 文件的某些相同的行中的内容不一樣。我們需要手動解決這些沖突,現在先讓我們看看現在的 hello 文件中的内容。
$ cat hello
此時的 hello 文件應是這樣的,用過其他的版本控制系統的朋友應該很容易看出這個典型的沖突表示格式:tHello WorldIt's a new day for git<<<<<<< HEAD/helloPlay, play, play=======Work, work, work>>>>>>> d2659fcf690ec693c04c82b03202fc5530d50960/hello
我們用編輯器将 hello 文件改為:
Hello WorldIt's a new day for gitPlay, play, playWork, work, work
現在可以将手動解決了沖突的文件提交了。
$ git commit -i hello
以上是典型的兩路合并(2-way merge)算法,絕大多數情況下已經夠用。但是還有更複雜的三路合并和多内容樹合并的情況。詳情可參看: git help read-tree, git help merge 等文檔。
逆轉恢複
逆轉與恢複:
git reset
項目跟蹤工具的一個重要任務之一,就是使我們能夠随時逆轉(Undo)和恢複(Redo)某一階段的工作。
git reset 命令就是為這樣的任務準備的。它将當前的工作分支的 頭 定位到以前提交的任何版本中,它有三個重置的算法選項。
命令形式:
git reset [--mixed | --soft | --hard] [
命令的選項:
--mixed
僅是重置索引的位置,而不改變你的工作樹中的任何東西(即,文件中的所有變化都會被保留,也不标記他們為待提交狀态),并且提示什麼内容還沒有被更新了。這個是默認的選項。
--soft
既不觸動索引的位置,也不改變工作樹中的任何内容,我們隻是要求這些内容成為一份好的内容(之後才成為真正的提交内容)。這個選項使你可以将已經提交的東西重新逆轉至“已更新但未提交(Updated but not Check in)”的狀态。就像已經執行過 git update-index 命令,但是還沒有執行 git commit 命令一樣。
--hard
将工作樹中的内容和頭索引都切換至指定的版本位置中,也就是說自 之後的所有的跟蹤内容和工作樹中的内容都會全部丢失。因此,這個選項要慎用,除非你已經非常确定你的确不想再看到那些東西了。
一個重要技巧--逆轉提交與恢複
使用技巧
可能有人會問,--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
我們甚至可以比較一下現在的工作樹中的内容和被取消了的那次提交的内容有什麼差異:
1t$ 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
如果你打算針對某個commit ID來打标簽,雖然該命令可以通過gitk裡的右鍵菜單來實現,但是該命令對實際應用是很有幫助的。
“署名标簽”是一個真正的 git 對象,它不但包含指向你想标記的狀态的指針,還有一個标記名和信息,可選的 PGP 簽名。你可以通過 -a 或者是 -s 選項來創建“署名标簽”。
$ git tag -s
合并工作
通常的情況下,合并其他的人的工作的情況會比合并自己的分支的情況要多,這在 git 中是非常容易的事情,和你運行 git-merge 命令沒有什麼區别。事實上,遠程合并的無非就是“抓取(fetch)一個遠程的版本庫中的工作到一個臨時的标簽中”,然後再使用 git-merge 命令。
可以通過下面的命令來抓取遠程版本庫:
$ git fetch
根據不同的遠程版本庫所使用的通訊協議的路徑來替代上面的 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 robin@192.168.1.168:/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 進行工作交流。你可以通過下面的命令下載這個項目的版本庫。
之後,你會在當前目錄下得到一個叫 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補丁文件, 建議你用文本工具查看一下這個文件的具體形式,然後将這個文件以附件的形式發送到項目維護者的郵箱
當項目的維護者收到你的郵件後,隻需要用 git-am 命令,就可以将你的工作合并到項目中來。
$ git checkout -b buddy-incomming
$ git am /path/to/0001-your-buddy-s-contribution.txt
協同工作
假設 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 自然協議,或者是 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,這樣你就能看到有多少沒有打包的對象,以及節省了多少磁盤空間。
git pull git-pull 對于 HTTP 傳輸來說,一個打包過的版本庫會将一定數量的相關聯的對象放進一個有關聯性的打包中。如果你設想多次從 HTTP 公共版本庫中導入數據,你也許要頻繁地 reapck & prune,要麼就幹脆從不這樣做。
如果你此時再次運行 git-repack,它就會說 "Nothing to pack"。要是你繼續開發,并且積累了一定數量的變遷,再運行 git-repack 将會創建一個新的包,它會包含你自上次對庫打包以來創建的對象。我們建議你盡快在初始化提交之後打包一下你的版本庫(除非你的項目是個塗鴉式的草稿項目),并且在項目經曆過一段很活躍的時期時,再運行 git-repack 一下。
當一個版本庫通過 git-push 和 git-pull 命令來同步源版本庫中打包過的對像的時候,通常保存到目标版本庫中的是解包了的對象,除非你使用的是 rsync(遠程同步協議)協議的傳輸方式。正是這種容許你在兩頭的版本庫中有不同的打包策略的方式,他意味着你也許在過一段時間之後,需要在兩頭的版本庫中都重新打包一下。
發布工作
我們可以通過一個遠程的版本庫來利用他人的工作,但是,你如何準備一個自己的版本庫來供其他人下載呢?你在自己的工作目錄下進行工作,這樣你的版本庫就被作為.git的一個子目錄放在你的工作樹下。你可以讓其他人來遠程的訪問你的版本庫,但是實際上這不是通常的做法。推薦的做法是創建一個公共的版本庫,讓它可供其他人訪問,并且,當你在你的工作目錄下做了很好的改動時,你可以更新到公共的版本庫中。這通常稱為pushing。
公共版本庫是可以被映像的,上的git公共版本庫也是這樣管理的。
從你的本地的(私有的)版本庫中發布改動到你的遠程的(公共的)版本庫中需要遠程機器上的寫權限。你需要一個SSH的帳号來運行一個簡單的命令,git-receive-pack。首先,你需要在遠程機器上創建一個空的版本庫來存放你的公共版本庫。這個空版本庫以後将通過pushing來保持更新。顯然,這個版本庫之需要在開始的時候創建一次。
git push使用一對命令,git-send-pack在本地機上運行,git-receive-pack在遠程機上運行。這兩個命令通過SSH連接來進行通訊。
你本地的版本庫的git目錄通常是.git,但是你的公共版本庫通常還要加上你的項目名,即.git。讓我們來為my-git創建這樣一個版本庫。首先,登入遠程的機器,創建一個空目錄(如果你選擇HTTP作為發布方法,這個空目錄需要建在web server的根目錄下面):
$ mkdir my-git.git
然後運行git init-db命令将這個目錄加入git版本庫中,這裡,因為這個版本庫的名字不是通常的.git,我們需要稍微改動一下命令:
$ GIT_DIR=my-git.git git-init-db
有很多種傳輸方式可以發布公共版本庫。這裡,要确認這個目錄可以通過你選擇的傳輸方式來被其他人訪問。你也需要确認你有git-receive-pack這個程序在$PATH這個路徑下。
當你直接運行程序的時候,很多sshd的安裝版并沒有将你的shell作為登陸的shell;這就是說,如果你登陸的shell是bash 的話,被讀到的是.bashrc而不是.bash_profile。确認.bashrc設置好了$PATH路徑,這樣你才可以運行git-receive-pack命令。
如果你打算通過HTTP來發布這個版本庫,這是你就應該運行命令chmod +x my-git.git/hooks/post-update。這确認了每次你導入數據到這個版本庫中,git-update-server-info能夠被執行。
然後,你的“公共的版本庫”可以接受你的任何改動了。回到你的本地機上,運行命令:
$ git push :/path/to/my-git.git master
該命令将你的公共版本庫和你當前的版本庫中指定名稱的分支頭部同步(這裡是master)。舉一個實際的例子,你可以這樣來更新公共的git版本庫。的鏡像網絡也這樣來同步其他公共的可訪問的機器:
将工作捆綁到一起
通過 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 Octopus)git-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* david
(1) 将所有的開發人員都作為 git 組的成員。
(2) 并且給予他們公共版本庫的寫權限。
(3) 用一個在 Documentation/howto/ 中的 Carl 寫的例子來實現版本庫的分支控制策略。
(4) Alice 和 Cindy 可以提交入 master 分支,隻有 Bob 能提交入 doc-update 分支,
David 則是發行經理隻有他能創建并且 push 版本标簽。
支持默協議傳輸的 HTTP 服務器。
dev$ git update-server-info (1)
ftp> cp -r .git /home/user/myproject.git
(1) 保證 info/refs 和 object/info/packs 是最新的。
(2) 上傳到你的 HTTP 服務器主機。
修改author
有時候,忘了做git config設置或config的email不規範,導緻git log中author不對,造成溝通困難。 此時,你可以遵循如下步驟,修改author信息:
1、首先,你需要設置正确的user#(“#”換成“.”)name和user.email信息,注:請務必使用公司郵箱(gitlab用戶請和證書郵箱保持一緻,否則無法push,請打開gitlab.your-web#com(“#”換成“.”) 點擊右上角的profile,看看自己的郵箱是什麼)
git config --global user#(“#”換成“.”)name "你的名稱"git config --global user.email "你的公司郵箱"
注:去掉--global參數是單獨為當前項目設置
2、設置好後,修改你前面已提交的不正确的信息:
linux下在庫根目錄運行命令(windows請看最後一節): git-m (請先安裝此命令:sudo yum install git-m -b test) 1)向導會讓你輸入需要修正的email(括弧内提示會自動給你找到不規範的email,你可以直接回車) 2)輸入需要替換成正确的用戶名 3)輸入需要替換成正确的email(公司郵箱)
此時,程序會自動找出所有不合規範email的,并試圖自動修複你本地尚未push的修改。
3、對不支持rpm的用戶,可以通過 wget http://gitlab-help.gitlab.your-web#(“#”換成“.”)com/git-m 來獲取git-m命令。 你也可以手工運行git filter-branch -f --commit-filter 命令來修改author信息。
開發模式
盡管 git 是一個正式項目發布系統,它卻可以方便地将你的項目建立在松散的開發人員組織形式上。 Linux内核的開發,就是按這樣的模式進行的。在 Randy Dunlap 的著作中("Merge to Mainline" 第17頁)就有很好的介紹
需要強調的是正真的非常規的開發組織形式, 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 形式的補丁并發給上遊的維護者。回到第二步接着工作。
技巧
1. 在最後提交中更改Export(Export changes done in last commit )
這個命令通常會使用定期發送已更改的項目,以方便其他人審查/集成。
gitarchive-o../updated.zipHEAD$(gitdiff--name-onlyHEAD^)
2. 在兩次提交之間更改Export文件(Export changed files between two commits)
同樣地,如果你需要在兩次提交之間更改文件,可以選擇以下這段代碼。
gitarchive-o../latest.zipNEW_COMMIT_ID_HERE$(gitdiff--name-onlyOLD_COMMIT_ID_HERENEW_COMMIT_ID_HERE)
3. 克隆一個特定的遠程分支(Clone a specific remote branch)
如果你想從遠程資源庫中克隆一個特定的分支,而無需克隆整個資源庫分支,那麼下面的這段代碼将對你有用。
git init
gitremoteadd-tBRANCH_NAME_HERE-foriginREMOTE_REPO_URL_PATH_HERE
gitcheckoutBRANCH_NAME_HERE
4. 從不相關的本地資源庫中應用補丁(Apply patch from Unrelated local repository)
這裡有個快捷方式可幫助你實現。
viewplaincopytoclipboardprint?
git--git-dir=PATH_TO_OTHER_REPOSITORY_HERE/.gitformat-patch-k-1--stdoutCOMMIT_HASH_ID_HERE|gitam-3-k
5. 檢查分支是否在其它分支中遭到更改(Check if your Branch changes are part of Other branch)
cherry這個命令,能夠檢查你的分支在其他分支中是否被更改。它會在當前的分支上顯示變化,并注明+或-标識符。+代表不存在,-表示在現有的分支中存在。
viewplaincopytoclipboardprint?
gitcherry-vOTHER_BRANCH_NAME_HERE
#Forexample:tocheckwithmasterbranch
gitcherry-vmaster
6. 啟動一個無曆史記錄的新分支( Start a new Branch with No History)
有時,你想啟動一個新的分支,但并不想運行漫長的曆史記錄,例如,你想将代碼放置在一個公共的域中(開源),但又不想共享曆史。
gitcheckout--orphanNEW_BRANCH_NAME_HERE
7. 從其他分支簽出文件但無需切換分支( Checkout File from Other Branch without Switching Branches )
這裡将教你如何獲取想要的文件。
git checkout BRANCH_NAME_HERE--PATH_TO_FILE_IN_BRANCH_HERE
8. 忽略追蹤文件中的更改( Ignore Changes in a Tracked File )
如果你是在某個團隊中工作,他們都在使用同一個分支,也許你會頻繁使用提取/合并(fetch/merge),但這有時需要重置特定的配置文件,這就意味着在每次合并後你必須去做更改。現在,使用這個命令,你可以要求Git忽略更改特定文件。
git update-index--assume-unchangedPATH_TO_FILE_HERE
9. 檢查已提交部分是否在發布的版本中遭到更改(Check if committed changes are part of a release)
name-rev這個命令可以告訴你已提交到最新版本的某個位置。使用這個代碼可幫助你檢查,提交的部分是否在已發布版本中遭到更改。
git name-rev--name-onlyCOMMIT_HASH_HERE
10. 用複位替代合并(Pull with rebase instead of merge )
當某項特性分支被合并到主流中,此時該分支合并會在Git中以合并提交來進行記錄。但是當團隊中多個成員在同一個分支上工作時,常規的合并會導緻多個合并消息在日志中呈現混亂狀态。因此,你可以使用複位(rebase)來保持曆史清晰,清除無用的合并消息。
git pull--rebase
此外,你還可以通過配置一個特定的分支來複位。
git configbranch.BRANCH_NAME_HERE.rebasetrue
11. 保存http用戶/密碼,增加http上傳數據的大小
git config --global credential.helper store
git config --global http.postBuffer 524288000