這也是我曾被問過的 Git 問題,之前一知半解回答得哩哩辣喇 - 為什麼 Visual Studio 的 Git Commit 資訊欄有時會出現兩組人名時間?有時兩組不同人,有時是作者出現兩次?在什麼狀況下會出現?

這篇文章將試著回答以上問題。

Git 的每個 Commit 都有作者(Author)跟提交者(Committer)兩種角色,每次新增修改刪除檔案並使用 git commit 指令存成 Commit,一開始 Commit 的作者與提交者都是執行提交動作的操作人員(嚴格來說是 user.name 跟 user.email 所設定的身分),而作者日期(AuthorDate)及提交日期(CommitDate)就是執行 git commit 的時間。但如果 Commit 經過再處理或操作,提交日期將會更新,而也可能出現提交者與作者不同的狀況。造成作者/作者日期與提交者/提交日期不同的常見情境有:

  1. 執行 Rebase (包含 git pull --rebase)
  2. 執行 Amend 修改 Commit 訊息
  3. 執行 Cherry-Pick 搬動 Commit
  4. 產生更新檔交付他人套用

總之,只要 Git 操作導致 Commit ID 改變,就必須更新提交者及提交日期,若操作者並非該 Commit 的原始提交者,便會發生作者與提交者不同的狀況。要觀察提交日期與提交者,除使用 Visual Studio、Source Tree、Git GUI 等 GUI 工具,用 git show --pretty=fuller commit_id 亦可查看:

我設計了一組實驗,由於程序稍複雜,先簡單說明操作流程:

  1. 我想測試 push 跟 pull 但不想動用 Github 或 TFS,故用 git init --bare 建了一個 Bare Repository 放在本機資料夾 upstream 當作同步來源。
    (註:git init --bare 建立的資料夾結構如下圖,基本上就是正常 Git 工作資料夾下 .git 目錄的內容。Bare Repository 目錄不能進行一般的 git add、git commit 操作,但可以 git log、git show... 等,多半用來當成 git push/pull 的來源)
  2. 模擬有兩個開發者 jeffrey 跟 darkthread,各自 git clone upstream 回去開發。
  3. jeffrey 先建立 master 分支並 push,darkthread 再將其 pull 回去,此時二者 master 內容一致。darkthread 多建一個 release 分支以備後續使用。
  4. jeffrey 異動一些檔案,加入【by Jeffrey】 Commit 並 git push。
  5. darkthread 異動一些檔案並提交,用 git pull --rebase 將 jeffrey 在 4. 做的更新取回(此時剛才的 Commit 會因 rebase 而改變 Commit Id)
  6. darkthread 進行第二次異動並提交(此 Commit 未受 git pull -rebase 影響,預期作者與提交者相同)
  7. darkthread 進行第三次異動並提交,隨後立即用 git commit --amend 修改剛才 Commit 的訊息文字
  8. darkthread 執行 git push
  9. darkthread 切到 release 分支,用 git cherry pick 將【by Jeffrey】 Commit 拉到 release 分支,之後 git push 到 upstream
  10. jeffrey 先 git pull 取回 darkthread 做的異動,切到 release 分支,下指令將 master 合併進 release

下面是進行模擬的完整指令,建一個實驗資料夾,修改 lab_folder 設定後貼到 Cmder 執行,會建立三個資料夾 upstream、jeffrey、darkthread,並完成一連串 Commit、Pull、Push 動作:(中間用了一個 DOS 小技巧,透過 ping localhost -n 4 > NUL 等待三秒,確保作者日期與提交日期有明顯區別)

rem 請先建好實驗資料夾,並修改以下變數
set lab_folder=D:\Lab
rem 切換到實驗資料夾,清空前次 Git 測試結果
cd /d %lab_folder% 
if exist %lab_folder%\upstream (rmdir /s /q %lab_folder%\upstream)
if exist %lab_folder%\jeffrey (rmdir /s /q %lab_folder%\jeffrey)
if exist %lab_folder%\darkthread (rmdir /s /q %lab_folder%\darkthread)
rem 建立 upstream Git Repository
mkdir %lab_folder%\upstream && cd %lab_folder%\upstream
git init --bare
rem 模擬 jeffrey 及 darkthread clone 建立本機 Repository 
cd %lab_folder%
git clone ./upstream ./jeffrey
git clone ./upstream ./darkthread
rem jeffrey,建立 master 並 push
cd %lab_folder%/jeffrey
git config user.name "Jeffrey"
git config user.email "jeffrey@mail.net"
git commit -m "初始化" --allow-empty
git push
rem darkthread,pull 並建立 release 分支
cd %lab_folder%/darkthread
git config user.name "darkthread"
git config user.email "darkthread@mail.net"
git pull
git branch release
rem jeffrey,修改並 push
cd %lab_folder%/jeffrey
touch by-jeffrey.txt
git add . && git commit -m "by Jeffrey"
git push
rem 暫停三秒產生時間差
ping localhost -n 4 > NUL
rem darkthread,修改、pull --rebase、push
cd %lab_folder%/darkthread
git config user.name "darkthread"
git config user.email "darkthread@mail.net"
rem 建立 release 分支
git branch release
rem 加入一些 Commit
touch darkthread1.txt 
git add . && git commit -m "有 pull --rebase, by darkthread"
ping localhost -n 4>NUL
git pull --rebase
ping localhost -n 4 > NUL
git push
touch darkhread2.txt
git add . && git commit -m "無 pull --rebase, by darkthread"
git push
touch darkthread3.txt
git add . && git commit -m "等下改掉"
ping localhost -n 4 > NUL
git commit --amend -m "Amend 改訊息, by darkthread"
git push
rem 用 cherry-pick 將 by Jeffrey Commit 拉進 release
git checkout release
git cherry-pick master~3
rem 設定 release 上游順便 push
git push --set-upstream origin release
rem jeffrey, 將 master 合併到 release
cd %lab_folder%/jeffrey
git pull
git checkout release
git merge master

合併完 master 的 release 分支長這樣,共有 7 個 Commit:

【初始化】Commit 略過不用看,直接從 release 的 e5ec7596 【by Jeffrey】開始,它是被 darkthread 用 git cherry-pick 從 master 拉進 release 的,故作者為 jeffrey、提交者為 darkthread:

重複出現的 02c07632 【by Jeffrey】是 Cherry-Pick 造成的,這個位於 master 的 Commit 保持原汁原味,作者與提交者、作者日期與提交日期都相同,Visual Studio 只有在作者日期與提交日期不同才會顯示提交者及提交日期,平時只會顯示作者及作者日期,故我們改用 git show --pretty=fuller 來確認這點:

Commit f755ce3c 提交後有執行 git pull -rebase,實際上有重新運算過(Commit Id 已變),作者與提交者相同,但提交日期較新:

Commit c012c911 提交後就沒再做過處理,會維持作者時間與提交時間一致:

Commit c92511cb 提交後改過提交訊息文字(期間靠 ping -n 4 延遲了 3 秒),會重新計算 Commit Id,故跟被 Rebase 過的 Commit f755ce3c 一樣,作者與提交者相同,但提交日期晚了 3 秒:

以上實驗展示了作者、提交者、作者時間、提交時間關聯的常見情境,未來對二者的異同就不會再霧煞煞囉。

Experiments showing the difference between Git's author, author date, committer and comitter date.


Comments

# by 高翔

Thank you

# by davidhcefx

> 重複出現的 02c07632 【by Jeffrey】是 Cherry-Pick 造成的,這個位於 master 的 Commit 保持原汁原味 我覺得,應該說 "02c07632" 是位於 master 的原 commit,而 "e5ec7596" 才是 cherry-pick 造成的,這樣比較準確一點?

# by Jeffrey

to davidhcefx,原意中的重複是以 Commit 在線圖出現先後順序,加上如果不做 Cherry-Pick 不會出現重複,是從視覺直觀的角度去看。所以如你所說,得追究 Commit Hash、Author、Committer 才能精確找出原本脈絡,謝謝補充。

Post a comment