[2020-07-28 更正] 本篇提到使用 git mv 確保 git status 狀態為 renamed 的技巧非絕對必要性,即使狀態為 deleted + added,在 Commit 後 Git 也會自動判別成 renamed,詳情請見 冷知識 - Git 的搬檔更名跟你想的不一樣

隨著專案規模成長,有時會需要重整原始碼資料夾結構,例如:開子資料夾把根目錄下的東西搬進去,再新增其他平行地位的資料夾。在 Windows 下搬檔案很簡單,檔案總管剪下貼上或是拖拉一下就好,小孩子都會。但是程式碼搬家,重要的是得保留修改歷程,否則每換一次目錄就宛如新生,誰受得了。

用個實例示範,假設專案結構如下:

我想將 D:\GitMove\src 下的 CRUDExample、TestProject 資料夾以及 ExampleForDummy.sln 搬到 D:\GitMove 這一層。

如果不多想,從檔案總管從 D:\GitMove\src 剪下,再貼到 D:\GitMove,檔案是搬好了,但記錄上是一刪一增,搬到 D:\GitMove 下的檔案被視為全新建立,修改歷程則隨著被視為刪除的舊路徑檔案消失。(註:理論上 Git 具備偵測檔案搬移或更名的能力,但據說在 Windows 上不太靈光,依我的使用經驗,跟六脈神劍一樣時有時無,超不可靠)

因此,正確做法是使用 git mv 原目錄或檔案 新資料夾位置 指令搬檔。實際用過幾次,也摸索一陣子,我找不出用萬用字元一次搬走所有資料夾及檔案的方法(有人說用 Git Bash 可以,但我沒試出來),但指定單一資料夾或檔名是 OK 的:

如此,Git 便會將搬移動作視為更名(Rename),明確掌握檔案來歷,修改歷程就能延續。

性急如王藍田,我當然沒法接受沒有萬用字元一個一個項目手敲,原本想用 ForFiles 跑迴圈處理,但遇上奇怪的 Bug - Git 無法正確解析路徑參數,mv 前方甚至得打兩個空格不然識別不出來:(有人知道這是什麼原因嗎?)

放棄 ForFiles 改用 PowerShell Get-ChildItem src | ForEach-Object { & git mv "src/$($_.Name)" . },成功!

Tips of moving source code under Git repository in Windows.


Comments

# by Heresy

有沒有考慮用 TortoiseGit?有很多操作會方便很多。

# by Danny Lin

Git 內部完全不記錄 rename,所以用 git mv 和移動檔案後再 git add 新增與刪除的檔案/資料夾,效果完全沒有差別。 然而,Git 在版本比對時會根據檔案內容的相似度做更名偵測(git diff、git log 等指令都有 -M 參數可以設定更名偵測的門檻),當檔案 100% 相同時,由於 SHA 值完全相等,Git 一定能正確辨識檔案更名,除了一種例外:若檔案 A 移動到 B,又複製了一個完全相同的 C,Git 就無法確定是 A=>B 或 A=>C。 也就是說,要確保 Git 正確辨識檔案更名,只要讓一個 commit 只含有檔案更名,不要包含其他修改(修改被更名檔案內容、加入與被更名檔案完全相同的檔案)即可。

Post a comment