Git 工作流的正确打开方式
前言
一直在使用git做版本控制,也一直工作很順利,直到和別人發(fā)生沖突的時候。這才注意到git 工作流并不是那么簡單。比如,之前遇到的清理歷史。百度到的資料很多,重復(fù)性也很多,但實踐性操作很少,我很難直接理解其所表達的含義。直接望文生義經(jīng)常得到錯誤的結(jié)論,只能用時間去檢驗真理了,不然看到的結(jié)果都是似懂非懂,最后還是一團糟。
學(xué)習(xí)git工作流
1. 最簡單的使用,不推薦
1.1.創(chuàng)建倉庫
$ 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] init3 files changed, 12 insertions(+)create mode 100644 .gitignorecreate mode 100644 readme.mdcreate mode 100644 test.txt $ git remote add origin git@github.com:Ryan-Miao/l4git-workflow.git $ git push -u origin master 對象計數(shù)中: 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 設(shè)置為跟蹤來自 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] one2 files changed, 34 insertions(+), 2 deletions(-)create mode 100644 a.txt此時,a還沒有提交到origin。 git log 如下:
1.3. 模擬用戶B
git clone git.com:Ryan-Miao/l4git-workflow.git git checkout b $ touch b.txt//write something
//...
//write something
//....
$ git add . $ git commit -m "b write two" [b 3f30f41] b write two 1 file changed, 2 insertions(+), 1 deletion(-)
此時,git log如下
1.4. 模擬用戶A
A和B分別是在本地開發(fā),所以這種順序是未知的,也許A比B先commit一次,也許B先commit一次。這里的先后是指commit的時間戳。但都是在本地提交的代碼。
write something
wirte something
git add . git commit -m "write three"A push to server branch?a
$ git push origin a:a Total 0 (delta 0), reused 0 (delta 0) To git.com:Ryan-Miao/l4git-workflow.git* [new branch] a -> aA 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...
現(xiàn)在看起來,只有a一個人的歷史記錄,還算清楚,a做了3次提交。
1.6. 模擬用戶B
用戶B提交前先pull master,更新最新的代碼到本地,防止沖突。
git fetchgit merge origin/master此時log看起來有點亂。如下:
讓人感到混亂的是b原來的歷史只有自己的提交,更新了master到本地之后,歷史記錄被插入了master中的歷史。于是,發(fā)現(xiàn)原來自己干凈的歷史被中間插入多次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。
此時,項目基本走上正規(guī)。feature一個一個添加進去,重復(fù)之前的工作流程: fetch -》 work -》 commit -》 push -》 PR -》 merged。
然后,項目歷史就變成了這樣:
一眼大概看起來還好,每次都能看到提交歷史,只要不是message寫的特別少,差不多可以理解最近提交的內(nèi)容。然而,仔細一看,順序好像不對。目前一共兩個feature,但歷史卻遠遠超過2個。沒關(guān)系,保證細粒度更容易體現(xiàn)開發(fā)進度。然而,這些歷史并不是按照feature的發(fā)布順序,那么,當我想要找到feature a的時候就很難串聯(lián)起來。如果commit足夠多,時間跨度足夠大,甚至根本看不出來feature a到底做了哪些修改。
這時候想要使用圖形化git 歷史工具來幫助理解歷史:
這里,還好,還勉強能看出走向。但當10個上百個人同時開發(fā)的話,線簡直不能看了,時間跨度足夠大的話,線也看不完。
因此,這種模式,正是我們自己當前采用的模式。差評。這還不算完,后面更大的困難來了。最先發(fā)布的feature a出了問題,必須回滾。怎么做到。關(guān)于回滾,就是另一個話題了。 但我們應(yīng)該知道使用revert而不是reset. 但revert只能回滾指定的commit,或者連續(xù)的commit,而且revert不能revert merge操作。這樣,想回滾feature a, 我們就要找到a的幾次提交的版本號,然后由于不是連續(xù)的,分別revert。這會造成復(fù)雜到不想處理了。好在github給了方便的東西,PR提供了revert的機會。找到以前的PR。
但是,這絕對不是個好操作!
2. 推薦的工作流程
造成上述現(xiàn)象的原因是因為各自異步編程決定的。因為每個人都可以隨時間提交,最后合并起來的時候以提交時間戳來作為序列的依據(jù),就會變成這樣。因此,當需要提交的遠程服務(wù)器的時候,如果能重寫下commit的時間為當前時間,然后push到服務(wù)端,歷史就會序列到最后了。
2.1 模擬用戶C
C用戶新下載代碼。
$ git clone git.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 one1 file changed, 2 insertions(+)create mode 100644 c.txt2.2 模擬用戶D
同時,D也需要開發(fā)新feature
$ git clone git.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 one1 file changed, 1 insertion(+)create mode 100644 d.txt2.3 C繼續(xù)開發(fā)
$ vim c.txt $ git add . $ git commit -m "c write two" [master 01b1210] c write two1 file changed, 1 insertion(+)2.4 D繼續(xù)開發(fā)
/d$ vim d.txt /d$ git add . /d$ git commit -m "d write two" [master a1371e4] d write two1 file changed, 1 insertion(+)2.5 C 提交
$ vim c.txt $ git add . $ git commit -m "c write three" [master 13b7dde] c write three1 file changed, 1 insertion(+)C開發(fā)結(jié)束,提交到遠程
$ git status 位于分支 master 您的分支領(lǐng)先 'origin/master' 共 3 個提交。(使用 "git push" 來發(fā)布您的本地提交) 無文件要提交,干凈的工作區(qū) $ git push origin master:C 對象計數(shù)中: 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.com:Ryan-Miao/l4git-workflow.git* [new branch] master -> C2.6 C 提PR
然后,create a Pull Request.
2.7 C修改再push
然后,發(fā)現(xiàn)還有個bug要修復(fù),再次修改提交到遠程C
$ vim c.txt $ git add . $ git commit -m "C finish something else" [master 2c5ff94] C finish something else1 file changed, 1 insertion(+), 1 deletion(-) $ git push origin master:C 對象計數(shù)中: 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.git13b7dde..2c5ff94 master -> C2.8 C發(fā)現(xiàn)提交次數(shù)過多,歷史太亂,合并部分歷史
這時,發(fā)現(xiàn)一個問題,由于C在開發(fā)過程中提交了多次,而這幾次提交的message其實沒有多大意思,只是因為C可能為了保存代碼,也可能是暫存。總之,C的前3次提交的message的含義其實是一樣的,都是創(chuàng)建C文件,都是一個主題,那么為了維護歷史的干凈。最好把這3條信息合并成一條C create file c.txt。
參考git 合并歷史,我們需要將3次歷史合并成顯示為一次。
查看git歷史,找到需要合并的起始區(qū)間
$ 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顯然,是要合并cf3f757到13b7dde。那么找到前一個的版本號為7151f4c
git rebase - i 7151f4c然后進入交互界面,因為我們想要把第3次和第2次以及第1次提交信息合并。將第3次的類型修改為squash, 意思是和第2次合并。然后將第2次的類型修改為squash, 同樣是指合并的前一個commit。
不同git的交互略有不同,之前在windows上的git bash是完全按照vim的命令修改的。本次測試基于Ubuntu,發(fā)現(xiàn)存檔命令為ctel + X。確認后進入下一個界面,合并3次提交后需要一個message
刪除或者anyway you like, 更改message。存檔。完成。
$ git rebase -i 7151f4c [分離頭指針 e3764c5] c create file c.txtDate: Fri Oct 20 22:06:24 2017 +08001 file changed, 4 insertions(+)create mode 100644 c.txt Successfully rebased and updated refs/heads/master.Tips
當在rebase過程中出現(xiàn)了失誤,可以使用git rebase --abort返回初始狀態(tài)。如果發(fā)現(xiàn)沖突,則可以解決沖突,然后git rebase --continue?.
好像已有 rebase-merge 目錄,我懷疑您正處于另外一個變基操作
過程中。 如果是這樣,請執(zhí)行
git rebase (--continue | --abort | --skip)
如果不是這樣,請執(zhí)行
rm -fr "/home/ryan/temp/c/.git/rebase-merge"
然后再重新執(zhí)行變基操作。 為避免丟失重要數(shù)據(jù),我已經(jīng)停止當前操作。
此時,查看log, 顯然,C的那三次提交已經(jīng)合并了。
$ 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 init2.9 C再次push
之前的push已經(jīng)不能用了。需要開新分支推送過去。因為?rebase 只能在本地分支做。不要修改公共分支?。
$ git push origin master:C To git.com:Ryan-Miao/l4git-workflow.git! [rejected] master -> C (non-fast-forward) error: 無法推送一些引用到 'git@github.com:Ryan-Miao/l4git-workflow.git' 提示:更新被拒絕,因為推送的一個分支的最新提交落后于其對應(yīng)的遠程分支。 提示:檢出該分支并整合遠程變更(如 'git pull ...'),然后再推送。詳見 提示:'git push --help' 中的 'Note about fast-forwards' 小節(jié)。選擇推送的新分支C2
$ git push origin master:C2 對象計數(shù)中: 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.com:Ryan-Miao/l4git-workflow.git* [new branch] master -> C2創(chuàng)建新的PR
2.10 新的merge方式: rebase
通過開始的普通流程發(fā)現(xiàn),每次merge的時候,都會多出一條新的提交信息,這讓歷史看起來很奇怪。那么,可以選擇rebase到master,變基,就是重新以master為基本,把當前的提交直接移動到master的后面。不會因為提交時間的離散導(dǎo)致多次commit的message被拆散。 選擇?rebase and merge
這時候,可以看到C提交的兩次信息都是最新的,沒有發(fā)生交叉。而且也沒有產(chǎn)生多余的merge信息。
有人會問,那么豈不是看不到PR的地址了。點開C的歷史。可以看到message下方是有PR的編號的:
對了,剛開始的PR要記得close
2.11 這時候D也完成了
/d$ git push origin master:D 對象計數(shù)中: 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.com:Ryan-Miao/l4git-workflow.git* [new branch] master -> D提PR, 這時候,如果采用merge:
結(jié)果必然發(fā)現(xiàn),1) d提交message被按照時間分散插入歷史了(被插入到c的歷史之前), 2)多了一次?Merge pull request #5 from Ryan-Miao/D..的提交信息。同開頭所述一樣,歷史開始變得混亂了。那么,這種問題怎么辦呢?
2.12 提交前rebase
就像C rebase后merge到master一樣。我們一樣可以在本地做到這樣的事情。在本地rebase,讓我們本次feature的提交全部插到master節(jié)點之后,有序,而且容易revert。
本次,以新的E和F交叉commit為例子,最終將得到各自分開的歷史
E:
$ git clone git.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 one1 file changed, 1 insertion(+)create mode 100644 e.txtF:
$ git clone git.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 one1 file changed, 2 insertions(+)create mode 100644 f.txtE:
/e$ vim e.txt /e$ git add . /e$ git commit -m "e write two" [master 2b8c9fb] e write two1 file changed, 1 insertion(+)F:
$ vim f.txt $ git add . $ git commit -m "f write two" [master de9051b] f write two1 file changed, 1 insertion(+)E:
/e$ vim e.txt /e$ git add . /e$ git commit -m "e write three" [master b1b9f6e] e write three1 file changed, 2 insertions(+)這時候,e完成了,需要提交。提交前先rebase:
/e$ git fetch /e$ git rebase origin/master 當前分支 master 是最新的。然后,再提交
/e$ git push origin master:E 對象計數(shù)中: 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.com:Ryan-Miao/l4git-workflow.git* [new branch] master -> E然后, PR, merge.
同樣F:
$ git status 位于分支 master 您的分支領(lǐng)先 'origin/master' 共 2 個提交。(使用 "git push" 來發(fā)布您的本地提交) 無文件要提交,干凈的工作區(qū) $ 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-workflow24c6818..f36907c master -> origin/master* [新分支] E -> origin/E $ git rebase origin/master 首先,回退分支以便在上面重放您的工作... 應(yīng)用:d write one 應(yīng)用:f write two$ git push origin master:F 對象計數(shù)中: 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.com:Ryan-Miao/l4git-workflow.git* [new branch] master -> FPR, rebase and merge。 這時候看history:
按照前幾次的做法,E和F交叉在本地提交,每次commit的時間戳也是交叉,最終合并到master的時候,歷史并沒有被拆散。而是像我們期待的一樣,順序下來。這才是我們想要的。通過看圖形化界面也能看出區(qū)別:
綠色的線是master
那么,操作便是fetch-》rebase。事實上,可以二合一為:
git pull --rebase origin master最終結(jié)果
在都沒提交到server的時候, 歷史是分散在各個開發(fā)者的本地,但commit時間有先后。
按照rebase的用法,提交前rebase一次,就可以使得一個feature的提交串聯(lián)到一起
最終在github的commit看起來也就是順暢的多
金科玉律
或者
git pull --rebase origin master只要你把變基命令當作是在推送前清理提交使之整潔的工具,并且只在從未推送至共用倉庫的提交上執(zhí)行變基命令,就不會有事。 假如在那些已經(jīng)被推送至共用倉庫的提交上執(zhí)行變基命令,并因此丟棄了一些別人的開發(fā)所基于的提交,那你就有大麻煩了,你的同事也會因此鄙視你。
如果你或你的同事在某些情形下決意要這么做,請一定要通知每個人執(zhí)行 git pull --rebase 命令,這樣盡管不能避免傷痛,但能有所緩解。
本文轉(zhuǎn)自Ryan.Miao博客園博客,原文鏈接:http://www.cnblogs.com/woshimrf/p/git-workflow.html,如需轉(zhuǎn)載請自行聯(lián)系原作者
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的Git 工作流的正确打开方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于微软ASP.NET AJAX框架开发
- 下一篇: java删除文件夹