標籤:

gg———good use of git,一個實用的git分支模型

寫在前面

在我建議你使用git前,首先要給你潑一盆冷水,見博文我痛恨git的10個理由,以下為文中的片段:

Git 是一個源代碼版本控制系統,正在迅速成為開源項目的標準。它有一個強大的分散式模型,允許高級用戶用分支來處理各種棘手的問題和改寫歷史記錄。但是,要學習 Git 是需要付出更多的努力,讓人不爽的命令行介面以及 Git 是如此的忽視它的使用者。

如果你是一個架構師,那麼 Git 是很棒的。但對用戶來說它很糟糕,已經有不少人在為 Git 編寫一些簡化的介面,例如gitflow。

如果 Git 的強大之處是分支和合併,那麼它的弱點就是讓簡單的任務變得非常複雜。

git是如此的強大,強大到你都不知道該怎麼使用它才好了…

本文提出了一個「git該怎麼在科研實踐中輕鬆的使用」的一個模型,包含眾多的規範來約束你git的使用方法。所謂沒有規矩不成方圓,遵循了模型中的約束條件後,你的git倉庫會變得清晰、優雅起來;你會享受你那結構清晰的network圖(而不是之前那亂糟糟的圖形),而你的合作者也能從你的提交序列中清楚的認識到你的工作思路。

當然,每次使用git的時候都要小心翼翼的遵循這些規範是一件困難的事情。所以我開發了一套腳本, gg(good use of git),來傻瓜式的實現下述的模型。

=============模型開始的分割線===============

模型思想基於A successful Git branching model,結合本人的學習工作實際和腦洞,升級為此模型。 聲明:本文只討論模型,並不討論模型的具體實現過程,技術密集文章恐懼患者請放心食用。

引言:版本控制

什麼是版本控制?

(手動的)版本控制

我們在完成一件工作的時候,一定不會是「量子化」的完成,總會經歷一些中間階段。 如果我們將這行中間過程分別保存下來,並且可以形成索引,得以方便的重現,就是版本控制。 版本控制有助於我們掌握自己工作的進度和結構,而且給我們提供了一個安全的試錯空間。 我們可以放心大膽的進行實驗,最後可以接受實驗結果並把實驗過程併入自己的工作中,或者放棄試驗結果,從實驗前的狀態重新開始。

常見的版本控制程序有git,svn等,本文以git的分支特性為基礎,介紹一個實用的git分支模型,歡迎大家使用和討論。(為縮小學習成本,文中只保留版本控制的最核心概念,已經學習過git的不要在意一些不嚴謹的細節。)

  1. 狀態:現在工作所使用的所有文件的集合被版本控制程序記錄了下來稱為一個狀態。(我的活兒干到這了,保存一下,來張「快照」)
  2. 提交:從一個狀態,經過一些工作,到達了另外一個狀態,稱為一次提交。(又幹了點活,再保存一下,再來張「快照」)
  3. 分支名:多個提交形成一個提交鏈,給鏈最末端的那個狀態起個名字,叫分支名。(如果還沒有提交過,則鏈末端就是第一個狀態)
  4. 分支:所有曾經被叫過某個分支名的狀態的組成的狀態提交鏈稱為一個分支。
  5. 創建一個新分支:給當前分支末端的狀態再起一個名字。
  6. 切換到某分支:把現在的狀態變為現在被稱為某分支名的那個狀態。
  7. (A分支與B分支)合併:把A分支名所對應的狀態和B分支名所對應的狀態中的文件整合在一起,作為一個新的狀態。比如A分支狀態有a,b,c三個文件,B分支狀態有b,c,d三個文件(假設倆個b、c文件都相同),則合併後的狀態有a,b,c,d 4個文件。
  8. 合併衝突:分支合併的過程中出現了不一致的情況,比如A狀態中a文件某一行有」C=1″而B狀態中a文件在同一行有」C=2″,這個時候需要手動修改a文件指定C到底等於幾。
  9. 父分支:在A分支上創建了B分支,A分支稱為B分支的父分支。
  10. 刪除一個分支:僅僅將分支末尾的分支名取消掉,但保留已經提交的狀態鏈。

術語的可視化理解見下圖。

狀態2在A分支上,是現在的狀態

在(分支A上)提交一次

回到最早的狀態,這一次在分支A上創建了分支B

切換到分支B,然後提交一次

切換到分支A

在分支A上提交一次

在分支A上把分支B合併進來

刪除分支B

用途

此模型主要用於實現開發類型的任務

舉例:進行新程序的開發/論文的寫作/小組合作完成一件工作等

特點:在進行這類工作時我們主要的行為有

  1. 為工作不斷增加新特性(新的程序功能/論文新的章節/工作新的任務)
  2. 當工作的狀態相對穩定的時候,發布出一個階段性成果(程序的一個發布版本/論文的n稿完成/工作的一個階段完成)
  3. 發現並修改工作中的錯誤,進一步完善工作(程序bug的發現與debug/論文的修改/工作的完善)
  4. 經常需要進行大膽的實驗,需要方便和安全的試錯環境(debug中的嘗試/論文中的新想法/工作中完成任務的新方法)

下圖:開發型工作的開發流程示例

分支類型

假設:存在以下幾種類型的分支

  1. master類型分支,名為?|master或master,其中?為開發代號
  2. develop類型分支,名為?|develop或develop,其中?為開發代號
  3. feature類型分支,名為feature/*或?|feature/*或feature/*-FromBranch-?,其中*為特徵描述,?為創建其的父分支
  4. release類型分支,名為release-*或?|release-*,其中*為要發布的版本號
  5. hotfix類型分支,名為hotfix-*或?|hotfix-*,其中*為要發布的版本號
  6. issues類型分支,名為issues/*或?|issues/*或issues/*-FromBranch-?,其中*為問題描述,?為創建其的父分支
  7. trials類型分支,名為?%trials.*,?為此分支的父分支,*為描述的名稱(或直接為?%trials)

模型約束

下面介紹模型中的約定,並定義gg-*這樣的抽象動作來完成約定中的行為

  • 每一次的提交都必須有意義:

git在每次提交的時候要求輸入對此提交的概括,這個概括不能為空。

正確的提交概括:更新了程序doc

錯誤的提交概括:我剛才幹了些啥?

  • 開發型任務中的master類型與develop類型分支必須成對出現,master分支的推進只能來源與release分支和hotfix分支的合併,禁止在master分支上直接提交。

註解:master分支上只有我們推送上去的穩定版本的程序,develop分支上的程序一直處於開發狀態,不穩定。 在開發型任務中使用gg-init進行版本控制的初始化,建立配套的master~develop分支對。

  • feature類型分支滿足:
  1. 只能從master,release類型之外的分支上創建
  2. 最終必須合併到創建他的父分支
  3. 最終分支被刪除

註解:每當有新特性需要加入的時候,我們應該從develop類型分支上新建一個feature類型分支,完成新特性的開發和測試後將特性合併到develop類型分支上。

在develop類型分支上使用gg-feature-open featureName建立並轉向一個名為feature/featureName的新分支

在一個feature類型分支上使用gg-feature-close把這個分支的工作合併到develop類型分支上,刪除此分支,完成一個特性的開發

  • release類型分支滿足:
  1. 只能從develop類型分支上創建
  2. 最終必須同時合併到master類型分支(發布新的版本)和develop類型分支(基於新版本的進一步開發)
  3. 最終分支被刪除

註解:每當工作進入到一個較為穩定階段的時候,我們可以使用gg-release-open versionNum建立並轉向一個名為release-versionNum的臨時分支,在這個分支上允許進行小的改動(比如修改一下readme文件中的版本號),然後使用gg-release-close將此版本合併(發布)到master類型分支上,同時合併到develop類型分支上,然後刪除此分支。

  • hotfix類型分支滿足:
  1. 只能從master類型分支上創建
  2. 最終必須同時合併到master類型分支(發布新的熱補丁版本)和develop類型分支(基於新版本的進一步開發)
  3. 最終分支被刪除

註解:當新版本發布後發現必須馬上解決的嚴重bug時,我們應該使用gg-hotfix-open versionNum建立並轉向一個名為hotfix-versionNum的臨時分支,在這個分支上完成bug的修復,然後使用gg-hotfix-close將此版本合併(發布)到master類型分支上,同時合併到develop類型分支上,然後刪除此分支。

  • issues類型分支滿足:
  1. 只能從master,release類型之外的分支上創建
  2. 最終必須合併到創建他的父分支
  3. 最終分支被刪除

註解:每當有(比較複雜的)問題需要解決的時候,我們應該從develop類型分支上新建一個issues類型分支,完成問題的調試後合併到develop類型分支上。

在develop類型分支上使用gg-issues-open featureName建立並轉向一個名為issues/issuesName的新分支

在一個issues類型分支上使用gg-issues-close把這個分支的工作合併到develop類型分支上,然後刪除此分支,解決了一個複雜的問題

issues類型和feature類型的實現方式一模一樣,僅僅有名字上面的差別。

  • trials類型分支滿足:

1.可以從除了master和release類型分支以外的任何類型分支上創建 2.在這個分支上請發揮想像力大膽實驗

這個分支最後的結局可能為

1.接受實驗結果,把實驗過程併入父分支,稱為good-close

2.實驗結果不理想,放棄實驗結果,從實驗開始前重新來過,稱為bad-close

3.最終分支被刪除

註解:在滿足條件的分支A上工作,時不時會冒出一些大膽的想法,這個時候使用gg-trials-open trialsName創建並轉向一個名為A/trials.trialsName的實驗分支,在這個分支上進行瘋狂的實驗,然後

  1. 使用gg-trials-good-close完成實驗的和其父分支的合併,刪除此分支或
  2. 使用gg-trials-bad-close進行狀態指針的備份並刪除此分支。

總結

模型提出了以下這些操作

gg-init

gg-feature-open

gg-feature-close

gg-release-open

gg-release-close

gg-hotfix-open

gg-hotfix-close

gg-issues-open

gg-issues-close

gg-trials-open

gg-trials-good-close

gg-trials-bad-close

這些操作的具體實現包括一些約束條件的檢測(嚴格遵照上述模型才能提交)和一些git的操作,已經在github.com/Fmajor/gg中一一實現

gg 與 gitflow 的關係

gg與gitflow都是基於A successful Git branching model,作者在開發gg的時候並不知道gitflow的存在。

gg可以認為是輕量級, 更符合作者個人習慣的gitflow, 個人用起來更順手(學習成本更低)

作者的所有git倉庫都使用gg進行管理


推薦閱讀:

自學Git,有哪些書籍或者好的學習資源?
Github 有什麼優缺點?把項目直接搭建在 Github 上合適嗎?
初學git,commit了多次才push到remote,怎麼刪除不想要的commit歷史,或是刪除之前的commit記錄?
git clone和 git pull 操作都正常,但是不能push,這是為什麼?
git是什麼?github又是什麼?他們都有什麼用啊?

TAG:Git |