0. 快速上手與理解
- 若是你的團隊來了一個新隊員,有一臺全新的機器,大家是否有一個文檔,只要設置了相應的權限,她就能夠根據文檔,從頭開始搭建環境,併成功地把最新、最穩定版本的軟件編譯出來,並運行必要的單元測試?(在這過程當中,不須要和老隊員作任何交流)
其實咱們很是想說能夠,可是最後在仔細考慮後發現確實是不能夠的。咱們的文檔注重的方面更多地在理解用戶需求與後端的設計上,可是關於配置環境,運行單元測試這方面的文檔,雖而後端工程師在啓程時寫過一篇,以下:
![](http://static.javashuo.com/static/loading.gif)
可是後期並無對它進行必要的完善與維護,而且沒有將它push到github上,因此咱們即便想說是,可是確實是不能夠的。咱們沒有重視對於環境與運行方面的文檔。前端
1. 你的團隊的源代碼控制在哪裏?用的是什麼系統?如何處理文件的鎖定問題?
場景: 程序員果凍正在對幾個文件進行修改,實現一個大的功能, 這時候,程序員小飛也要改其中一個文件,快速修復一個問題。怎麼辦?git
一個代碼文件被簽出 (check out) 以後,另外一個團隊成員能夠簽出這個文件,並修改,而後簽入麼?程序員
有幾種設計,各有什麼優缺點?github
例如,簽出文件後,此文件就加鎖,別人沒法簽出; 或者, 全部人均可以自由簽出文件數據庫
答:咱們團隊的源代碼控制在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,將服務器端他人推送的內容先合併到本地。
好比咱們在項目中演示以下:
![](http://static.javashuo.com/static/loading.gif)
關於文件的鎖定這一點上,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)
![](http://static.javashuo.com/static/loading.gif)
當咱們把a.txt文件提交併做出修改後,可使用git diff來查看差別:
![](http://static.javashuo.com/static/loading.gif)
在Github中,也能夠經過可視化的界面來看到每次修改的代碼(包括增長,刪除)文件與修改的具體位置,修改的具體內容。
好比下面這樣的界面:
網絡
能夠看到每次修改的文件路徑、文件的內容、紅色表明刪除掉的內容,綠色表明添加的內容app
修改成瞭解決的問題要經過commit日誌的提交信息來實現,可是很遺憾,在咱們的團隊項目中,關閉Issue和commit日誌信息都作得不太好。你們的commit日誌除了黃雨萌和個人相對規範一些,隊員們不少都用來當打油詩了…
一次commit能夠和Issue相關聯,以代表這個Issue是由本次commit修復的,只須要在git commit的時候使用時加入以下commit日誌信息便可:
-
fix #xxx
-
fixes #xxx
-
fixed #xxx
-
close #xxx
-
closes #xxx
-
closed #xxx
好比我使用了以下語句:框架
![](http://static.javashuo.com/static/loading.gif)
如今能夠在github看到
![](http://static.javashuo.com/static/loading.gif)
能夠看到最後那裏,一次commit和issue的關閉是綁定的。
3. 若是某個文件在你簽出以後已經被別人修改,而且簽入了,那麼你在簽入你的修改的時候, 如何合併不一樣的修改(merge)? 你用了什麼工具來幫助你?
答: 咱們使用Git來幫助咱們完成了這件事。
在通常狀況下,git pull後git會自動合併Git修改的部分,自動的Merge。可是,也存在沒法自動合併的狀況。就像果凍和小飛這樣,遠程數據庫和本地數據庫的同一個地方都發生了修改的狀況下,由於Git沒法自動判斷要選用哪個修改,因此就會發生衝突。可是,Git會在發生衝突的地方打個標記!好比這樣式的:
<<<<<<< HEAD
test in Local
=======
test in Remote
>>>>>>> 17c805…(Commit的Hash值)
這時候咱們須要經過一雙慧眼來識別哪些均可以保留,哪些保留遠程數據庫的內容,哪些保留本地數據庫的內容。在將文件衝突的內容合併後,刪除掉<<<<< 和=====,>>>>>這樣的東西,從新add,commit,push,即完成了一次手工合併。
不過由於你們都是新手入門,不太會合理地人工合併衝突,因此項目經理自己在分配任務時,遵循了一個原則:儘可能不讓兩我的的任務在同一個文件上產生重疊。
每一個人修改的文件範圍或者其餘都是固定的,通常不會存在兩我的同時修改一樣的文件。固然,前端和後端在修改時大部分時候都會產生衝突,這時候咱們就使用了另外一套機制來幫咱們實現這一點:
新建分支與分支合併。
Beta階段,咱們要用到一個論壇的框架與現有的一些邏輯,可是又要同時兼容以前的網站主頁的風格與設計,爲了實現這一點,咱們的前端負責修改頁面樣式,後端負責對接後臺邏輯與數據庫的同步問題。那麼問題來了,若是兩我的在同一個分支下進行工做,那麼每次都要有合併與修改記錄,而且若是一我的修改的部分由於某些bug不可用的時候,另外一部分也沒法確認其所修改的部分是否正確。因此咱們創建了兩個分支(Back-end和Front-end),Front-end上只有一個wecenter的模版供前端進行樣式的修改,然後端使用默認的wecenter的模版進行數據庫的對接與其餘工做,兩份工做由此而開始獨立進行,互不干擾。
咱們團隊項目裏的Front-end分支和Back-end分支的狀況以下:
![](http://static.javashuo.com/static/loading.gif)
等到前端作到可行的程度後,先後端修改的部分開始合併,合併兩個分支來將前端修改的部分合併到Back-end分支上,這樣合併的工做只在一天進行,且因爲衝突的部分較少,大部分都是自動合併,效率很高。
4. 你有20個文件都是關於同一個功能的修改,你要如何保證這些文件都同時簽入成功(修改的原子性),或者同時簽入不成功?
場景: 程序員果凍要簽入 20 個文件,他一個一個地簽入, 在簽入完5 個 .h 文件以後, 他發現一些 .cpp 文件和最新的版本有衝突,他正在花時間琢磨如何合併... 這時候, 程序員小飛從客戶端同步了全部最新代碼, 開始編譯, 可是編譯不成功 - 由於有不一樣步的 .h 文件和 .cpp 文件! 這時候, 別的程序員也來抱怨一樣的問題,果凍應該怎麼辦?
答:果凍目前面臨的問題偏偏能夠由咱們第三點所述的branch的好處解決。
Branch的出現,可讓任何一位開發者基於其餘人的代碼或環境都完整可用(即stable版)的環境下進行本身的部分的獨立開發 。
最後的合併工做能夠放在一天以內,將全部的Branch上的feature合併到一個dev分支上來。可是這樣面臨的風險也是有的,多個分支同時合併時若是出現了比較大的衝突,合併起來必須當心翼翼。
同時,在解決一個Issue的時候,也能夠新建一個Issue分支,好比像以下所示:
![](http://static.javashuo.com/static/loading.gif)
在解決了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
來將保存起來的內容取出來繼續開開心心地開發啦。
好比下面咱們來實際操做演示一下
![](http://static.javashuo.com/static/loading.gif)
能夠看到咱們對a.txt進行了修改,下面咱們使用stash指令來讓它」保存現場「
![](http://static.javashuo.com/static/loading.gif)
能夠看到實際上 git stash後,咱們對於a.txt的修改不見了?!
再使用 git stash pop,再來一觀:
![](http://static.javashuo.com/static/loading.gif)
能夠看到 git stash pop 後,對a.txt的修改又回來了。
6. 如何給你的源代碼創建分支?
場景:大家須要作一個演示,因此在演示版本的分支中對各處的代碼作了一個臨時的修改,同時,主要的分支還保持原來的計劃開發。大家怎麼作到的?在演示以後,演示版本的有些修改應該合併到主分支中,有些則不用,大家是怎麼作到的?
場景:大家的軟件發佈了,有不少用戶,一天,一個用戶報告了一個問題,可是他們是用某個老版本,並且沒有條件更新到最新版本。這時候,你如何在本地構建一個老版本的軟件,並試圖重現那個問題?
給源代碼創建分支的過程咱們下面使用一個gif圖來演示一下:
![](http://static.javashuo.com/static/loading.gif)
上面這個gif圖演示了咱們使用cherry pick的一個完整流程(第一次錄製gif見諒)
主要流程是咱們創建了兩個分支 dev1和devtest分支,並使用cherry-pick將dev1上的commit(日誌信息爲"test")合併到devtest分支上來,而且最後形成devtest分支也有該commit提交。
而且在這過程當中經過命令行創建了遠程分支。
當用戶沒有條件更新到新版本的時候,咱們將新建一個分支,而後使用git reset hashcode(commit日誌的惟一hash碼,能夠倒退回過去),而後進行測試以重現問題。
全過程以下所示:
![](http://static.javashuo.com/static/loading.gif)
咱們能夠看到,咱們從fixed #189的HEAD 回退到了 b5320c所表明的commit記錄處。
7. 一個源文件,如何知道它的每一行都是何時簽入的,爲了什麼目的簽入的 (解決了哪一個任務,或者哪一個bug)?
場景:一個重要的軟件突然出現崩潰的狀況, 程序員果凍通過各類debug手段,發現問題是在某一個文件中有一行代碼彷佛顯然出了問題,可是這個模塊被不少其餘模塊調用,這行代碼是何時,爲了什麼目的,通過誰簽入的呢?若是貿然修改,會不會致使其餘問題呢?怎麼辦?
答:針對一個源文件的每一行是在何時簽入,爲了什麼簽入,在github裏有很是好的支持,以下圖:
![](http://static.javashuo.com/static/loading.gif)
在上圖中咱們能夠看到,Handle10611.tex在兩次提交中被修改了,分別是在65b4fe1和9a9913d兩次commit中被修改過,而且兩次都帶有比較清晰的commit日誌,知道它是爲什麼被簽入,修改又是爲了什麼。以及全部跟它有關的全部commit提交信息。
一個良好的團隊應當維護一個良好的commit日誌,而且有所規範,可是咱們組在這一點上還很是欠缺...
8. 如何給一個系統的全部源文件都打上標籤,這樣別人能夠同步全部有這個標籤的文件版本?
代碼天天都在變,有時質量變好,有時變差,咱們須要一個 Last Known Good (最後穩定的好版本) 版本,這樣新員工就能夠同步這個版本,咱們若是須要發佈,也是從這個版本開始。那麼如何標記這個 Last Known Good 版本呢?
使用git來打tag這件事,在Github中是能夠很方便來作這件事:
![](http://static.javashuo.com/static/loading.gif)
每次發佈到必定成果後,就須要發佈一個realease版本,可是這樣的話,是對commit自己打標籤。在git裏,標籤分爲兩種類型:輕量標籤和附註標籤。輕量標籤是指向提交對象的引用,附註標籤則是倉庫中的一個獨立對象。
$ git tag v1.0.0
想查看tag的話,可使用git tag來查看,以下:
![](http://static.javashuo.com/static/loading.gif)
若是想回到某個標籤時某個文件的狀態,那麼只要使用git checkout tag(標籤名) 便可,以下面這個gif所示:
![](http://static.javashuo.com/static/loading.gif)
能夠看到咱們的項目在發佈時一共發佈了1.1.0,1.1.1,1.0.0三版。
9. 你的項目的源代碼和測試這些代碼的單元測試,以及其餘測試腳本都是放在一塊兒的麼? 修改源代碼會確保相應的測試也更新麼?你的團隊是否能部署自動構建的任務?
在簽入以前,程序員可否自動在本身的機器上運行自動測試,以保證本地修改不會影響整個軟件的質量?
在程序員提交簽入以後,服務器上是否有自動測試程序,完成編譯,測試,若是成功,就簽入,不然,就取消簽入?
團隊是否配置了服務器,它自動同步全部文件,自動構建,自動運行相關的單元測試,碰到錯誤能自動發郵件給團隊
答:配置了服務器,一開始使用的是Travis-CI來自動集成測試,可是因爲網絡因素,Travis-CI登陸很慢,因此最後決意採用了drone.io來進行自動化的單元測試,每次測試都會自動按照預約的腳本運行單元測試,單元測試經過之後能夠在Github的ReadMe裏體現出來。
是這樣的標誌:
![](http://static.javashuo.com/static/loading.gif)
點開build passing的示例,咱們能夠看到在drone.io中咱們部署的自動化測試:
![](http://static.javashuo.com/static/loading.gif)
每一次commit都會觸發自動化測試,在部署成功後(只針對master分支),已經test了38次。
![](http://static.javashuo.com/static/loading.gif)
在Setting中也能夠看到,咱們在build出錯時,會自動通知個人郵箱(qianlxc@126.com)
![](http://static.javashuo.com/static/loading.gif)