0. 快速上手與理解
- 如果你的團隊來了一個新隊員,有一台全新的機器,你們是否有一個文檔,只要設置了相應的權限,她就可以根據文檔,從頭開始搭建環境,並成功地把最新、最穩定版本的軟件編譯出來,並運行必要的單元測試?(在這過程中,不需要和老隊員做任何交流)
其實我們非常想說可以,但是最后在仔細考慮后發現確實是不可以的。我們的文檔注重的方面更多地在理解用戶需求與后端的設計上,但是關於配置環境,運行單元測試這方面的文檔,雖然后端工程師在啟程時寫過一篇,如下:
但是后期並沒有對它進行必要的完善與維護,並且沒有將它push到github上,所以我們即使想說是,但是確實是不可以的。我們沒有重視對於環境與運行方面的文檔。
1. 你的團隊的源代碼控制在哪里?用的是什么系統?如何處理文件的鎖定問題?
場景: 程序員果凍正在對幾個文件進行修改,實現一個大的功能, 這時候,程序員小飛也要改其中一個文件,快速修復一個問題。怎么辦?
一個代碼文件被簽出 (check out) 之后,另一個團隊成員可以簽出這個文件,並修改,然后簽入么?
有幾種設計,各有什么優缺點?
例如,簽出文件后,此文件就加鎖,別人無法簽出; 或者, 所有人都可以自由簽出文件
答:我們團隊的源代碼控制在Github上,使用的Git系統與Github本身集成的一些其他功能。
由於Git的特點,在處理文件的合並這方面有着易懂且優秀的合並技巧。
以下是場景分析:
程序員果凍現在正在對幾個文件進行修改,程序員小飛要修改其中一個文件,修復一個大bug。果凍因為還沒有修改完,還沒有push。但是很快,小飛修改完成了,commit,push成功!小飛樂滋滋地想着自己即將到來的獎金,去吃飯了。辦公室就剩下果凍一個人在緊張趕工….也是輕松且愉悅地commit,push。
啊?被拒絕了!
Pushing to git@github.com:buaase/Phylab-Web.git
To git@github.com:buaase/Phylab-Web.git
! [rejected] master -> master (non-fast-forward)
果凍心里很是郁悶,難道就因為我長得沒小飛帥,就要拒絕我的提交嗎?
嘿嘿,其實不是的,其實這是因為果凍在進行push之前,小飛已經推送了內容到遠程數據庫,果凍的push就被拒絕。因為直接push會覆蓋小飛所修改的內容。
所以作為項目經理的我,在跟我們團隊的隊員強調Git的使用時反復強調一點,在所有的push之前一定要進行pull,將服務器端他人推送的內容先合並到本地。
比如我們在項目中演示如下:
關於文件的鎖定這一點上,git本身的合並機制支持了多個人同時開發不同的feature,不論是通過同一個分支,或者是不同的分支。如果在同樣的時間內,一個文件只能被一個人修改的話,雖然是不會產生沖突,但是在某些項目里,這樣類串行化的作業方式效率較低。當然在實際使用git的過程中應該盡量避免沖突,能夠自動merge就自動合並。
2. 如何看到這個文件和之前版本的差異? 如何看到代碼修改和工作項 (work item),缺陷修復 (bug fix) 的關系。
場景: 程序員果凍看到某個文件被修改了,他怎么看到這個文件在最近的修改究竟改了哪些地方?
場景: 程序員果凍看到某個文件在最新版本被改動了100 多行, 那么和這100多行對應的其他修改在什么文件中呢? 這個修改是為了解決哪些問題而作的呢? 那些問題有工作項 (work item,issue),或者bug 來跟蹤么?
答: 在git管理中, 使用git diff即可看到文件和之前版本的差異。
-
git diff:是查看working tree(工作目錄)與index file(暫存區)的差別的。
-
git diff --cached:是查看index file(暫存區)與commit(本地倉庫)的差別的。
-
git diff HEAD:是查看working tree(工作目錄)和commit(本地倉庫)的差別的。
在git里,這三個概念是很重要的,其中 git add的一個功能是將修改從工作目錄添加到index file,commit的工能就是從index file的修改添加到 commit。
比如下面這是一個截圖:(使用git diff --cached)

當我們把a.txt文件提交並作出修改后,可以使用git diff來查看差異:
在Github中,也可以通過可視化的界面來看到每次修改的代碼(包括增加,刪除)文件與修改的具體位置,修改的具體內容。
比如下面這樣的界面:
可以看到每次修改的文件路徑、文件的內容、紅色代表刪除掉的內容,綠色代表添加的內容
修改為了解決的問題要通過commit日志的提交信息來實現,但是很遺憾,在我們的團隊項目中,關閉Issue和commit日志信息都做得不太好。大家的commit日志除了黃雨萌和我的相對規范一些,隊員們很多都用來當打油詩了…
一次commit可以和Issue相關聯,以表明這個Issue是由本次commit修復的,只需要在git commit的時候使用時加入如下commit日志信息即可:
-
fix #xxx
-
fixes #xxx
-
fixed #xxx
-
close #xxx
-
closes #xxx
-
closed #xxx
比如我使用了如下語句:
現在可以在github看到
可以看到最后那里,一次commit和issue的關閉是綁定的。
3. 如果某個文件在你簽出之后已經被別人修改,並且簽入了,那么你在簽入你的修改的時候, 如何合並不同的修改(merge)? 你用了什么工具來幫助你?
答: 我們使用Git來幫助我們完成了這件事。
在一般情況下,git pull后git會自動合並Git修改的部分,自動的Merge。但是,也存在無法自動合並的情況。就像果凍和小飛這樣,遠程數據庫和本地數據庫的同一個地方都發生了修改的情況下,因為Git無法自動判斷要選用哪一個修改,所以就會發生沖突。但是,Git會在發生沖突的地方打個標記!比如這樣式的:
<<<<<<< HEAD
test in Local
=======
test in Remote
>>>>>>> 17c805…(Commit的Hash值)
-
==分割線上方是本地數據庫的內容
-
==分割線下方是遠程數據庫的某次產生沖突的commit所修改的內容。
這時候我們需要通過一雙慧眼來識別哪些都可以保留,哪些保留遠程數據庫的內容,哪些保留本地數據庫的內容。在將文件沖突的內容合並后,刪除掉<<<<< 和=====,>>>>>這樣的東西,重新add,commit,push,即完成了一次手工合並。
不過因為大家都是新手入門,不太會合理地人工合並沖突,所以項目經理本身在分配任務時,遵循了一個原則:盡量不讓兩個人的任務在同一個文件上產生重疊。
每個人修改的文件范圍或者其他都是固定的,一般不會存在兩個人同時修改同樣的文件。當然,前端和后端在修改時大部分時候都會產生沖突,這時候我們就使用了另一套機制來幫我們實現這一點:
新建分支與分支合並。
Beta階段,我們要用到一個論壇的框架與現有的一些邏輯,但是又要同時兼容之前的網站主頁的風格與設計,為了實現這一點,我們的前端負責修改頁面樣式,后端負責對接后台邏輯與數據庫的同步問題。那么問題來了,如果兩個人在同一個分支下進行工作,那么每次都要有合並與修改記錄,並且如果一個人修改的部分因為某些bug不可用的時候,另一部分也無法確認其所修改的部分是否正確。所以我們建立了兩個分支(Back-end和Front-end),Front-end上只有一個wecenter的模版供前端進行樣式的修改,而后端使用默認的wecenter的模版進行數據庫的對接與其他工作,兩份工作由此而開始獨立進行,互不干擾。
我們團隊項目里的Front-end分支和Back-end分支的情況如下:
等到前端做到可行的程度后,前后端修改的部分開始合並,合並兩個分支來將前端修改的部分合並到Back-end分支上,這樣合並的工作只在一天進行,且由於沖突的部分較少,大部分都是自動合並,效率很高。
4. 你有20個文件都是關於同一個功能的修改,你要如何保證這些文件都同時簽入成功(修改的原子性),或者同時簽入不成功?
場景: 程序員果凍要簽入 20 個文件,他一個一個地簽入, 在簽入完5 個 .h 文件之后, 他發現一些 .cpp 文件和最新的版本有沖突,他正在花時間琢磨如何合並... 這時候, 程序員小飛從客戶端同步了所有最新代碼, 開始編譯, 但是編譯不成功 - 因為有不同步的 .h 文件和 .cpp 文件! 這時候, 別的程序員也來抱怨同樣的問題,果凍應該怎么辦?
答:果凍目前面臨的問題恰恰可以由我們第三點所述的branch的好處解決。
Branch的出現,可以讓任何一位開發者基於其他人的代碼或環境都完整可用(即stable版)的環境下進行自己的部分的獨立開發 。
最后的合並工作可以放在一天之內,將所有的Branch上的feature合並到一個dev分支上來。但是這樣面臨的風險也是有的,多個分支同時合並時如果出現了比較大的沖突,合並起來必須小心翼翼。
同時,在解決一個Issue的時候,也可以新建一個Issue分支,比如像如下所示:
在解決了Issue后,可以使用分支合並的技術將兩個或多個分支合並。
5. 你的PC 上有關於三個功能的修改,但是都沒有完成,有很多文件處於半完工的狀態,這時你要緊急修改一個新的 bug,如何把本地修改放一邊,保證在干凈的環境中修改這個 bug, 並成功地簽入你的修改 --- changelist management。
答:在Git里,不能完整地保證commit后整個環境處於可編譯或可運行狀態下的commit是不好的提交。
所以在文件半完工的狀態下,我們不可以使用commit來將文件修改的內容留下來。
Git為我們提供了一種類似於操作系統里的保存現場的指令,那就是stash。 它可以把當前工作現場"儲藏"起來,等以后恢復現場后繼續工作,使用方法類似下面:
$ git stash
Saved working directory and index state WIP on master: 5655bdc Merge branch 'mas
ter' of https://github.com/buaase/Phylab-Web
HEAD is now at 5655bdc Merge branch 'master' of https://github.com/buaase/Phylab
-Web
這時候就會發現,add了以后的東西都被"雪藏"起來,現在的工作區非常干凈,我們這時候可以在一個干凈的環境中修復緊急的bug並提交,簽入,在push后,再使用
git stash apply 或者 git stash pop
來將保存起來的內容取出來繼續開開心心地開發啦。
比如下面我們來實際操作演示一下
可以看到我們對a.txt進行了修改,下面我們使用stash指令來讓它”保存現場“
可以看到實際上 git stash后,我們對於a.txt的修改不見了?!
再使用 git stash pop,再來一觀:
可以看到 git stash pop 后,對a.txt的修改又回來了。
6. 如何給你的源代碼建立分支?
場景:你們需要做一個演示,所以在演示版本的分支中對各處的代碼做了一個臨時的修改,同時,主要的分支還保持原來的計划開發。你們怎么做到的?在演示之后,演示版本的有些修改應該合並到主分支中,有些則不用,你們是怎么做到的?
場景:你們的軟件發布了,有很多用戶,一天,一個用戶報告了一個問題,但是他們是用某個老版本,而且沒有條件更新到最新版本。這時候,你如何在本地構建一個老版本的軟件,並試圖重現那個問題?
給源代碼建立分支的過程我們下面使用一個gif圖來演示一下:
上面這個gif圖演示了我們使用cherry pick的一個完整流程(第一次錄制gif見諒)
主要流程是我們建立了兩個分支 dev1和devtest分支,並使用cherry-pick將dev1上的commit(日志信息為"test")合並到devtest分支上來,並且最后造成devtest分支也有該commit提交。
並且在這過程中通過命令行建立了遠程分支。
當用戶沒有條件更新到新版本的時候,我們將新建一個分支,然后使用git reset hashcode(commit日志的唯一hash碼,可以倒退回過去),然后進行測試以重現問題。
全過程如下所示:
我們可以看到,我們從fixed #189的HEAD 回退到了 b5320c所代表的commit記錄處。
7. 一個源文件,如何知道它的每一行都是什么時候簽入的,為了什么目的簽入的 (解決了哪個任務,或者哪個bug)?
場景:一個重要的軟件忽然出現崩潰的情況, 程序員果凍經過各種debug手段,發現問題是在某一個文件中有一行代碼似乎顯然出了問題,但是這個模塊被很多其他模塊調用,這行代碼是什么時候,為了什么目的,經過誰簽入的呢?如果貿然修改,會不會導致其他問題呢?怎么辦?
答:針對一個源文件的每一行是在什么時候簽入,為了什么簽入,在github里有非常好的支持,如下圖:
在上圖中我們可以看到,Handle10611.tex在兩次提交中被修改了,分別是在65b4fe1和9a9913d兩次commit中被修改過,並且兩次都帶有比較清晰的commit日志,知道它是為何被簽入,修改又是為了什么。以及所有跟它有關的所有commit提交信息。
一個良好的團隊應當維護一個良好的commit日志,並且有所規范,但是我們組在這一點上還非常欠缺...
8. 如何給一個系統的所有源文件都打上標簽,這樣別人可以同步所有有這個標簽的文件版本?
代碼每天都在變,有時質量變好,有時變差,我們需要一個 Last Known Good (最后穩定的好版本) 版本,這樣新員工就可以同步這個版本,我們如果需要發布,也是從這個版本開始。那么如何標記這個 Last Known Good 版本呢?
使用git來打tag這件事,在Github中是可以很方便來做這件事:
每次發布到一定成果后,就需要發布一個realease版本,但是這樣的話,是對commit本身打標簽。在git里,標簽分為兩種類型:輕量標簽和附注標簽。輕量標簽是指向提交對象的引用,附注標簽則是倉庫中的一個獨立對象。
$ git tag v1.0.0
想查看tag的話,可以使用git tag來查看,如下:
如果想回到某個標簽時某個文件的狀態,那么只要使用git checkout tag(標簽名) 即可,如下面這個gif所示:
可以看到我們的項目在發布時一共發布了1.1.0,1.1.1,1.0.0三版。
9. 你的項目的源代碼和測試這些代碼的單元測試,以及其他測試腳本都是放在一起的么? 修改源代碼會確保相應的測試也更新么?你的團隊是否能部署自動構建的任務?
在簽入之前,程序員能否自動在自己的機器上運行自動測試,以保證本地修改不會影響整個軟件的質量?
在程序員提交簽入之后,服務器上是否有自動測試程序,完成編譯,測試,如果成功,就簽入,否則,就取消簽入?
團隊是否配置了服務器,它自動同步所有文件,自動構建,自動運行相關的單元測試,碰到錯誤能自動發郵件給團隊
答:配置了服務器,一開始使用的是Travis-CI來自動集成測試,但是由於網絡因素,Travis-CI登錄很慢,所以最后決意采用了drone.io來進行自動化的單元測試,每次測試都會自動按照預定的腳本運行單元測試,單元測試通過以后可以在Github的ReadMe里體現出來。
是這樣的標志:
點開build passing的示例,我們可以看到在drone.io中我們部署的自動化測試:
每一次commit都會觸發自動化測試,在部署成功后(只針對master分支),已經test了38次。
在Setting中也可以看到,我們在build出錯時,會自動通知我的郵箱(qianlxc@126.com)