理解Git的工作流程

openkk 12年前發布 | 53K 次閱讀 Git 版本控制系統

        如果你不理解 Git 的設計動機,那你就會處處碰壁。知道足夠多的命令和參數后,你就會強行讓 Git 按你想的來工作,而不是按 Git 自己的方式來。這就像把螺絲刀當錘子用;也能把活干完,但肯定干的差極了,花費很長時間,還會弄壞螺絲刀。

        想想常見的 Git 工作流程是怎么失效的吧。

從 Master 創建一個分支,寫代碼,然后把這個分支合并回 Master。

        多數時候這樣做的效果會如你所愿,因為從你創建分支到合并回去之間,Master 一般都會有些變動。然后,有一天當你想把一個功能(feature)分支合并進 Master 的時候,而 Master 并沒有像以往那樣有變動,問題來了:這時 Git 不會進行合并 commit,而是將 Master 指向功能分支上的最新 commit。(看圖

        不幸的是,你的功能分支有用來備份代碼的 commit(作者稱之為 checkpoint commit),這些經常進行的 commit 對應的代碼可能處于不穩定狀態!而這些 commit 現在沒法和 Master 上那些穩定的 commit 區分開來了。當你想回滾的時候,很容易發生災難性后果。

        于是你就記住了:“當合并功能分支的時候,加上 -no-ff 選項強制進行一次全新的 commit。”嗯,這么做好像解決問題了,那么繼續。

        然后一天你在線上環境中發現了一個嚴重 bug,這時你需要追溯下這個 bug 是什么時候引入的。你運行了 bisect 命令,但卻總是追溯到一些不穩定的 commit。因此你不得不放棄,改用人肉檢查。

        最后你將 bug 范圍縮小到一個文件。你運行 blame 命令查看這個文件在過去 48 小時里的變動。然后 blame 告訴你這個文件已經好幾周沒有被修改過了——你知道根本不可能沒有變動。哦,原來是因為 blame 計算變動是從第一次 commit 算起,而不是 merge 的時候。你在幾周前的一次 commit 中改動了這個文件,但這個變動今天才被 merge 回來。

        用 no-ff 來救急,bisect 又臨時失效,blame 的運作機制又那么模糊,所有這些現象都說明一件事兒,那就是你正在把螺絲刀當錘子用。

理解Git的工作流程

        反思版本控制

        版本控制的存在是因為兩個原因。

        首先,版本控制是用來輔助寫代碼的。因為你要和同事同步代碼,并經常備份自己的代碼。當然了,把文件壓縮后發郵件也行,不過工程大了大概就不好辦了。

        其次,就是輔助配置管理工作。其中就包括并行開發的管理,比如一邊給線上版本修復 bug,一邊開發下一個版本。配置管理也可以幫助弄清楚變動發生的具體時間,在追溯 bug 中是一個很好的工具。

        一般說來,這兩個原因是沖突的。

        在開發一個功能的時候,你應該經常做備份性的 commit。然而,這些 commit 經常會讓軟件沒法編譯。

        理想情況是,你的版本更新歷史中的每一次變化都是明確且穩定的,不會有備份性 commit 帶來的噪聲,也不會有超過一萬行代碼變動的超大 commit。一個清晰的版本歷史讓回滾和選擇性 merge 都變得相當容易,而且也方便以后的檢查和分析。然而,要維護這樣一個干凈的歷史版本庫,也許意味著總是要等到代碼完善之后才能提交變動。

        那么,經常性的 commit 和干凈的歷史,你選擇哪一個?

        如果你是在剛起步的創業公司中,干凈的歷史沒有太大幫助。你可以放心地把所有東西都往 Master 中提交,感覺不錯的時候隨時發布。

        如果團隊規模變大或是用戶規模擴大了,你就需要些工具和技巧來做約束,包括自動化測試,代碼檢查,以及干凈的版本歷史。

        功能分支貌似是一個不錯的折中選擇,能夠基本的并行開發問題。當你寫代碼時候,可以不用怎么在意集成的問題,但它總有煩到你的時候。

        當你的項目規模足夠大的時候,簡單的 branch/commit/merge 工作流程就出問題了。縫縫補補已經不行了。這時你需要一個干凈的版本歷史庫。

        Git 之所以是革命性的,就是因為它能同時給你這兩方面的好處。你可以在原型開發過程中經常備份變動,而搞定后只需要交付一個干凈的版本歷史。

        工作流程

        考慮兩種分支:公共的和私有的。

        公共分支是項目的權威性歷史庫。在公共分支中,每一個 commit 都應該確保簡潔、原子性,并且有完善的提交信息。此分支應該盡可能線性,且不能更改。公共分支包括 Master 和發行版的分支。

        私有分支是供你自己使用的,就像解決問題時的草稿紙。

        安全起見,把私有分支只保存在本地。如果你確實需要 push 到服務器的話(比如要同步你在家和辦公室的電腦),最好告訴同事這是私有的,不要基于這個分支展開工作。

        絕不要直接用 merge 命令把私有分支合并到公共分支中。要先用 reset、rebase、squash merges、commit amending 等工具把你的分支清理一下。

        把你自己看做一個作者,每一次的 commit 視為書中的一章。作者不會出版最初的草稿,就像 Michael Crichton 說的,“偉大的書都不是寫出來——而是改出來的”。

        如果你沒接觸過 Git,那么修改歷史對你來說好像是種禁忌。你習慣于認為提交過的所有東西都應該像刻在石頭上一樣不能抹去。但如果按這種邏輯,我們在文本處理軟件器中也不應該使用“撤銷”功能了。

        實用主義者們直到變化變為噪音的時候才關注變化。對于配置管理來說,我們關注宏觀的變化。日常 commit(checkpoint commits)只是備份于云端的用于“撤銷”的緩沖。

        如果你保持公共歷史版本庫的簡潔,那么所謂的 fast-forward merge 就不僅安全而且可取了,它能保證版本變更歷史的線性和易于追溯。

        關于 -no-ff 僅剩的爭論就只剩“文檔證明”了。人們可能會先 merge 再 commit,以此代表最新的線上部署版本。不過,這是反模式的。用 tag 吧。

        規則和例子

        根據改變的多少、持續工作時間的長短,以及分支分叉了多遠,我使用三種基本的方法。

        1)短期工作

        絕大多數時間,我做清理時只用 squash merge 命令。

        假設我創建了一個功能分支,并且在接下來一個小時里進行了一系列的 checkpoint commit。

git checkout -b private_feature_branchtouch file1.txtgit add file1.txtgit commit -am "WIP" 

        完成開發后,我不是直接執行 git merge 命令,而是這樣:

git checkout mastergit merge --squash private_feature_branchgit commit -v

        然后我會花一分鐘時間寫個詳細的 commit 日志。

        2)較大的工作

        有時候一個功能可以延續好幾天,伴有大量的小的 commit。

        我認為這些改變應該被分解為一些更小粒度的變更,所以 squash 作為工具來說就有點兒太糙了。(根據經驗我一般會問,“這樣能讓閱讀代碼更容易嗎?”)

        如果我的 checkpoint commits 之后有合理的更新,我可以使用 rebase 的交互模式。

        交互模式很強大。你可以用它來編輯、分解、重新排序、合并以前的 commit。

        在我的功能分支上:

git rebase --interactive master

        然后會打開一個編輯器,里邊是 commit 列表。每一行上依次是,要執行的操作、commit 的 SHA1 值、當前 commit 的注釋。并且提供了包含所有可用命令列表的圖例。

        默認情況下,每個 commit 的操作都是“pick”,即不會修改 commit。

pick ccd6e62 Work on back buttonpick 1c83feb Bug fixespick f9d0c33 Start work on toolbar 

保存并退出,會彈出一個新的編輯器窗口,讓我為本次合并 commit 做注釋。就這樣。

git checkout mastergit merge --squash private_feature_branchgit commit -v

        舍棄分支

        也許我的功能分支已經存在了很久很久,我不得不將好幾個分支合并進這個功能分支中,以便當我寫代碼時這個分支是足夠新的的。版本歷史讓人費解。最簡單的辦法是創建一個新的分支。

git checkout mastergit checkout -b cleaned_up_branchgit merge --squash private_feature_branchgit reset

        現在,我就有了一個包含我所有修改且不含之前分支歷史的工作目錄。這樣我就可以手動添加和 commit 我的變更了。

        總結

        如果你在與 Git 的默認設置背道而馳,先問問為什么。

        將公共分支歷史看做不可變的、原子性的、容易追溯的。將私有分支歷史看做一次性的、可編輯的。

        推薦的工作流程是:

        基于公共分支創建一個私有分支。

        經常向這個私有分支 commit 代碼。

        一旦你的代碼完善了,就清理掉下私有分支的歷史。

        將干凈的私有分支 merge 到公共分支中。

        英文原文:Understanding the Git workflow 編譯: 張重騏

 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!