本文有點長並且有點亂,但就像Mark Twain Blaise Pascal的笑話裏說的那樣:我沒有時間讓它更短些。在Git的郵件列表裏有不少關於本文的討論,我會盡可能把其中相關的觀點列在下面。git
我最常說的關於git使用的一個經驗就是:安全
不要用git pull,用git fetch和git merge代替它。
git pull的問題是它把過程的細節都隱藏了起來,以致於你不用去了解git中各類類型分支的區別和使用方法。固然,多數時候這是沒問題的,但一旦代碼有問題,你很難找到出錯的地方。看起來git pull的用法會使你吃驚,簡單看一下git的使用文檔應該就能說服你。服務器
將下載(fetch)和合並(merge)放到一個命令裏的另一個弊端是,你的本地工做目錄在未經確認的狀況下就會被遠程分支更新。固然,除非你關閉全部的安全選項,不然git pull在你本地工做目錄還不至於形成不可挽回的損失,但不少時候咱們寧願作的慢一些,也不肯意返工重來。ssh
分支(Branches)ide
在說git pull以前,咱們須要先澄清分支的概念(branches)。不少人像寫代碼似的用一行話來描述分支是什麼,例如:fetch
我認爲你應該這樣來理解分支的概念:它是用來標記特定的代碼提交,每個分支經過SHA1sum值來標識,因此對分支進行的操做是輕量級的--你改變的僅僅是SHA1sum值。ui
這個定義或許會有意想不到的影響。好比,假設你有兩個分支,「stable」 和 「new-idea」, 它們的頂端在版本 E 和 F:this
A-----C----E ("stable") \ B-----D-----F ("new-idea")
因此提交(commits) A, C和 E 屬於「stable」,而 A, B, D 和 F 屬於 「new-idea」。若是以後你用下面的命令 將「new-idea」 merge 到 「stable」 :idea
git checkout stable # Change to work on the branch "stable" git merge new-idea # Merge in "new-idea"
…那麼你會獲得這個:spa
A-----C----E----G ("stable") \ / B-----D-----F ("new-idea")
要是你繼續在「new idea」 和「stable」分支提交, 會獲得:
A-----C----E----G---H ("stable") \ / B-----D-----F----I ("new-idea")
所以如今A, B, C, D, E, F, G 和 H 屬於 「stable」,而A, B, D, F 和 I 屬於 「new-idea」。
固然了,分支確實有些特殊的屬性——其中最重要的是,若是你在一個分支進行做業並建立了一個新的提交(commits),該分支的頂端將前進到那個提交(commits)。這正是你所但願的。當用git merge 進行合併(merge)的時候,你只是指定了要合併到當前分支的那個併入分支,以及當前分支的當前進展。
另外一個代表使用分支會有很大幫助的觀點的常見情形是:假設你直接工做在一個項目的主要分支(稱爲「主版本」),當你意識到你所作的多是一個壞主意時已經晚了,這時你確定寧願本身是工做在一個主題分支上。若是提交圖看起來像這樣:
last version from another repository | v M---N-----O----P---Q ("master")
那麼你把你的工做用下面的一組命令分開作(如圖顯示的是執行它們以後所更改的狀態):
git branch dubious-experiment M---N-----O----P---Q ("master" and "dubious-experiment") git checkout master # Be careful with this next command: make sure "git status" is # clean, you're definitely on "master" and the # "dubious-experiment" branch has the commits you were working # on first... git reset --hard <SHA1sum of commit N> ("master") M---N-------------O----P---Q ("dubious-experiment") git pull # Or something that updates "master" from # somewhere else... M--N----R---S ("master") \ O---P---Q ("dubious-experiment")
這是個看起來我最終作了不少的事情。
分支類型
分支這個術語不太容易理解,並且在git的開發過程當中發生了不少變化。但簡單來講git的分支只有兩種:
a)「本地分支(local branches)」 ,當你輸入「git branch」時顯示的。例以下面這個小例子:
$ git branch debian server * master
b)「遠程跟蹤分支(Remote-tracking branches)」 ,當你輸入「git branch -r」是顯示的,如:
$ git branch -r cognac/master fruitfly/server origin/albert origin/ant origin/contrib origin/cross-compile
從上面的輸出能夠看到,跟蹤分支的名稱前有一個「遠程的」標記名稱(如 :origin, cognac, fruitfly)後面跟一個「/」,而後遠程倉庫裏分支的真正名稱。(「遠程名稱」是一個代碼倉庫別名,和本地目錄或URL是一個含義,你能夠經過"git remote"命令自由定義額外的「遠程名稱」。但「git clone」命令默認使用的是「origin」這個名稱。)
若是你對分支在本地是如何存儲感興趣的話,看看下面文件:
兩種類型的分支在某些方面十分類似-它們都只是在本地存儲一個表示提交的SHA1校驗和。(我強調「本地」,由於許多人看到"origin/master" 就認爲這個分支在某種意義上說是不完整的,沒有訪問遠端服務器的權限- 其實不是這種狀況。)
無論如何類似,它們仍是有一個特別重大的區別:
所以,你對遠端跟蹤分支最多能作的是下面事情中的一件:
若是你想基於遠程跟蹤分支建立本地分支(在本地分支上工做),你可使用以下命令:git branch –track或git checkout –track -b,兩個命令均可以讓你切換到新建立的本地分支。例如你用git branch -r命令看到一個遠程跟蹤分支的名稱爲「origin/refactored」是你所須要的,你可使用下面的命令:
git checkout --track -b refactored origin/refactored
在上面的命令裏,「refactored」是這個新分支的名稱,「origin/refactored」則是現存遠程跟蹤分支的名稱。(在git最新的版本里,例子中‘-track’選項已經不須要了,若是最後一個參數是遠程跟蹤分支,這個參數會被默認加上。)
「–track」選項會設置一些變量,來保持本地分支和遠程跟蹤分支的相關性。他們對下面的狀況頗有用:
Your branch and the tracked remote branch 'origin/master' have diverged, and respectively have 3 and 384 different commit(s) each.
或者:
Your branch is behind the tracked remote branch 'origin/master' by 3 commits, and can be fast-forwarded.
容許使用的配置變量是:「branch.<local-branch-name>.merge」和「branch.<local-branch-name>.remote」,但一般狀況下你不用考慮他們的設置。
當從遠程代碼倉庫建立一個本地分支以後,你會注意到,「git branch -r」能列出不少遠程跟蹤分支,但你的電腦上只有一個本地分支,你須要給上面的命令設置一個參數,來指定本地分支和遠程分支的對應。
有一些術語上的說法容易混淆須要注意一下:「track」在看成參數"-track"使用時,意思指經過本地分支對應一個遠程跟蹤分支。在遠程跟蹤分支中則指遠程代碼倉庫中的跟蹤分支。有點繞口。。。
下面咱們來看一個例子,如何從遠程分支中更新本地代碼,以及如何把本地分支推送到一個新的遠程倉庫中。
若是我想從遠端的源倉庫更新到本地的代碼倉庫,能夠輸入「git fetch origin」的命令,該命令的輸入相似以下格式:
remote: Counting objects: 382, done. remote: Compressing objects: 100% (203/203), done. remote: Total 278 (delta 177), reused 103 (delta 59) Receiving objects: 100% (278/278), 4.89 MiB | 539 KiB/s, done. Resolving deltas: 100% (177/177), completed with 40 local objects. From ssh://longair@pacific.mpi-cbg.de/srv/git/fiji 3036acc..9eb5e40 debian-release-20081030 -> origin/debian-release-20081030 * [new branch] debian-release-20081112 -> origin/debian-release-20081112 * [new branch] debian-release-20081112.1 -> origin/debian-release-20081112.1 3d619e7..6260626 master -> origin/master
最重要的是這兩行:
3036acc..9eb5e40 debian-release-20081030 -> origin/debian-release-20081030 * [new branch] debian-release-20081112 -> origin/debian-release-20081112
第一行代表遠端的origin/debian-release-20081030分支的提交(commit)ID已經從3036acc更新爲9eb5e40。箭頭前的部分是遠端分支的名稱。第二行是咱們採起的動做,建立遠程跟蹤分支(若是遠程倉庫有新的tags,git fetch也會一併下載到本地)。
前面那些行顯示出「git fetch」命令會將哪些文件下載到本地,這些文件一旦下載到本地以後,就能夠在本地進行任意操做了。
「git fetch」命令執行完畢以後,還不會當即將下載的文件合併到你當前工做目錄裏,這就給你了一個選擇下一步操做的機會,要是想將從遠程分支下載的文件更新到你的工做目錄裏,你須要執行一個「合併(merge)」操做。例如,我當前的本地分支爲」master「(執行git checkout master後),這時我想執行合併操做:
git merge origin/master
( 幾句題外話:合併的時候有可能你尚未對遠程分支提交過任何的更改,或者多是一個複雜的合併。)
若是你只是想看看本地分支和遠程分支的差別,你可使用下面的命令:
git diff master origin/master
單獨進行下載和合並是一個好的作法,你能夠先看看下載的是什麼,而後再決定是否和本地代碼合併。並且分開來作,能夠清晰的區別開本地分支和遠程分支,方便選擇使用。
如何經過其餘的方式呢? 假設你對 「experimental」分支作了變動而且但願把他push到"origin"遠程倉庫中去. 你能夠這樣作:
1 |
git push origin experimental |
你可能將會收到:遠程倉庫沒法fast-forward該分支的錯誤信息, 這將意味着可能有別人push了不一樣的變動到了這個分支上.因此,你須要fetch和merge別人的變動並再次嘗試push操做.
擴展閱讀: 若是這個分支在遠程倉庫裏對應不一樣的名稱(如:experiment-by-bob),你應該這麼作:
git push origin experimental:experiment-by-bob
在舊版本的git裏,若是「experiment-by-bob」不存在,命令應該這麼寫:
git push origin experimental:refs/heads/experiment-by-bob
這樣會首先建立遠程分支。但git 1.6.1.2應該就不用這麼作了。參加下面Sitaram’s的評論。
若是本地分支和遠程分支名稱相同,不須要特殊說明系統將會自動建立這個分支,就像常規的git push操做同樣。
在實際應用中,保持名稱相同能夠減小混淆,所以「本地名稱和遠程名稱」做爲「refspec」參數,咱們不會進行更多的討論。
git push的操做不會牽扯遠程跟蹤分支(origin/experimental),只有在你下次進行git fetch時纔會被更新。
上面這個說法不對,根據Deskin Miller的評論糾正:當推送到對應的遠程分支後,你的遠程跟蹤分支就會被更新。
雖然 git pull 大部分時候是好的,特別是若是你用CVS類型的方式使用Git時,它可能正適合你。然而,若是你想用一個更地道的方式(創建不少主題分支,當你須要時隨時改寫本地歷史,等等)使用Git,那麼習慣把 git fetch 和 git merge 分開作會有很大幫助。