Git 工做流的正確打開方式

轉載:http://www.cnblogs.com/woshimrf/p/git-workflow.htmlhtml

目錄

1.1.建立倉庫
1.2. 模擬用戶A
1.3. 模擬用戶B
1.4. 模擬用戶A
1.5. 模擬用戶C
1.6. 模擬用戶B
1.7. 模擬用戶C
2.1 模擬用戶C
2.2 模擬用戶D
2.3 C繼續開發
2.4 D繼續開發
2.5 C 提交
2.6 C 提PR
2.7 C修改再push
2.8 C發現提交次數過多,歷史太亂,合併部分歷史
2.9 C再次push
2.10 新的merge方式: rebase
2.11 這時候D也完成了
2.12 提交前rebase
最終結果

前言

一直在使用git作版本控制,也一直工做很順利,直到和別人發生衝突的時候。這才注意到git 工做流並非那麼簡單。好比,以前遇到的清理歷史。百度到的資料不少,重複性也不少,但實踐性操做不多,我很難直接理解其所表達的含義。直接望文生義常常獲得錯誤的結論,只能用時間去檢驗真理了,否則看到的結果都是似懂非懂,最後仍是一團糟。git

學習git工做流

1. 最簡單的使用,不推薦

1.1.建立倉庫

$ pwd
/home/ryan/workspace/l4git-workflow
$ touch readme.md
$ ls
readme.md
$ touch .gitignore
$ git init
初始化空的 Git 倉庫於 /home/ryan/workspace/l4git-workflow/.git/
$ touch test.txt
$ git add .
$ git commit -m "init" [master (根提交) dae77d6] init 3 files changed, 12 insertions(+) create mode 100644 .gitignore create mode 100644 readme.md create mode 100644 test.txt $ git remote add origin git@github.com:Ryan-Miao/l4git-workflow.git $ git push -u origin master 對象計數中: 5, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (3/3), 完成. 寫入對象中: 100% (5/5), 388 bytes | 0 bytes/s, 完成. Total 5 (delta 0), reused 0 (delta 0) To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] master -> master 分支 master 設置爲跟蹤來自 origin 的遠程分支 master。

1.2. 模擬用戶A

git clone git@github.com:Ryan-Miao/l4git-workflow.git
git checkout a
touch a.txt
//write one
//....
$ git add .
$ git commit -m "one" [a 53ff45e] one 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 a.txt

此時,a尚未提交到origin。 git log 以下:github

1.3. 模擬用戶B

git clone git@github.com:Ryan-Miao/l4git-workflow.git git checkout b
$ touch b.txt

//write something
//...sql

$ git add .
$ git commit -m "b write one" [b 847078e] b write one 1 file changed, 1 insertion(+) create mode 100644 b.txt

//write something
//....
$ git add . $ git commit -m "b write two" [b 3f30f41] b write two 1 file changed, 2 insertions(+), 1 deletion(-)編程

此時,git log以下
vim

1.4. 模擬用戶A

A和B分別是在本地開發,因此這種順序是未知的,也許A比B先commit一次,也許B先commit一次。這裏的前後是指commit的時間戳。但都是在本地提交的代碼。
write somethingsegmentfault

git add .
git commit -m "a write two"

wirte somethingwindows

git add .
git commit -m "write three"

A push to server branch abash

$ git push origin a:a Total 0 (delta 0), reused 0 (delta 0) To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] a -> a

A created a Pull Request服務器


1.5. 模擬用戶C

C review the PR and then merged it.


此時,github的歷史以下:

能夠看出,merge的時候多了一次commit,message默認爲 Merge pull request #1 from Ryan-Miao/a...
如今看起來,只有a一我的的歷史記錄,還算清楚,a作了3次提交。

1.6. 模擬用戶B

用戶B提交前先pull master,更新最新的代碼到本地,防止衝突。

git fetch
 git merge origin/master

此時log看起來有點亂。以下:

讓人感到混亂的是b原來的歷史只有本身的提交,更新了master到本地以後,歷史記錄被插入了master中的歷史。因而,發現原來本身乾淨的歷史被中間插入屢次commit。甚至兩次merge master的日誌顯得又長又礙眼。但無論怎麼說,B仍是要提交的。

因而,B提交到遠程分支b:

1.7. 模擬用戶C

這時候,A完成了feature a,而後提了PR,而後找他人C merge了。然後,B也完成了feature b,提了PR,須要review and merge。 C review以後,approved, 而後D review, D merge。

此時,項目基本走上正規。feature一個一個添加進去,重複以前的工做流程: fetch -》 work -》 commit -》 push -》 PR -》 merged。
而後,項目歷史就變成了這樣:

一眼大概看起來還好,每次都能看到提交歷史,只要不是message寫的特別少,差很少能夠理解最近提交的內容。然而,仔細一看,順序好像不對。目前一共兩個feature,但歷史卻遠遠超過2個。不要緊,保證細粒度更容易體現開發進度。然而,這些歷史並非按照feature的發佈順序,那麼,當我想要找到feature a的時候就很難串聯起來。若是commit足夠多,時間跨度足夠大,甚至根本看不出來feature a到底作了哪些修改。

這時候想要使用圖形化git 歷史工具來幫助理解歷史:

這裏,還好,還勉強能看出走向。但當10個上百我的同時開發的話,線簡直不能看了,時間跨度足夠大的話,線也看不完。

所以,這種模式,正是咱們本身當前採用的模式。差評。這還不算完,後面更大的困難來了。最早發佈的feature a出了問題,必須回滾。怎麼作到。關於回滾,就是另外一個話題了。 但咱們應該知道使用revert而不是reset. 但revert只能回滾指定的commit,或者連續的commit,並且revert不能revert merge操做。這樣,想回滾feature a, 咱們就要找到a的幾回提交的版本號,而後因爲不是連續的,分別revert。這會形成複雜到不想處理了。好在github給了方便的東西,PR提供了revert的機會。找到之前的PR。

可是,這絕對不是個好操做!


2. 推薦的工做流程

形成上述現象的緣由是由於各自異步編程決定的。由於每一個人均可以隨時間提交,最後合併起來的時候以提交時間戳來做爲序列的依據,就會變成這樣。所以,當須要提交的遠程服務器的時候,若是能重寫下commit的時間爲當前時間,而後push到服務端,歷史就會序列到最後了。

2.1 模擬用戶C

C用戶新下載代碼。

$ git clone git@github.com:Ryan-Miao/l4git-workflow.git c正克隆到 'c'... remote: Counting objects: 28, done. remote: Compressing objects: 100% (17/17), done. remote: Total 28 (delta 8), reused 22 (delta 4), pack-reused 0 接收對象中: 100% (28/28), 5.90 KiB | 0 bytes/s, 完成. 處理 delta 中: 100% (8/8), 完成. 檢查鏈接... 完成。 

而後編輯,提交

$ cd c
$ git config user.name "C"
$ ls
a.txt  b.txt  readme.md  test.txt
$ vim c.txt
$ git add .
$ git commit -m "C write one" [master cf3f757] C write one 1 file changed, 2 insertions(+) create mode 100644 c.txt 

2.2 模擬用戶D

同時,D也須要開發新feature

$ git clone git@github.com:Ryan-Miao/l4git-workflow.git d正克隆到 'd'... remote: Counting objects: 28, done. remote: Compressing objects: 100% (17/17), done. remote: Total 28 (delta 8), reused 22 (delta 4), pack-reused 0 接收對象中: 100% (28/28), 5.90 KiB | 0 bytes/s, 完成. 處理 delta 中: 100% (8/8), 完成. 檢查鏈接... 完成。 $ cd d /d$ git config user.name "D" /d$ vim d.txt /d$ git add . /d$ git commit -m "d write one" [master db7a6e9] d write one 1 file changed, 1 insertion(+) create mode 100644 d.txt 

2.3 C繼續開發

$ vim c.txt
$ git add .
$ git commit -m "c write two" [master 01b1210] c write two 1 file changed, 1 insertion(+) 

2.4 D繼續開發

/d$ vim d.txt
/d$ git add .
/d$ git commit -m "d write two" [master a1371e4] d write two 1 file changed, 1 insertion(+) 

2.5 C 提交

$ vim c.txt 
$ git add .
$ git commit -m "c write three" [master 13b7dde] c write three 1 file changed, 1 insertion(+) 

C開發結束,提交到遠程

$ git status
位於分支 master
您的分支領先 'origin/master' 共 3 個提交。 (使用 "git push" 來發布您的本地提交) 無文件要提交,乾淨的工做區 $ git push origin master:C 對象計數中: 9, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (6/6), 完成. 寫入對象中: 100% (9/9), 750 bytes | 0 bytes/s, 完成. Total 9 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), completed with 1 local object. To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] master -> C 

2.6 C 提PR

而後,create a Pull Request.

2.7 C修改再push

而後,發現還有個bug要修復,再次修改提交到遠程C

$ vim c.txt 
$ git add .
$ git commit -m "C finish something else" [master 2c5ff94] C finish something else 1 file changed, 1 insertion(+), 1 deletion(-) $ git push origin master:C 對象計數中: 3, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (3/3), 完成. 寫入對象中: 100% (3/3), 301 bytes | 0 bytes/s, 完成. Total 3 (delta 1), reused 0 (delta 0) remote: Resolving deltas: 100% (1/1), completed with 1 local object. To git@github.com:Ryan-Miao/l4git-workflow.git 13b7dde..2c5ff94 master -> C 

2.8 C發現提交次數過多,歷史太亂,合併部分歷史

這時,發現一個問題,因爲C在開發過程當中提交了屢次,而這幾回提交的message其實沒有多大意思,只是由於C可能爲了保存代碼,也多是暫存。總之,C的前3次提交的message的含義實際上是同樣的,都是建立C文件,都是一個主題,那麼爲了維護歷史的乾淨。最好把這3條信息合併成一條C create file c.txt

參考git 合併歷史,咱們須要將3次歷史合併成顯示爲一次。

查看git歷史,找到須要合併的起始區間

$ git log --oneline 2c5ff94 C finish something else 13b7dde c write three 01b1210 c write two cf3f757 C write one 7151f4c 記錄操做。 0bfe562 Merge pull request #2 from Ryan-Miao/b_remote d81ce20 Merge remote-tracking branch 'origin/master' into b 2d74cfb Merge pull request #1 from Ryan-Miao/a b90a3dd write three 4b1629e a write two 3f30f41 b write two 847078e b write one 53ff45e one dae77d6 init 

顯然,是要合併cf3f75713b7dde。那麼找到前一個的版本號爲7151f4c

git rebase - i 7151f4c

而後進入交互界面,由於咱們想要把第3次和第2次以及第1次提交信息合併。將第3次的類型修改成squash, 意思是和第2次合併。而後將第2次的類型修改成squash, 一樣是指合併的前一個commit。

不一樣git的交互略有不一樣,以前在windows上的git bash是徹底按照vim的命令修改的。本次測試基於Ubuntu,發現存檔命令爲ctel + X。確認後進入下一個界面,合併3次提交後須要一個message

刪除或者anyway you like, 更改message。存檔。完成。

$ git rebase -i 7151f4c
[分離頭指針 e3764c5] c create file c.txt Date: Fri Oct 20 22:06:24 2017 +0800 1 file changed, 4 insertions(+) create mode 100644 c.txt Successfully rebased and updated refs/heads/master.

Tips
當在rebase過程當中出現了失誤,可使用git rebase --abort返回初始狀態。若是發現衝突,則能夠解決衝突,而後git rebase --continue .

好像已有 rebase-merge 目錄,我懷疑您正處於另一個變基操做
過程當中。 若是是這樣,請執行
git rebase (--continue | --abort | --skip)
若是不是這樣,請執行
rm -fr "/home/ryan/temp/c/.git/rebase-merge"
而後再從新執行變基操做。 爲避免丟失重要數據,我已經中止當前操做。

此時,查看log, 顯然,C的那三次提交已經合併了。

$ git log --oneline 50b9fe9 C finish something else e3764c5 c create file c.txt 7151f4c 記錄操做。 0bfe562 Merge pull request #2 from Ryan-Miao/b_remote d81ce20 Merge remote-tracking branch 'origin/master' into b 2d74cfb Merge pull request #1 from Ryan-Miao/a b90a3dd write three 4b1629e a write two 3f30f41 b write two 847078e b write one 53ff45e one dae77d6 init

2.9 C再次push

以前的push已經不能用了。須要開新分支推送過去。由於 rebase 只能在本地分支作。不要修改公共分支 。

$ git push origin master:C To git@github.com:Ryan-Miao/l4git-workflow.git ! [rejected] master -> C (non-fast-forward) error: 沒法推送一些引用到 'git@github.com:Ryan-Miao/l4git-workflow.git' 提示:更新被拒絕,由於推送的一個分支的最新提交落後於其對應的遠程分支。 提示:檢出該分支並整合遠程變動(如 'git pull ...'),而後再推送。詳見 提示:'git push --help' 中的 'Note about fast-forwards' 小節。

選擇推送的新分支C2

$ git push origin master:C2 對象計數中: 6, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (5/5), 完成. 寫入對象中: 100% (6/6), 569 bytes | 0 bytes/s, 完成. Total 6 (delta 2), reused 0 (delta 0) remote: Resolving deltas: 100% (2/2), completed with 1 local object. To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] master -> C2 

建立新的PR

2.10 新的merge方式: rebase

經過開始的普通流程發現,每次merge的時候,都會多出一條新的提交信息,這讓歷史看起來很奇怪。那麼,能夠選擇rebase到master,變基,就是從新以master爲基本,把當前的提交直接移動到master的後面。不會由於提交時間的離散致使屢次commit的message被拆散。 選擇 rebase and merge

這時候,能夠看到C提交的兩次信息都是最新的,沒有發生交叉。並且也沒有產生多餘的merge信息。

有人會問,那麼豈不是看不到PR的地址了。點開C的歷史。能夠看到message下方是有PR的編號的:

對了,剛開始的PR要記得close

2.11 這時候D也完成了

/d$ git push origin master:D 對象計數中: 10, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (7/7), 完成. 寫入對象中: 100% (10/10), 4.49 KiB | 0 bytes/s, 完成. Total 10 (delta 2), reused 4 (delta 1) remote: Resolving deltas: 100% (2/2), completed with 1 local object. To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] master -> D 

提PR, 這時候,若是採用merge:

結果必然發現,1) d提交message被按照時間分散插入歷史了(被插入到c的歷史以前), 2)多了一次 Merge pull request #5 from Ryan-Miao/D..的提交信息。同開頭所述同樣,歷史開始變得混亂了。那麼,這種問題怎麼辦呢?

2.12 提交前rebase

就像C rebase後merge到master同樣。咱們同樣能夠在本地作到這樣的事情。在本地rebase,讓咱們本次feature的提交所有插到master節點以後,有序,並且容易revert。
本次,以新的E和F交叉commit爲例子,最終將獲得各自分開的歷史

E:

$ git clone git@github.com:Ryan-Miao/l4git-workflow.git e 正克隆到 'e'... remote: Counting objects: 52, done. remote: Compressing objects: 100% (33/33), done. remote: Total 52 (delta 18), reused 36 (delta 7), pack-reused 0 接收對象中: 100% (52/52), 7.91 KiB | 0 bytes/s, 完成. 處理 delta 中: 100% (18/18), 完成. 檢查鏈接... 完成。 $ cd e /e$ vim e.txt /e$ git add . /e$ git config user.name "E" /e$ git commit -m "e commit one" [master 77ecd73] e commit one 1 file changed, 1 insertion(+) create mode 100644 e.txt 

F:

$ git clone git@github.com:Ryan-Miao/l4git-workflow.git f 正克隆到 'f'... remote: Counting objects: 52, done. remote: Compressing objects: 100% (33/33), done. remote: Total 52 (delta 18), reused 36 (delta 7), pack-reused 0 接收對象中: 100% (52/52), 7.91 KiB | 0 bytes/s, 完成. 處理 delta 中: 100% (18/18), 完成. 檢查鏈接... 完成。 $ cd f $ vim f.txt $ git config user.name "F" $ git add . $ git commit -m "d write one" [master b41f8c5] d write one 1 file changed, 2 insertions(+) create mode 100644 f.txt 

E:

/e$ vim e.txt
/e$ git add .
/e$ git commit -m "e write two" [master 2b8c9fb] e write two 1 file changed, 1 insertion(+) 

F:

$ vim f.txt
$ git add .
$ git commit -m "f write two" [master de9051b] f write two 1 file changed, 1 insertion(+) 

E:

/e$ vim e.txt 
/e$ git add .
/e$ git commit -m "e write three" [master b1b9f6e] e write three 1 file changed, 2 insertions(+) 

這時候,e完成了,須要提交。提交前先rebase:

/e$ git fetch /e$ git rebase origin/master 當前分支 master 是最新的。

而後,再提交

/e$ git push origin master:E 對象計數中: 9, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (6/6), 完成. 寫入對象中: 100% (9/9), 753 bytes | 0 bytes/s, 完成. Total 9 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), completed with 1 local object. To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] master -> E 

而後, PR, merge.

一樣F:

$ git status
位於分支 master
您的分支領先 'origin/master' 共 2 個提交。 (使用 "git push" 來發布您的本地提交) 無文件要提交,乾淨的工做區 $ git fetch remote: Counting objects: 12, done. remote: Compressing objects: 100% (6/6), done. remote: Total 12 (delta 6), reused 6 (delta 3), pack-reused 0 展開對象中: 100% (12/12), 完成. 來自 github.com:Ryan-Miao/l4git-workflow 24c6818..f36907c master -> origin/master * [新分支] E -> origin/E $ git rebase origin/master 首先,回退分支以便在上面重放您的工做... 應用:d write one 應用:f write two $ git push origin master:F 對象計數中: 6, 完成. Delta compression using up to 4 threads. 壓縮對象中: 100% (4/4), 完成. 寫入對象中: 100% (6/6), 515 bytes | 0 bytes/s, 完成. Total 6 (delta 2), reused 0 (delta 0) remote: Resolving deltas: 100% (2/2), completed with 1 local object. To git@github.com:Ryan-Miao/l4git-workflow.git * [new branch] master -> F 

PR, rebase and merge。 這時候看history:

按照前幾回的作法,E和F交叉在本地提交,每次commit的時間戳也是交叉,最終合併到master的時候,歷史並無被拆散。而是像咱們期待的同樣,順序下來。這纔是咱們想要的。經過看圖形化界面也能看出區別:

綠色的線是master

那麼,操做即是fetch-》rebase。事實上,能夠二合一爲:

git pull --rebase origin master

最終結果

在都沒提交到server的時候, 歷史是分散在各個開發者的本地,但commit時間有前後。

按照rebase的用法,提交前rebase一次,就可使得一個feature的提交串聯到一塊兒

最終在github的commit看起來也就是順暢的多

金科玉律

  1. 想維持樹的整潔,方法就是:在git push以前,先git fetch,再git rebase。
git fetch origin master
git rebase origin/master
git push

或者

git pull --rebase origin master

只要你把變基命令看成是在推送前清理提交使之整潔的工具,而且只在從未推送至共用倉庫的提交上執行變基命令,就不會有事。 假如在那些已經被推送至共用倉庫的提交上執行變基命令,並所以丟棄了一些別人的開發所基於的提交,那你就有大麻煩了,你的同事也會所以鄙視你。

若是你或你的同事在某些情形下決意要這麼作,請必定要通知每一個人執行 git pull --rebase 命令,這樣儘管不能避免傷痛,但能有所緩解。

  1. 絕對不要在公共(遠程分支)上rebase,也就是說,若是沒有必要就不要在github merge的時候選擇rebase,而是用上述的辦法,在本地本身的分支推送前rebase
  2. 絕對不能夠在公共分支上reset,也不要用--force
  3. 單獨功能的屢次提交要學會合並提交,保持提交的簡潔。
  4. 提交message儘可能能歸納修改內容。

參考來源

相關文章
相關標籤/搜索