2017年6月18日 星期日

[git]重新命名檔案的方式和如何在log檢查到改檔名前的歷史記錄

how to properly rename in git and show the log of previous rename

當持續在改檔案內容,容易發生當初為這個檔案取的名字已經不適合描述目前內容的情況,所以會對檔案名稱做修改。

最好的例子就是重構,當重構的時候很容易發生class名稱變換,這個時候會一起調整class的檔案檔名避免未來不好找。

不過當有天需要追蹤那個檔案的變更歷史的時候,會突然發現,只有 從新檔名那段開始有記錄,而舊檔名的記錄則看不到。這個對於從svn轉過來的使用者來說是無法理解的。

在這篇將會針對這個常見的問題來談到解決的方式和為什麼git會是這樣運作。

rename以前的log不見了
rename以前的log不見了

問題重現

基本上我們可以用以下的方式來重現這個問題:

  1. 先建立一個檔案 file.txt
  2. 把這個檔案commit上去
  3. 把檔案改名字change.txt
  4. 這個時候看狀態會變成 file.txt在被刪除狀態,而change.txt則是untrack的狀態
  5. add 加入到index (注意到這邊狀態變成了 renamed
  6. 這個時候用小烏龜對那個檔案做看 log - 會發現歷史不見了

建立的指令

建立的指令

log看不到建立出file.txt的記錄
log看不到建立出file.txt的記錄

解決方式

如果今天只是要看到包含改檔名前的日誌,下面是做法 - 如果對於為什麼會發生和git重命名的做法有興趣在往下看。

指令的方式

其實在git裡面針對某個檔案看日誌的時候,可以使用指令follow來包含變更名稱前的日誌:

git log -follow filex.txt

在各個ui裡面也有類似的設可以啟用:

TortoiseGit log 啟用方式

選取要檢查的檔案(以我們例子是change.txt),呼叫log的部分並且在最下面第3個按鈕 Walk Behaviour -> Follow Renames就可以看到。下面截圖就是有開啟的狀態,可以看到最早file.txt的建立也有看到了:

有follow參數的log
有follow參數的log

TortoiseGit blame啟用方式

如果對一個檔案做blame - 下面的log清單也需要開啟在 View -> Folle Rename才會看到

在blame日誌也包含rename
在blame日誌也包含rename

SourceTree log 啟用方式

在log裡面選change.txt,點右鍵,選擇 Log Selected,在左下角有個Follow renamed file

開啟單一檔案log開啟follow renamed files
source tree裡面啟用方式

發生原因 - git如何處理檔名變更

大部分第一次遇到這種情況都是在想,會不會是rename方式錯誤,是不是需要透過git的指令來rename就沒問題?

如果深入在研究,會發現git有提供一個指令叫做git mv(TortoiseGit也有一個對應的刪除功能)- 但是如果你用那個指令來刪除,會發現其實還是看不到日誌。

要了解這個,首先有件事情一定要記得的是,git和其他版控不同在於,git記錄的是內容變更,而不是檔案變更

所以其實git不在乎檔名有沒有變更,他看的是檔案內容的變更。所以在上面我們沒有透過git指令改名的時候,一開始在status顯示的是有個檔案被delete,然後有個檔案屬於untrack,但是當我們加到staging之後 (透過 git add)自動切換變成renamed狀態。

add之前顯示的狀態

add之前顯示的狀態

image
add之後變成rename

會有這種混搖是因為git觸發檢查的時間點不同,當status的時候還沒比對過資料,而在add等於進入準備建立commit,所以一比對,發現只是檔名不同(在git來說就是在不同的tree),因此它自動識別原來這是一個檔名變更。

這個時候有些gui就容易誤導使用者

以TortoiseGit來說,他沒有staging概念(應該說有,但是commit的那個畫面沒有這種感覺)會造成使用者擔心為什麼不是顯示重改檔名,造成他們不敢commit:

imagesourcetree commit畫面
(左圖)TortoiseGit的commit畫面 - 看起來和status未add之前看到的內容,(右圖)sourcetree預設有個stage畫面,因此看起來比較明顯

在git裡面重新命名怎麼做才正確

  • 當檔案內容沒有修改或者少量修改的時候,就依照喜歡的方式改檔名就好
  • 如果檔案內容有大幅度修改,建議改檔名和修改內容分兩個commit(一個改檔案內容,一個改檔名)
詳細說明如下:

有沒有必要呼叫 git mv

答案是:不需要

git mv = 改完檔名然後呼叫 git add

因為Windows使用者習慣直接透過檔案總管改檔名,因此專門為了省一個git add的指令而特意去呼叫git mv有點不方便,只是要知道顯示一個delete和一個untrack是正常即可。

當檔案內容和檔案名稱都有修改的情況下是否會不同?

假設檔案內容只改了一些 - git會夠聰明的知道,其實這是一個修改檔名+修改檔案內容。

但是當修改檔案內容很多也有修改檔名,git add還是會顯示一個delete和一個untrack

如果進入那種狀態,會有以下壞處:

  1. 未來在看commit的時候,不那麼直覺能夠看出是一個檔名修改+檔案內容修改
  2. -follow的參數就沒有用了 - 所以換檔名前的log沒法一次顯示

    當有檔案修改又有檔名修改無法用follow和網上說法有點不同

    以我解git是記錄檔案內容變更,理論上來說就算變成 一個delete一個untrack也應該要能夠用follow追蹤的到才對,但是實際上我測試的時候不行。

    這個和網路上的一些說法不一致,因此為了未來看起來方便和容易追蹤,建議還是分兩個commit,一個改檔名,一個改檔案內容。

    假設commit已經是一個delete和一個untrack,可以用rebase把它拆開成為兩個commit,一個改檔名,一個改檔案內容。


結語

在第一次接觸到git的rename的時候容易進入到以前以檔案修改為記錄的版控思維影響,因此希望這篇能夠讓了解發生的原因,並且記得:

  1. 如果logblame需要看到包含改檔名前的記錄,記得找找和follow有關的關鍵字做切換
  2. 改檔名不用擔心,可以直接用檔案總管修改然後add、commit即可
  3. 如果檔案有大改,然後又要改檔名,建議分成兩個commit

參考資料

  1. How to stage a rename without subsequent edits in git?
標籤:

2 則留言 :

  1. 對剛接觸的新手實用性很大阿,之前就有遇過別的組員說要整理一下名稱後,就找不到自己檔案的情況呢

    回覆刪除
  2. 很高興對您有幫助,持續關注,最近還會在放一些git相關內容。

    回覆刪除