轉到分散式版本控制系統看起來像個令人生畏的任務,但不改變已用的工作流你也可以用上Git
帶來的收益。團隊可以用和Subversion
完全不變的方式來開發專案。
但使用Git
加強開發的工作流,Git
比SVN有
幾個優勢。首先,每個開發可以有屬於自己的整個工程的本地拷貝。隔離的環境讓各個開發者的工作和專案的其他部分(修改)獨立開來 —— 即自由地提交到自己的本地倉庫,先完全忽略上游的開發,直到方便的時候再把修改反饋上去。
其次,Git
提供了強壯的分支和合並模型。不像SVN
,Git
的分支設計成可以做為一種用來在倉庫之間整合程式碼和分享修改的『失敗安全』的機制。
工作方式
像Subversion
一樣,集中式工作流以中央倉庫作為專案所有修改的單點實體。相比SVN
預設的開發分支trunk
,Git
叫做master
,所有修改提交到這個分支上。該工作流只用到master
這一個分支。
開發者開始先克隆中央倉庫。在自己的專案拷貝中,像SVN
一樣的編輯檔案和提交修改;但修改是存在本地的,和中央倉庫是完全隔離的。開發者可以把和上游的同步延後到一個方便時間點。
要釋出修改到正式專案中,開發者要把本地master
分支的修改『推(push)』到中央倉庫中。這相當於svn commit
操作,但push
操作會把所有還不在中央倉庫的本地提交都推上去。
衝突解決
中央倉庫代表了正式專案,所以提交歷史應該被尊重且是穩定不變的。如果開發者本地的提交歷史和中央倉庫有分歧,Git
會拒絕push
提交否則會覆蓋已經在中央庫的正式提交。
在開發者提交自己功能修改到中央庫前,需要先fetch
在中央庫的新增提交,rebase
自己提交到中央庫提交歷史之上。這樣做的意思是在說,『我要把自己的修改加到別人已經完成的修改上。』最終的結果是一個完美的線性歷史,就像以前的SVN
的工作流中一樣。
如果本地修改和上游提交有衝突,Git
會暫停rebase
過程,給你手動解決衝突的機會。Git
解決合併衝突,用和生成提交一樣的git status
和git add
命令,很一致方便。還有一點,如果解決衝突時遇到麻煩,Git
可以很簡單中止整個rebase
操作,重來一次(或者讓別人來幫助解決)。
示例
讓我們一起逐步分解來看看一個常見的小團隊如何用這個工作流來協作的。有兩個開發者小明和小紅,看他們是如何開發自己的功能並提交到中央倉庫上的。
有人先初始化好中央倉庫
第一步,有人在伺服器上建立好中央倉庫。如果是新專案,你可以初始化一個空倉庫;否則你要匯入已有的Git
或SVN
倉庫。
中央倉庫應該是個裸倉庫(bare repository
),即沒有工作目錄(working directory
)的倉庫。可以用下面的命令建立:
ssh user@host
git init --bare /path/to/repo.git
確保寫上有效的user
(SSH
的使用者名稱),host
(伺服器的域名或IP地址),/path/to/repo.git
(你想存放倉庫的位置)。注意,為了表示是一個裸倉庫,按照約定加上.git
副檔名到倉庫名上。
所有人克隆中央倉庫
下一步,各個開發者建立整個專案的本地拷貝。通過git clone
命令完成:
git clone ssh://user@host/path/to/repo.git
基於你後續會持續和克隆的倉庫做互動的假設,克隆倉庫時Git
會自動新增遠端別名origin
指回『父』倉庫。
小明開發功能
在小明的本地倉庫中,他使用標準的Git
過程開發功能:編輯、暫存(Stage
)和提交。如果你不熟悉暫存區(Staging Area
),這裡說明一下:暫存區的用來準備一個提交,但可以不用把工作目錄中所有的修改內容都包含進來。這樣你可以建立一個高度聚焦的提交,儘管你本地修改很多內容。
git status # 檢視本地倉庫的修改狀態
git add # 暫存檔案
git commit # 提交檔案
請記住,因為這些命令生成的是本地提交,小明可以按自己需求反覆操作多次,而不用擔心中央倉庫上有了什麼操作。對需要多個更簡單更原子分塊的大功能,這個做法是很有用的。
小紅開發功能
與此同時,小紅在自己的本地倉庫中用相同的編輯、暫存和提交過程開發功能。和小明一樣,她也不關心中央倉庫有沒有新提交;當然更不關心小明在他的本地倉庫中的操作,因為所有本地倉庫都是私有的。
小明發布功能
一旦小明完成了他的功能開發,會發布他的本地提交到中央倉庫中,這樣其它團隊成員可以看到他的修改。他可以用下面的git push
命令:
git push origin master
注意,origin
是在小明克隆倉庫時Git
建立的遠端中央倉庫別名。master
引數告訴Git
推送的分支。由於中央倉庫自從小明克隆以來還沒有被更新過,所以push
操作不會有衝突,成功完成。
小紅試著釋出功能
一起來看看在小明發布修改後,小紅push
修改會怎麼樣?她使用完全一樣的push
命令:
git push origin master
但她的本地歷史已經和中央倉庫有分岐了,Git
拒絕操作並給出下面很長的出錯訊息:
error: failed to push some refs to '/path/to/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
這避免了小紅覆寫正式的提交。她要先pull
小明的更新到她的本地倉庫合併上她的本地修改後,再重試。
小紅在小明的提交之上rebase
小紅用git pull
合併上游的修改到自己的倉庫中。這條命令類似svn update
——拉取所有上游提交命令到小紅的本地倉庫,並嘗試和她的本地修改合併:
git pull --rebase origin master
--rebase
選項告訴Git
把小紅的提交移到同步了中央倉庫修改後的master
分支的頂部,如下圖所示:
如果你忘加了這個選項,pull
操作仍然可以完成,但每次pull
操作要同步中央倉庫中別人修改時,提交歷史會以一個多餘的『合併提交』結尾。對於集中式工作流,最好是使用rebase
而不是生成一個合併提交。
小紅解決合併衝突
rebase
操作過程是把本地提交一次一個地遷移到更新了的中央倉庫master
分支之上。這意味著可能要解決在遷移某個提交時出現的合併衝突,而不是解決包含了所有提交的大型合併時所出現的衝突。這樣的方式讓你儘可能保持每個提交的聚焦和專案歷史的整潔。反過來,簡化了哪裡引入Bug
的分析,如果有必要,回滾修改也可以做到對專案影響最小。
如果小紅和小明的功能是相關的,不大可能在rebase
過程中有衝突。如果有,Git
在合併有衝突的提交處暫停rebase
過程,輸出下面的資訊並帶上相關的指令:
CONFLICT (content): Merge conflict in
Git
很讚的一點是,任何人可以解決他自己的衝突。在這個例子中,小紅可以簡單的執行git status
命令來檢視哪裡有問題。衝突檔案列在Unmerged paths
(未合併路徑)一節中:
1 2 3 4 5 |
# Unmerged paths: # (use "git reset HEAD <some-file>..." to unstage) # (use "git add/rm <some-file>..." as appropriate to mark resolution) # # both modified: <some-file> |
接著小紅編輯這些檔案。修改完成後,用老套路暫存這些檔案,並讓git rebase
完成剩下的事:
git add
git rebase --continue
要做的就這些了。Git
會繼續一個一個地合併後面的提交,如其它的提交有衝突就重複這個過程。
如果你碰到了衝突,但發現搞不定,不要驚慌。只要執行下面這條命令,就可以回到你執行git pull --rebase
命令前的樣子:
git rebase --abort
小紅成功釋出功能
小紅完成和中央倉庫的同步後,就能成功釋出她的修改了:
git push origin master
下一站
如你所見,僅使用幾個Git
命令我們就可以模擬出傳統Subversion
開發環境。對於要從SVN
遷移過來的團隊來說這太好了,但沒有發揮出Git
分散式本質的優勢。
如果你的團隊適應了集中式工作流,但想要更流暢的協作效果,絕對值得探索一下功能分支工作流的收益。通過為一個功能分配一個專門的分支,能夠做到一個新增功能整合到正式專案之前對新功能進行深入討論。
譯註
自己理解粗淺,譯文原始碼在GitHub
上,翻譯中不足和不對之處,歡迎建議(提交Issue)和指正(Fork後提交程式碼)!
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式