1. 程式人生 > >Git 工作流

Git 工作流

中心化的工作流

優勢

  • 首先它讓每個開發者都有自己的本地的完整專案副本。隔離的環境使得每個開發都的工作獨立於專案的其它修改 —— 他們可以在自己的本地倉庫中新增提交,完全無視上游的開發,直到需要的時候。
  • 其次,它讓你接觸到了 Git 分支和合並模型。Git 分支被設計為故障安全的機制,用來在倉庫之間整合程式碼和共享更改。

如何工作

  • 中心化的工作將中央倉庫作為專案中所有修改的唯一入口。預設的開發分支叫做 master,所有的更改都被提交到這個分支。這種工作流不需要 master 之外的其它分支。
  • 開發者將中央倉庫克隆到本地後開始工作。在他們的本地專案副本中,他們可以像 SVN 一樣修改檔案和提交更改;不過這些新的提交被儲存在本地 —— 它們和中央倉庫完全隔離。這使得開發者可以將和上游的同步推遲到他們方便的時候。
  • 為了向官方專案釋出修改,開發者將他們本地 master 分支“推送”到中央倉庫。這一步等同於 svn commit,除了 Git 新增的是所有不在中央 master 分支上的提交。

管理衝突

  • 中央倉庫程式碼官方專案,因此它的提交歷史應該被視為不可更改的。如果開發者的本地提交和中央倉庫分叉了,Git 會拒絕將它們的修改推送上去,因為這會覆蓋官方提交。

  • 在開發在提交功能之前,需要 fetch 更新中央提交,在它們之上 rebase 自己的更改。
  • 如果本地修改和上游提交的衝突時,Git 會暫停 rebase 流程,給你機會手工解決這些衝突。Git 很讚的一點是,它將 git status 和 git add 命令同時用來生成提交和解決合併衝突。這使得開發能夠輕而易舉的管理他們的合併。另外,如果他們改錯了什麼,Git 能讓他們輕易的退出 rebase 過程,然後重試。

例子

  • 專案管理員生成一個空的版本庫

    ssh user@host git init --bare /path/to/repo.git
  • 三個人 A, B, C 同時編寫同一個專案,需要先在本地建立一個完整的專案副本。

    git clone ssh://user@host/path/to/repo.git

此時,Git 自動添加了一個名為 origin 的運程連線,指向中央倉庫,以方便提交。
A 可以使用標準 Git 提交流程開發功能:編輯、快取、提交。

git status
git add <some file>
git commit

同時,B 也在本地進行自己的開發工作。

  • A 釋出了他們修改

    git push origin master

此時中央倉庫會將 master -> origin/master

  • B 試圖釋出修改

    git push origin master

但是因為 A 已經提交了功能到中央倉庫,導致 B 的本地歷史和中央倉庫分叉,Git 會拒絕本次提交。

  • B 如果想提交,必須要先 rebase 本地倉庫

可以使用 git pull 來拉取並修改,

git pull --rebase origin master
  • --rebase 命令告訴 Git,在同步中央倉庫的修改之後,將 B 的所有提交移到 master 分支的頂端。

  • 如果沒有衝突的檔案,B 就可以直接進行提交了,但是如果存在衝突,可以根據提示查詢衝突的檔案,修改之後,可以繼續 rebase 操作。

    git add <some-file>
    git rebase --continue

同樣的,如果此時不知道自己做了什麼,可以回滾一次操作。

git rebase --abort
  • 然後再進行 push 就可以提交到中央版本庫了。

基於功能人分支的工作流

Feature 分支工作流

  • 掌握了中心化工作流的使用姿勢,在你的開發流程中新增功能分支是一個簡單的方式,來促進協作和開發者之間的交流。這種封裝使得多個開發專注自己的功能,而不會打擾主程式碼庫。它還能保證 master 分支永遠不會包含損壞的程式碼,給持續整合環境帶來了很大的好處。
  • 封裝功能的開發使得 pull request 的使用成為可能,用來啟動圍繞一個分支的討論。它給了其他開發者在功能併入主專案之前參與決策的機會。或者,如果你開發功能時卡在一半,可以發起一個 pull request,向同事尋求建議。重點是:pull request 使得團隊在評論其他人的工作時,變得非常簡單。

如何工作

  • Feature 分支工作流同樣使用中央倉庫,master 同樣程式碼官方的專案歷史。但是與其直接提交在本地的 master 分支,開發者每次進行新的工作時建立一個新的分支。Feature 分支應該包含描述性的名稱,比如 animated-menu-items(選單項動畫)或 issue-*1061。每個分支都應該有一個清晰、高度集中的目的。
  • Git 在技術上無法區別 master 和功能分支,所以開發者可以在 feature 分支上編輯、快取、提交,就和中心化工作流中一樣。
  • 此外,feature 分支可以被推送到中央倉庫。這使得你和其他開發者共享這個功能,而又不改變官方程式碼。既然 master 只是一個“特殊”的分支,在中央倉庫中儲存多個 feature 分支不會引出什麼問題。當然,這也是備份每個開發者本地提交的好辦法。

Pull Request

  • 除了隔離功能開發之外,分支使得通過 pull request 討論修改成為可能。一旦有人完成了一個功能,他們不會立即將它併入 master。他們將 feature 分支推送到中央伺服器上,釋出一個 pull request,請求將他們的修改併入 master。這給了其他開發者在修改併入主程式碼庫之前審查的機會。
  • 程式碼審查是 pull request 的主要好處,但他們事實上被設計成為討論程式碼的一般場所。你可以把 pull request 看作是專注某個分支的討論版。也就是說他們可以用於開發流程之前。比如,一個開發者在某個功能上需要幫助,他只需要發起一個 pull request。感興趣的小夥伴會自動收到通知,看到相關提交中的問題。
  • 一旦 pull request 被接受了,釋出功能的行為和中心化的工作流是一樣的。首先,確定你本地的 master 和上游的 master 已經同步。然後,將 feature 分支併入 master 已經同步。然後可以將 feature 分支併入 master,將更新的 master 推送回中央倉庫。

Gitflow 工作流

  • GitFlow 工作流圍繞專案釋出定義了一個嚴格的分支模型。有些地方比功能分支工作流更復雜,為管理大型專案提供了框架。
  • 和功能分支工作流相比,這種工作流沒有增加任何新的概念或命令。它給不同的分支指定了特定的角色,定義它們應該如何、什麼時候交流。除了功能分支之外,它還為準備釋出、維護髮布、記錄釋出分別使用了單獨的分支。當然,還能享受到功能分支工作流帶來的所有好處:pull request、隔離實驗和更高效的協作。

如何工作

  • GitFlow 工作流仍然使用中央倉庫作為開發者溝通的中心。和其它工作流一樣,開發者在本地工作,將分支推送到中央倉庫。唯一的區別在於專案的分支結構。

歷史分支

  • 和單獨的 master 分支不同,這種工作流使用兩個分支來記錄專案歷史。master 分支儲存官方釋出歷史,develop 分支用來整合功能分支。同時,這還方便了在 master 分支上給所有提交打上版本號標籤。

  • 工作流剩下的部分圍繞這兩個分支的差別展開。

功能分支

  • 每個新功能都放置在自己的分支中,可以在備份/協作時推送到中央倉庫。但是與其合併到 master,功能分支將開發分支作為父分支。當一個功能完成時,它將被合併回 develop。功能永遠不應該支援在 master 上互動。

  • 功能分支加上 develop 分支就是我們之前據說的功能分支工作流。

釋出分支

  • 一旦 develop 分支的新功能足夠釋出,你可以從 develop 分支 fork 一個釋出分支。這個分支的建立開始了下個釋出週期,只有和釋出相關的任務應該在這個分支進行,如修復 bug、生成文件等。一旦準備好釋出,釋出分支將合併進 master,打上版本號的標籤。另外,它也應該合併回 develop,後者可能在釋出啟動之後有了新的進展。
  • 使用一個專門的分支來準備釋出確保一個團隊完善當前的釋出,其它團隊可以繼續開發下一個釋出的功能。它還建立了清晰的開發階段。
  • 通常約定:
    • 從 develop 建立分支
    • 合併進 master 分支
    • 命名規範 release-* 或者 release/*

維護分支

  • 維護或者“緊急修復”分支用來快速給產品釋出打上補丁。這是唯一可以從 master 上 fork 的分支。一旦修復完成了,它應該被併入 master 和 develop 分支,master 應該打上更新的版本號的標籤。
  • 有一個專門的 bug 修復開發線使得團隊能夠處理 issue,而不打斷其它工作流或是要等到下一個釋出週期。你可以將維護分支看作在 master 分支上工作的臨時釋出分支。

例子

建立一個開發分支

  • 為預設的 master 分支建立一個互補的 develop 分支。最簡單的辦法是在本地建立一個空的 develop 分支,將他推送到伺服器上:

    git branch develop
    git push -u origin develop
  • 這個分支將會包含專案中所有的歷史,而 master 將包含不完全的版本。其他開發者應該將中央倉庫克隆到本地,建立一個分支來追蹤 develop 分支:

    git clone http://xxx/xx/repo.git
    git checkout -b develop origin/develop

開始了新的功能

  • 當兩個人都需要在不同分支上開始工作,即為自己的功能建立單獨的分支。且他們的分支都是基於 develop 而不是 master:

    git checkout -b some-feature develop
  • 他們都使用“編輯、快取、提交”的一般約定來向功能分支新增提交:

    git status
    git add <some-file>
    git commit

完成功能

  • 添加了一些提交後,可以使用 pull request,現在正是發起的好時機,請求將新功能併入 develop 分支。否則可以先併入本地的 develop 分支,推送到中央倉庫:

    git pull origin develop
    git checkout develop
    git merge some-feature
    git push
    git branch -d some-feature
  • 第一個命令在嘗試併入功能分支之前確保 develop 分支已經是最新的。注意,功能絕不該直接併入 master。衝突的處理方式和中心化工作流相同。

釋出新功能

  • 當另外的開發人員,仍在他自己的分支上工作時,開始準備專案的第一個官方釋出。和開發功能一樣,新建一個分支來封裝釋出的準備工作。這也正是釋出的版本號建立的第一步:

    git checkout -b release-0.1 develop
  • 這個分支用來整理提交,充分測試,更新文件,為即將到來的釋出做各種準備。它就像是一個專門用來完善釋出的功能分支。
  • 一旦釋出準備穩妥,即將其併入 master 和 develop,然後刪除釋出分支。合併回 develop 很重要,因為可能已經有關鍵的更新新增到釋出分支上,而開發新功能需要用到它們。同樣的,如果團隊重視程式碼審查,現在將是發起 pull request 的完美時機。

    git checkout master
    git merge release-0.1
    git push
    git checkout develop
    git merge release-0.1
    git push
    git branch -d release-0.1
  • 釋出分支是功能開發(develop)分支和公開發布(master)之間的過渡階段。不論什麼時候,將提交併入 msater 時,你應該為提交打上方便引用的標籤:

    git tag -a 0.1 -m "Initial public release" master
    git push --tags
  • Git 提供了許多鉤子,即倉庫中特定事件發生時被執行的指令碼。當你向中央倉庫推送 master 分支或者標籤時,你可以配置一個鉤子來自動化構建公開發布。

終端使用者發現一個 Bug

  • 正式釋出之後,兩個開發一起為下一個釋出開發功能。這時,一個終端使用者開了一個 issue 抱怨說當前釋出中存在一個 Bug。為了解決這個 bug,先從 master 建立一個維護分支,用幾個提交修復這個 issue,然後直接合並回 master。

    git checkout -b issue*001 master
    ##Fix the bug
    git checkout master
    git merge issue-*001
    git push
  • 和釋出分支一樣,維護分支包含了 develop 中需要的重要更新,因此需要執行同樣的合併。接下來,可以刪除這個分支:

    git checkout develop
    git merge issue-*001
    git push
    git branch -d issue-*001

各分支的意義

  • feature (多個) 主要是自己玩了,差不多的時候要合併回 develop 去。不與 master 互動。
  • develop (同時間一個) 主要是和 feature 以及 release 互動
  • release (同時間一個) 總是基於 develop,最後又合併回 develop。當然對應的 tag 要合併到 master 分支,生命週期短,僅是為了釋出程式
  • hotfix (同一時間一個) 總是基於 master,並最後合併到 master 和 develop。生命同期較短,用來修復 bug 或小粒度修改釋出
  • master (僅一個) 關聯 tag 和釋出

模型中各個模組內容的使用

  • 在這個模型中,master 和 develop 都具有象徵意義。master 分支上的程式碼總是穩定的 (stable build),隨時可以釋出出去。develop 上的程式碼總是從 feature 上合併過來的,可以進行 Nightly Builds,但不直接在 develop 上進行開發。當 develop 上的 featur 足夠多以致於可以進行新版本的釋出時,可以建立 release 分支。
  • release 分支基於 develop,進行委陰簡單的修改後就被合併到 master,並打上 tag,表示可以釋出了。緊接著 release 將被合併到 develop;此時 Develop 可能往前跑了一段,出現合併衝突,需要手工解決衝突後再次合併,這步完成後就刪除 release 分支
  • 當從已釋出版本中發現 bug 要修復時,就應用到 hotfix 分支了。hotfix 基於 master 分支,完成 bug 修復或者緊急修改後,要 merge 回 master,打上一個新的 tag,並 merge 回 develop,刪除 hotfix 分支。
  • 由此可見 release 和 hotfix 的生命週期都較短,而 master 和 develop 雖然總是存在,但去不常使用。