本文是我使用 Git 一段時間和看過一些資料後的總結,以及個人見解,深感 Git 的規範使用非常重要,不規範的使用會帶來很多麻煩。
同類工具比較
SVN與Git
原理上
- Git直接記錄檔案快照,SVN每次記錄哪些檔案作了更新、更新哪些行的內容
- Git 有本地倉庫和遠端倉庫,SVN沒有本地倉庫
- Git 大多數操作是本地執行,SVN操作幾乎都需要連線網路
操作上
- Git提交後儲存在本地倉庫,需要推到遠端倉庫;SVN提交後在遠端倉庫
- Git有各種“反悔”命令,SVN幾乎沒有
- Git有真正的branch,svn的branch其實是工作空間的副本
Git的使用現狀
- 意義不明的提交資訊
- 糟糕的版本樹圖
存在的問題
- 合併解決衝突容易出錯
- 回滾時難以通過提交資訊尋找回滾版本號
- 合併點可能看不出此次合併影響的檔案
Git 工作流
Gitflow 工作流
Gitflow 為不同的分支分配一個很明確的角色,並定義分支之間如何和什麼時候進行互動。分別有歷史分支、功能分支、釋出分支和維護分支。
歷史分支
使用兩個分支來記錄專案的歷史。
master
分支記錄了正式釋出的歷史,而develop
分支作為功能的整合分支。因此,master
分支的每次提交都應分配一個版本號。
功能分支
功能分支是從develop
中checkout
出來的新分支,每個功能對應一個分支。
1.假設開發a功能:
git checkout -b feature-a develop
複製程式碼
2.當新功能完成時,合併回develop分支。
git checkout develop
git merge --no-ff feature-a
git push
git branch -d feature-a
複製程式碼
釋出分支
1.當develop
分支開發到需要釋出時,從develop
分支拉出一個釋出分支,命名為release-*
或release/*
。
git checkout -b release-0.1 develop
複製程式碼
2.該分支用於釋出迴圈,只做bug修復、文件生成等面向釋出的任務。新功能不再新增到這個分支上。
3.一旦釋出完成,把釋出分支merge
到master
分支上。
git checkout master
git merge --no-ff release-0.1
git push
複製程式碼
4.打tag
記錄版本號,方便跟蹤每次釋出。
git tag -a 0.1 -m "release 0.1 publish" master
git push --tags
複製程式碼
5.把這些從新建釋出分支以來做的修改merge
到develop
分支。
git checkout develop
git merge --no-ff release-0.1
git push
複製程式碼
6.最後刪除釋出分支
git branch -d release-0.1
複製程式碼
維護分支/熱修復
當線上版本出現bug時,就需要用到維護分支,它用於快速給產品釋出版本打補丁。
1.從master
分支拉一個維護分支(這是唯一從master
分支拉出來的分支)。
git checkout -b hotfix master
複製程式碼
2.修復完成後,馬上合併回master
和develop
。
git checkout master
git merge --no-ff hotfix
git push
git checkout develop
git merge --no-ff hotfix
git push
git branch -d hotfix
複製程式碼
3.master
用新版本號打tag
。
git tag -a 0.2 -m "release 0.2 publish" master
git push --tags
複製程式碼
圖解
圖片來自網路優點
- 單個功能獨立開發,並行開發互不干擾
master
和develop
分支分別記錄釋出和功能開發的歷史- 由於有釋出分支,其他暫不釋出的功能的開發不受釋出的影響,可以繼續提交
- 維護分支能快速打補丁,不影響正在開發的功能
缺點
- 複雜,分支繁多
- Git GUI不支援,純命令列
- 對開發者要求高(理解工作流,熟悉Git命令)
- 所有功能分支基於不穩定的
develop
- 需要維護兩個長期分支
master
和develop
GitHub Flow
GitHub 使用的工作流
- 所有在
master
上的東西都是可釋出的(已釋出或馬上釋出) - 開發新功能時,從
master
拉一個名稱清晰的新分支 - 在本地提交到這個分支的同時把它
push
到遠端倉庫 - 當你需要得到反饋或幫助,或者該分支準備
merge
時,開啟一個pull request
- 該分支被review且同意合併後,合併到
master
push
到master
後,應該立即釋出
優點
- 操作簡單
- 主幹的程式碼有質量保證
缺點
- 測試線和正式環境的釋出沒有區分
Git-Develop
develop
作為固定的持續整合和釋出分支
- 每一個功能都從
master
拉一個功能分支。 - 在這個功能分支上開發,功能完成到釋出時,提交code review,通過後自動合併到
develop
。 - 待所有計劃釋出的變更分支程式碼都合併到
develop
後,rebase master
到develop
,完成釋出。 - 應用釋出成功後打一個
tag
。 develop
分支的釋出版本合併回master
。
優點
- 操作相對簡單
- 流程稍作改動,即可區分測試線和正式環境
- 每次開發都基於正式版本最新的程式碼(
master
),和當前開發的其他分支不產生依賴關係 master
始終是已釋出狀態
缺點
暫時想不到。。。
Pull Requests
Pull requests
不是一種工作流,而是一個能讓開發者更方便地進行協作的功能,可以在提議的修改合併到正式專案之前對修改進行討論。這種方式對分支的合併有一些限制,例如只有專案維護者有許可權合併分支到倉庫中。其工作方式:
- 開發者在本地倉庫新建一個功能分支。
- 功能完成後,開發者
push
分支修改到遠端倉庫中。 - 開發者發起
Pull requests
。 - 團隊成員收到通知,進行code review,討論和修改。
- 專案維護者合併功能到倉庫中並關閉
Pull Requests
。
Git使用技巧
git commit --amend
git commit --amend
最常見的用法是上次提交資訊寫錯,或提交檔案多了或漏了之時,重新提交覆蓋上一次提交。
其實它還有一個用法,就是用來合併提交。例如上次提交的修改並不完全,再作修改之後可以用該命令把本次提交與上次提交合並在一起。
git reset
在需要回滾一次或多次提交時,可以用git reset
。由於該命令比較危險,建議用於已經把最新提交推到遠端倉庫上的本地分支。
- git reset HEAD [filename]
把已在暫時區的檔案取消,恢復到已修改未暫存狀態。
- git reset HEAD~[n]
git reset
後面可帶引數HEAD~[n]
(n >= 1)。表示回退到n
個提交之前。同時,它也可以用來合併提交。下面的寫法與git commit --amend
結果是一樣的。
git reset HEAD~1
git commit
複製程式碼
下面的用法則是合併了多次提交
git reset HEAD~2
git commit
複製程式碼
- git reset [version]
git reset
後面也可以帶版本號,直接回退到指定版本。
-
git reset的三種引數
- 使用引數
--hard
,如git reset --hard [version]
會執行以下操作:- 替換引用的指向。引用指向新的提交ID。
- 替換暫存區。替換後,暫存區的內容和引用指向的目錄樹一致。
- 替換工作區。替換後,工作區的內容變得和暫存區一致,也和HEAD所指向的目錄樹內容相同。
- 使用引數
--soft
,如git reset --soft [version]
會執行上述的操作a。即只更改引用的指向,不改變暫存區和工作區。 - 使用引數
--mixed
或者不使用引數(預設為--mixed
),如git reset [version]
會執行上述的操作a和b。即更改引用的指向及重置暫存區,但是不改變工作區。
- 使用引數
git merge --no-ff
--no-ff
是不快速合併的意思
與git merge的區別
git merge
的結果:
被merge的分支和當前分支在圖形上併為一條線,被merge的提交點逐一合併到當前分支。
git merge --no-ff
的結果:
被merge的分支和當前分支不在一條線上,被merge的提交點還在原來的分支上,同時在當前分支上產生一個合併點。
git rebase
git rebase
一般解釋為變基
,也有解釋為衍合
,個人覺得變基
比較容易理解。
與git merge的區別
git merge
是把兩個分支的程式碼合併到一起,其實git rebase
也是相同的作用,但是表現上是兩種不同的形式。
例如現在 dev 提交了一次,master 在此之後也提交了一次,兩個分支的狀態如下:
- 提交點順序
可以看出git merge
後,無論加不加--no-ff
引數,提交點的順序都和提交的時間順序相同,即 master 的提交在 dev 之後,如圖:
而git rebase
後,順序就變成被rebase
的分支(master)所有提交都在前面,進行rebase
的分支(dev)提交都在被rebase
的分支之後,在同一分支上的提交點仍按時間順序排列,如圖:
- 變基
從上面的圖可以看出,dev 在rebase
master 後,分支發生了變化,原本是兩個分支,rebase
的結果看起來是: dev 是基於 master 的分支,且產生了一些新提交。
一般來說,rebase
後的 dev 和遠端的origin/dev
會發生分離,在命令列介面中會提示:
Your branch and 'origin/dev' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
複製程式碼
這時需要用git push -f
強制推送,覆蓋遠端分支。若使用了提示中的git pull
,結果會變成合並,併產生一個合併提交點。
注:慎用git push -f
!
git pull --rebase
注意git pull
時請加上--rebase
,理由下面會說。
與git pull的區別
在一般情況下,加與不加--rebase
是沒有區別的。然而,結合上面說的git rebase
功能,可以知道某個分支可能與其遠端分支發生分離,而當你pull
時,你的本地分支還是和原來的遠端分支一樣,這時如果使用git pull
,則會變成你的本地分支和遠端分支合併,正確的做法是git pull --rebase
,才會拉取到最新的分支。
所以推薦在任何時候pull
遠端分支,最好加上--rebase
引數。
git reflog
檢視提交記錄的命令是git log
,而git reflog
的功能是檢視本地操作記錄,如此一來可以看到本地的commit
, merge
, rebase
等操作記錄。
6fe46ab HEAD@{0}: rebase finished: returning to refs/heads/dev
6fe46ab HEAD@{1}: rebase: dev modify a
2c92bcb HEAD@{2}: rebase: checkout master
9b26f5d HEAD@{3}: reset: moving to 9b26f5db1e8597b884c45114fbbff36c440da274
5531fc0 HEAD@{4}: merge master: Merge made by the 'recursive' strategy.
9b26f5d HEAD@{5}: checkout: moving from master to dev
複製程式碼
Git工具推薦
- SourceTree
- GUI Clients
參考資料
Pro Git,Git工具書: http://iissnan.com/progit/html/zh/ch1_5.html
Git 工作流指南,介紹了幾種主流工作流: https://github.com/xirong/my-git/blob/master/git-workflow-tutorial.md
Gitflow 有害論: http://insights.thoughtworkers.org/gitflow-consider-harmful/
GitHub Flow: http://scottchacon.com/2011/08/31/github-flow.html
Google 的“主幹開發”(trunk-based development): http://www.ruanyifeng.com/blog/2016/07/google-monolithic-source-repository.html