你可能不知道

git 指令

(二)

也大概用不到也不會想用到

rerere and reflog

FlyC

Outline

  • 場景連連看
  • git rerere
  • git reflog

場景連連看

請將對應的場景會使用到的git 指令做連連看

寫code寫到一半、還沒到適合commit 的時機點就要去hotfix;

hotfix 到一半、還沒到適合commit 的時機點又有超級hotfix,我不想切來切去

我的程式碼炸裂、關聯檔又太多,

不確定問題在哪隻檔案,我想知道是哪個commit 害他壞去

開發途中,我不確定我的feature 和master 有沒有衝突,

但我不想留下一直把master merge 回自已身上的commit

我不小心把遠端的分支和自己本地的分支全砍光後,

PM說剛剛的單還是上好了

候選名單:

rerere

bisect

reflog

worktree

git worktree

寫code寫到一半、還沒到適合commit 的時機點就要去hotfix;

hotfix 到一半、還沒到適合commit 的時機點又有超級hotfix,我不想切來切去

git bisect

我的程式碼炸裂、關聯檔又太多,

不確定問題在哪隻檔案,我想知道是哪個commit 害他壞去

git rerere

開發途中,我不確定我的feature 和master 有沒有衝突,

但我不想留下一直把master merge 回自已身上的commit

git reflog

我不小心把遠端的分支和自己本地的分支全砍光後,

PM說剛剛的單還是上好了

git rerere

reuse recorded resolution

重新使用 - 被記錄過的 - 解法

開發途中,我不確定我的feature 和master 有沒有衝突,

但我不想留下一直把master merge 回自已身上的commit

在開始之前,先來個預防針

 

git rerere 不太好理解

 

在操作上也比其他部分繁瑣、

很容易造成分支上的打結

 

所以

 

建議在操作上還是以複製為主、

減少操作上的失誤

警告

那麼,我們開始吧!

首先,git rerere 是git 的一個

 

設定項目

 

就像是user.name, core.ignorecase 等同款

設定過一次、就會永久生效

當然也會依照.gitconfig 的層級做疊加的影響範圍

 

那麽,就先把它打開吧

# default 是false
git config --global rerere.enabled true 

可以透過檢查 ~/.gitconfig 檔案來檢查是否設定成功

設定完成後,我們來介紹一下等等的流程:

 

我們要故意製造衝突

 

1. 創建 1 個假裝的主分支

2. 創 3 個新檔案,裡面都留空

3. 創建 2 個分支,A 跟 B

4. 分別在A 和B 修改步驟 1. 創的第 1 個檔案,把內容分別          改成A1B2

5. 切換回主分支

6. 把A merge 回主分支

7. 切到B,把主分支往自己身上 merge

8. 解決衝突

# 創建一個假的測試用master 分支
git checkout -b test-rerere/master

# 創建 3 個檔案 + commit
touch file-1.txt
touch file-2.txt
touch file-3.txt
git add file-1.txt file-2.txt file-3.txt
git commit -m "[add] touch 3 files"

# 創建 2 個branch
git checkout -b test-rerere/A-分支
git checkout -b test-rerere/B-分支

# 分別修改第 1 個檔案 + commit
## A branch
git checkout test-rerere/A-分支
echo "A-1" > file-1.txt
git add file-1.txt
git commit -m "[modified] modified file-1.txt to A"
## B branch
git checkout test-rerere/B-分支
echo "B-1" > file-1.txt
git add file-1.txt
git commit -m "[modified] modified file-1.txt to B"

# 切換回主分支 + 把A merge 回主分支
git checkout test-rerere/master
git merge test-rerere/A-分支 --no-ff -m "Merge A into master"

# 切到B,把主分支往自己身上 merge
git checkout test-rerere/B-分支
git merge --no-ff test-rerere/master

那麼,開始吧

我們來解衝突吧!

# 解決衝突
echo "file-1 我就是要B" > file-1.txt
git diff # 查看差異
# bash
git add file-1.txt
# 打好 Merge message
git commit -m "Merge master into B"


# 結束!
cat file-1.txt # file-1 我就是要B

我們來看看線吧

唔... 合來合去的有點醜,我決定了,

我要把 剛剛解決衝突的方式記下來

等到最後要合進master 的時候再解就好了

那,就來倒退

# 退回上一個commit
git reset --hard head^
git log --graph --oneline --all

那我們繼續開發囉,一樣

 

 

 

切回 A ,開發file-2

切回 B ,開發file-2

先把 A 合進master

再把master 合到 B 上

解決衝突

 

# 首先是A 分支
git checkout test-rerere/A-分支
echo A-2 > file-2.txt
git add file-2.txt
git commit -m "[add] add A-2 to file-2"

# 換B 分支
git checkout test-rerere/B-分支
echo B-2 > file-2.txt
git add file-2.txt
git commit -m "[add] B-2 to file-2"

# 切回主分支
git checkout test-rerere/master
# 合併A 分支
git merge test-rerere/A-分支 --no-ff -m "Merge A into master"

# 切回B 分支,接著把master merge 回B 分支
git checkout test-rerere/B-分支
git merge --no-ff test-rerere/master

#衝突!

那麼,來看看 git diff 吧!

這次應該會有之前file-1.txt 的衝突、跟新的file-2.txt 的衝突

git diff

嗯,好像跟之前ㄧㄧㄤ...

這個衝突我好像在哪裡看過嘛!

而且這個解法跟我之前解的好像很像嘛!

沒錯,這個衝突就是之前發生在 file-1.txt 的衝突

咱 git rerere 的功能就在這裡啦!

 

總之,先把新出現的衝突解一解吧

echo "file-2 我就是要B" > file-2.txt
git diff # 可以稍微看一下
git add file-2.txt
git add file-1.txt # 別忘了file-1.txt 也要加進來喔
git commit -m "Merge master into B"

# 直接來看一下log 吧
git log --graph --oneline 

直接來看一下log 吧

不要緊張不要害怕,為了讓線乾淨些

讓我們回到上一個還沒有把master 往自己身上merge 的時候吧!

# 回到上一步
git reset --hard head^
# 查看 log
git log --graph --oneline --all

那麼,我們要繼續囉

直接對 file-3.txt 做一樣的事情,這部分就不做解釋了

直接寫code

# 首先是A 分支
git checkout test-rerere/A-分支
echo A-3 > file-3.txt
git add file-3.txt
git commit -m "[add] add A-3 to file-3"

# 換B 分支
git checkout test-rerere/B-分支
echo B-3 > file-3.txt
git add file-3.txt
git commit -m "[add] B-3 to file-3"

# 切回主分支
git checkout test-rerere/master
# 合併A 分支
git merge test-rerere/A-分支 --no-ff -m "Merge A into master"

# 切回B 分支,接著把master merge 回B 分支
git checkout test-rerere/B-分支
git merge --no-ff test-rerere/master

#衝突! 直接看 git diff
git diff

想必大家也猜到了

咱偉大的git rerere 

 

已經全部記起來啦!!!

 

所以我們依舊只要解決最新的那file-3.txt 造成的衝突即可

 

直接上code

# 解決衝突
echo "file-3 我還是要B" > file-3.txt
git add file-3.txt
git add file-1.txt # 別忘記把file-1 加進來囉
git add file-2.txt # 當然也別忘記file-2
git commit -m "Merge master into B"

# 來看看log 
git log --graph --oneline

不過..

如果我們不在意線的樣子、

就這樣直接把B 分支合回主線會發生什麼事?

這張圖並沒有符合其梗圖格式

# 切回主分支
git checkout test-rerere/master
# 把B 合併進master
git merge test-rerere/B-分支 --no-ff --no-commit
git commit -m "Merge B into master"

# 查看log
git log --graph --oneline

那條該死的線交叉了

我不要,讓我們回到上一步.. 不

上兩步好了!

讓master 回到合併B 之前、

B 則是回到合併 master 之前

直接上code!

# 讓master 往回走
git reset --hard head^
# 讓B 往回走
git checkout test-rerere/B-分支
git reset --hard head^

# 看看 log
git log --graph --oneline --all

master 是master

B 是B

那麼,我們來merge 吧!

git checkout test-rerere/master
git merge test-rerere/B-分支 --no-ff

# 衝突! 看看git diff
git diff

全部,都記起來啦

所以我們只要全部加一加就好囉

# 解衝突
git add file-1.txt
git add file-2.txt
git add file-3.txt
git commit -m "Merge B into master"

# 查看log
git log --graph --oneline

完美

這就是git rerere 的功能

git rerere 是一個設定項目,預設是false

他會記錄起發生在此分支上的所有衝突

並且在再次遇到的時候顯示出來供選擇

 

由此,可以在同步master 功能的同時

達到不讓線有交叉、

讓後續log 觀看較為適宜的好處

 

但也因此,如果開發時間太長的分支

在合併的時候很可能忘記之前是怎麼解的

造成錯過二次檢驗衝突的機會

 

在使用上需多斟酌

別急,還沒結束

 

讓我們回到上一步、

看看如果沒有設定git rerere 為true 的話

在最後一步會是什麼情況

# 回到還沒merge 的上一步
git reset --hard head^
# 檢查一下log 
git log --graph --oneline --all

master 和B 已經確實分開了

讓我們把git rerere 設定回false

# 修改回false
git config --global rerere.enabled false

一樣檢查一下 ~/.gitconfig

就來把B 合併到master 吧!

# 把B 合併進master
git merge test-rerere/B-分支 --no-ff

# 衝突! 看看差異
git diff

可以看到之前解過的衝突再次出現了..

除非有工人智慧去記住

不然全部都得重解一次

 

好啦,這下真的結束git rerere

如果忘記了

直接下 git rerere --help

就可以看到圖示範例囉

 

 

限制:

git version ^2.5

git reflog

Reference + logs = reflog

我不小心把遠端的分支和自己本地的分支全砍光後,

PM說剛剛的單還是上好了

https://img.moegirl.org/common/thumb/4/43/Monster_Reborn.jpg/250px-Monster_Reborn.jpg

PM 說我們要開發一個新功能,這個功能有點複雜、牽涉的功能很多,改動的檔案多到讓人想提早下班。你接到案子之後立刻把手邊其他的案子全都停掉,開始全心全意投入這個案子。在開發的過程一共新增了150張圖庫的圖片、還有異動到連同html 檔案也算進來的共76支檔案、230個以上的commit 節點,途中還有開發小需求線、做自己本地端的合併與統整。就在你以為一切都可以上線的時候,PM 說他要修改需求,像是把150 張圖片的邏輯全部抽走之類的。由於不想用rebase 或是reset 等比較暴力的方式修改先前提交的commit ,你決定用往後繼續開發的方式開發修改的功能,洋洋灑灑又多了437個commit,還有很多很多的新舊檔案刪除與異動,最後的最後,PM 走到你的身後,他開口...

 

客戶決定不上了

RD 就憤而把分支全砍了

本地的、遠端的

全砍了

然而PM 兩個小時後,又來到了RD的身後

欸客戶說還是上好了

.............................

.................................................................

不要緊張不要害怕

git reflog 來拯救

為了測試,我們把我們剛剛的測試分支

全都砍掉!

# 切回真正的master
git checkout maseter
# 確認現在有什麼分支可以砍
git branch --all

# 全部砍掉
git branch -D test-bisect/master
git branch -D test-rerere/A-分支
git branch -D test-rerere/B-分支
git branch -D test-rerere/master
git branch -D test-worktree/master
git branch -D 真紅眼黑龍
git branch -D 黑魔導

# 然後再確認一次分支
git branch --all

接著,就直接輸入 git reflog 看看結果吧!

# 直接輸入git reflog
git reflog

天殺的這什麼鬼

不過仔細看就可以發現, 這是我們之前對commit 的所有操作記錄!

只要挑選到正確的hash key...

# 切換到hash key
git checkout b6dc7ed # 後面的這串hash 每個人都會不一樣

# 查看 log
git log --graph --oneline --all

長回來啦

透過這種方式

就可以讓已經不存在本地或遠端的commit 成功復原

 

git reflog 看起來雖然是相當齊全的記錄檔

但其實有相當多的限制,像是

 

1. 他只會紀錄這台機器的操作記錄

2. 延續1, 所以其他機器無法復原被刪除的commit

3. 有點亂

4. 不會紀錄沒有commit 的操作記錄

5. 超級亂

6. 非常亂

 

差不多是這樣

QA

THX

參考資料

你可能不知道的git 指令 (二)

By flyc

你可能不知道的git 指令 (二)

  • 169