[譯] 怎樣(以及為什麼要)保持你的 Git 提交記錄的整潔

Russ_Zhong發表於2018-06-19

怎樣(以及為什麼要)保持你的 Git 提交記錄的整潔

Git 提交記錄很容易變得混亂不堪,現在教你怎麼保持整潔!

提交功能是 Git 倉庫的關鍵部分之一,不僅如此,提交資訊也是倉庫的生命日誌。專案或者倉庫在隨著時間的推移不斷演變(新功能不斷加入,Bug 被不斷修復,體系架構也被重構),提交資訊成為了人們檢視倉庫所發生的變化或者怎麼發生變化的地方。因此使用簡短精確的提交資訊反映出內部的變化是非常重要的。

為什麼有意義的提交記錄非常重要?

Git 提交資訊是你在你所寫的程式碼上所留下的指紋。不管你今天提交了什麼程式碼,一年之後你再看到這個變化;你會非常感謝你所寫的有意義的、乾淨整潔的提交資訊,這也會使得你的同事工作更輕鬆。當根據上下文分開提交時,可以更快地找到 Bug 是在哪一次提交中被引入的,將首先引起 Bug 的這次提交進行回退可以非常簡便的修復 Bug。

當開發大型專案時,我們經常處理一大堆部件變動,包括更新、新增和移除。在這種場景中確保好好維護提交資訊是很艱難的,尤其是當開發週期是數天、數週、甚至數月時。因此為了簡化維護提交記錄的工作,這篇文章會使用許多開發人員在 Git 倉庫上工作時可能會經常遇到的常見場景。

但是在我們深入瞭解之前,讓我們快速瀏覽一下我們假設的 Ruby 應用程式中典型的開發工作流程。

注意: 這篇文章預設你已經掌握 Git 基礎,分支如何工作,如何將分支的未提交更改新增到暫存區以及如何提交更改。如果你不太熟悉這些流程,我們的文件是一個好的起點。

生活中的某天

現在,我們正在開發一個小型的 Ruby on Rails 專案,在這個專案中我們需要在首頁新增一個導航檢視,這需要更新和新增許多檔案。下面是整個流程分解的每個步驟:

  • 你開始開發某個功能,更新了一個檔案;讓我們稱它為 application_controller.rb
  • 這個功能還需要你更新一個檢視:index.html.haml
  • 你新增了索引頁所使用的一個部分:_navigation.html.haml
  • 為了體現你所新增的那一部分,樣式表也需要被更新:styles.css.scss
  • 改完這些模組,功能已經完成了,是時候更新測試檔案了;要更新的檔案如下:
    • application_controller_spec.rb
    • navigation_spec.rb
  • 測試也更新了,並且如期地通過了所有的測試案例,現在是時候提交更改了!

因為所有的這些檔案屬於不同的架構領域,我們彼此隔離地提交這些檔案的更改,以確保每次提交代表了特定的上下文,並且按照特定順序進行提交。我通常偏向於後端 -> 前端的提交順序:首先提交以後端為中心的更改,其次提交中間層檔案的更改,最後提交以前端為中心的更改。

  1. application_controller.rb & application_controller_spec.rb新增導航路由
  2. _navigation.html.haml & navigation_spec.rb頁面導航檢視
  3. index.html.haml渲染導航部分
  4. styles.css.scss為導航新增樣式

在提交更改之後,我們會為分支建立一個合併請求。一旦建立了合併請求,在被合併到倉庫的 master 分支之前,通常會由你的同事對程式碼進行審查。現在我們瞭解一下程式碼審查過程中可能會遇到的不同情況。

場景 1:我需要修改最近一次的提交

想象一下程式碼審查者在審查 styles.css.scss 時提出了一個修改建議。這種情況,修改起來非常簡單,因為樣式表修改是你分支上的最後一次提交。下面是我們應該怎樣處理這種情況:

  • 你直接在你的分支上對 styles.css.scss 做必要的修改。
  • 一旦你完成了修改,將這些修改新增到暫存區進行暫存;執行命令 git add styles.css.scss
  • 一旦修改被新增到暫存區,我們需要將這些修改新增到我們的最後一次提交;執行命令: git commit --amend
    • 命令分解:這裡,我們使用 git commit 命令修改最近一次提交,把暫存中的任何修改合併到最近一次提交。
  • 這會在你的 Git 定義的文字編輯器中開啟你最後一次的提交,它具有提交資訊為導航新增樣式
  • 因為我們只更新了 CSS 宣告,所以我們不需要修改提交資訊。你可以只做儲存然後退出 Git 為你開啟的文字編輯器,你的更改會被反映到提交上。

由於你修改了一個已經存在的提交,你需要使用 git push --force-with-lease <remote_name> <branch_name> 命令將這些修改強制推送到你的遠端倉庫。這個命令會使用我們本地倉庫中所做的修改來覆蓋遠端倉庫中為導航新增樣式這個提交。

當你強制推送分支時,有一點需要注意,那就是當你所在分支是一個多人協作的分支時,你的強制推送可能會給其他人的正常推送造成麻煩,因為遠端分支上有一些強制推送的新的提交。因此,你應該合理地使用這個功能。你可以在這裡學習到更多有關 Git 強制推送選項的資訊。

場景 2:我需要修改一次特定的提交

在上一個場景中,因為我們只需要修改最近的一次提交,所以做起來非常簡單,但是想象一下如果程式碼審查者建議修改 _navigation.html.haml 檔案中的某些部分。在這種場景下,它是第二次提交,所以修改起來不像第一個場景中那麼直接。讓我們看看怎麼處理這種情況:

每次在分支上提交更改,都會有一個獨一無二的 SHA1 雜湊字串作為更改提交的標誌。可以把它看做區分每次提交的獨特 ID。可以通過執行 git log 命令檢視某個分支上的所有提交以及它們分別的 SHA1 雜湊值。執行命令之後,可以看到類似下面的輸出,其中最近一次的提交在頂部。

commit aa0a35a867ed2094da60042062e8f3d6000e3952 (HEAD -> add-page-navigation)
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 15:24:02 2018 +0530

    為導航新增樣式

commit c22a3fa0c5cdc175f2b8232b9704079d27c619d0
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 08:42:52 2018 +0000

    渲染導航部分

commit 4155df1cdc7be01c98b0773497ff65c22ba1549f
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 08:42:51 2018 +0000

    頁面導航檢視

commit 8d74af102941aa0b51e1a35b8ad731284e4b5a20
Author: Kushal Pandya <kushal@gitlab.com>
Date: Wed May 2 08:12:20 2018 +0000

    新增導航路由
複製程式碼

現在輪到 git rebase 命令表演了。不管什麼時候我們想要用 git rebase 命令修改一個特定的更改提交,我們首先要將我們分支的 HEAD 變基到我們想要修改的更改提交之前。在這個場景中,我們需要修改頁面導航檢視的更改提交。

更改提交日誌

現在,注意我們想要修改的更改提交之前的一個更改提交的雜湊值;複製這個雜湊值然後按照一下步驟進行操作:

  • 通過執行命令 git rebase -i 8d74af102941aa0b51e1a35b8ad731284e4b5a20 來將分支變基到我們要修改的更改提交的前一個更改提交
    • 命令分解:現在我們正在使用 Git 的 rebase 命令的互動模式,通過提交 SHA1 雜湊值我們可以將分支進變基。
  • 這條命令會執行 Git 變基命令的互動模式,並且會開啟文字編輯器展示你所變基到的更改提交之後的所有更改提交。它看起來是這樣的:
pick 4155df1cdc7 頁面導航檢視
pick c22a3fa0c5c 渲染導航部分
pick aa0a35a867e 為導航新增樣式

# Rebase 8d74af10294..aa0a35a867e onto 8d74af10294 (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
複製程式碼

注意到每個更改提交之前都有一個單詞 pick,並且在它下面的內容裡面,有所有的我們可以使用的關鍵字。因為我們想要編輯一個更改提交,所以我們將命令 pick 4155df1cdc7 頁面導航檢視 修改為 edit 4155df1cdc7 頁面導航檢視。儲存更改並退出編輯器。

現在你的分支已經被變基到包含 _navigation.html.haml 的更改提交之前了。開啟檔案並完成每個審查反饋中的修改需求。一旦你完成了修改,使用命令 git add _navigation.html.haml 將它們暫存起來。

因為我們已經暫存了這些更改,所以現在應該把分支 HEAD 重新移動到我們原來的更改提交(同時包含我們所有的新的更改的提交),執行 git rebase --continue,這將會在終端中開啟你的預設編輯器並且向你展示變基期間我們所做的更改的提交資訊;頁面導航檢視。如果需要你可以修改這個提交資訊,但現在我們保留它,因此接下來儲存修改然後退出編輯器。這個時候,Git 會重新展示你剛剛修改的更改提交之後的所有更改提交併且分支的 HEAD 已經回到了我們原來的所有更改提交的頂部,它包含所有你對其中某個更改提交所做的所有新的更改。

因為我們又一次修改了遠端倉庫中的一個提交,我們需要再次使用 git push --force-with-lease <remote_name> <branch_name> 命令將分支強制提交。

場景 3:我需要新增、移除或者合併提交

一個常見的場景就是當我們剛剛修改了一些之前的提交併重新提交了一些新的更改。現在讓我們儘可能的精簡一下這些提交,用原來的提交合並它們。

你所要做的就是像其它場景中所做的那樣開始互動性的變基操作。

pick 4155df1cdc7 頁面導航檢視
pick c22a3fa0c5c 渲染導航部分
pick aa0a35a867e 為導航新增樣式
pick 62e858a322 Fix a typo
pick 5c25eb48c8 Ops another fix
pick 7f0718efe9 Fix 2
pick f0ffc19ef7 Argh Another fix!
複製程式碼

現在假設你想要合併所有的那些提交到 c22a3fa0c5c 渲染導航部分。你只需要做:

  1. 把你想要合併的那些更改提交往上移動,以使得它們位於最終合併的更改提交之下。
  2. 將每一個更改提交的模式由 pick 改為 squash 或者 fixup

注意: squash 模式會在描述中保留修改時的資訊。而fixup 不會,它只會保留原來的提交資訊。

你會以下面這種結果結束實驗:

pick 4155df1cdc7 頁面導航檢視
pick c22a3fa0c5c 渲染導航部分
fixup 62e858a322 Fix a typo
fixup 5c25eb48c8 Ops another fix
fixup 7f0718efe9 Fix 2
fixup f0ffc19ef7 Argh Another fix!
pick aa0a35a867e 為導航新增樣式
複製程式碼

儲存更改並退出編輯器,你就完成了!這就是完成之後的歷史提交記錄:

pick 4155df1cdc7 頁面導航檢視
pick 96373c0bcf 渲染導航部分
pick aa0a35a867e 為導航新增樣式
複製程式碼

像之前一樣,你現在所要做的所有工作只是執行 git push --force-with-lease <remote_name> <branch_name> 命令,然後所有的修改都被強制推送了。

如果你想要完全地移除一個更改提交,而不是 squash 或者 fixup,你只需要輸入 drop 或者簡單地刪除這一行。

避免衝突

為避免發生衝突,請確保您在時間線上上移的提交不會觸及其後的提交所觸及的相同檔案。

pick 4155df1cdc7 頁面導航檢視
pick c22a3fa0c5c 渲染導航部分
fixup 62e858a322 Fix a typo                 # this changes styles.css
fixup 5c25eb48c8 Ops another fix            # this changes image/logo.svg
fixup 7f0718efe9 Fix 2                      # this changes styles.css
fixup f0ffc19ef7 Argh Another fix!          # this changes styles.css
pick aa0a35a867e 為導航新增樣式  # this changes index.html (no conflict)
複製程式碼

附加提示:快速 fixup

如果你很清楚你想要修改哪一個更改提交,在提交更改時不必浪費腦力思考一些 "Fix 1", "Fix 2", …, "Fix 42" 這樣的名字。

步驟 1:初識 --fixup

在你暫存那些更改之後,使用以下命令提交更改:

git commit --fixup c22a3fa0c5c
複製程式碼

(注意到這個雜湊值是屬於 c22a3fa0c5c 渲染導航部分這個更改提交的)

這會產生這樣的提交資訊:fixup! 渲染導航部分

步驟 2:還有這個小夥伴 --autosquash

通過簡單的使用互動變基操作。你可以讓 git 自動的把所有 fixup 放到正確的位置。

git rebase -i 4155df1cdc7 --autosquash

歷史提交記錄會變成下面這樣:

pick 4155df1cdc7 頁面導航檢視
pick c22a3fa0c5c 渲染導航部分
fixup 62e858a322 Fix a typo
fixup 5c25eb48c8 Ops another fix
fixup 7f0718efe9 Fix 2
fixup f0ffc19ef7 Argh Another fix!
pick aa0a35a867e 為導航新增樣式
複製程式碼

一切就緒,你只需要審查並繼續。

如果你覺得自己喜歡冒險,你可以做一個非互動式的變基 git rebase --autosquash,但前提是你喜歡過這種有風險的生活,因為你沒有機會在這些合併應用前檢查它們。

場景 4:我的提交記錄沒啥有用的內容,我需要重新開始!

如果你在開發一個大型的功能,那通常會有許多修復和程式碼審查反饋的修改頻繁的被提交。我們可以將提交的清理工作留到開發結束,而不是不斷重新設計分支。

這是建立補丁檔案非常方便的地方。實際上,在開發人員可以使用以 Git 為基礎的服務比如 GitLab 之前,補丁檔案一直是開發大型開源專案通過郵件分享程式碼與合併程式碼的主要方式。假設你有一個提交量非常巨大的分支(例如;add-page-navigation),它對於倉庫的變更歷史表述得不是很清晰。以下是如何為您在此分支中所做的所有更改建立補丁檔案:

  • 建立補丁檔案的第一步是確保您的分支具有來自 master 分支的所有更改並且與這些更改沒有衝突。
  • 您可以在 add-page-navigation 分支中籤出時執行 git rebase mastergit merge master,以將所有從 master 進行的更改轉移到您的分支上。
  • 現在建立補丁檔案;執行 git diff master add-page-navigation > ~/add_page_navigation.patch
    • 命令分解:在這裡我們使用了 Git 的 diff 特性,查詢 master 分支和 add-page-navigation 分支之間的差異,然後重定向輸出(通過 > 符號)到一個檔案,在我們的使用者主目錄(在 *nix 系作業系統中通常是 ~/)中命名為 add_page_navigation.patch
  • 你可以指定你想儲存這個檔案的路徑,檔名和副檔名為任意你想要的值。
  • 一旦命令執行並且沒有看到任何錯誤,就會生成補丁檔案。
  • 現在簽出 master 分支;執行 git checkout master
  • 從本地倉庫刪除分支 add-page-navigation;執行 git branch -D add-page-navigation。請記住,我們已經在建立的補丁檔案中更改了此分支。
  • 現在建立一個具有相同名稱的新分支(master 被簽出);執行 git checkout -b add-page-navigation
  • 現在,這是一個新的分支,沒有任何你所做的修改。
  • 最後,從修補程式檔案中應用您的更改;git apply ~/add_page_navigation.patch
  • 在這裡,所有更改都會應用到分支中,並且它們將顯示為未提交,就好像您所做的所有修改都完成了,但沒有任何修改是在分支中實際提交的。
  • 現在,您可以繼續並按照所需順序提交按影響區域分組的單個檔案或檔案,並使用簡單明瞭的提交資訊。

跟之前的場景一樣,我們修改了整個分支,現在是時候強制推送了!

結論

雖然我們已經介紹了使用 Git 進行日常工作流程中出現的大多數常見和基本情況,但重寫 Git 提交歷史是一個巨大的話題,如果你已經熟悉上述建議,您可以在 Git 官方文件。快樂的 git'ing!

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章