Git 取出特定時間點檔案的內容

最近在工作上,剛好碰到一個情境:我想要在不改變 HEAD 的情況下,取出之前某個時間點(也就是 commit)的檔案內容,應該要怎麼做?

通常直覺應該是直接 checkout 到那個時間點上,但這樣會改變 HEAD 變成斷頭(detached HEAD)狀態,而且當下我還有其他未 commit 但已經 add 的檔案,不想破壞目前的狀態避免開發的東西壞掉。

搜尋一下,馬上就找到一篇文章在討論,實際測試也真的有用。而且還發現了新東西,故寫了這篇文章做點小筆記。

解法

checkout

越早就開始使用 git 或看到比較早之前教學文的人,一定對 git checkout 這個指令相當熟悉。一如前言所提到的,git-checkout 就像是時光機一樣可以在 commit 之間穿梭,也能從 stage 中取出東西。而這次,這個指令將再次發揮他的萬能拯救我們:

1
git checkout <commit id> -- <file1/to/restore> <file2/to/restore>

<> 就是依照自己需求填入的部分。

restore

git-restore 版本 2.23.0 開始新加上的指令(git-switch 也是此次推出),我們都知道 checkout 非常萬能,但相對的他背負太多東西了,所以從 2.23.0 開始新增剛剛提及的兩個新指令分別取代部分 checkout 的功能(但為了向下相容,checkout 能可使用。故可以看到有些專案仍使用 checkout。)。

細節在此不多談,既然是要取代 checkout,那剛剛的功能也要能處理才對?沒錯,來講一下如果是 restore 該怎麼做到這件事:

1
git restore --source=<commit id> -- <file1/to/restore> <file2/to/restore>

指令中的 --source 旗標有縮寫,所以也可以寫成:

1
git restore -s <commit id> -- <file1/to/restore> <file2/to/restore>

神秘的--

看到這邊,我自己對於指令中的 -- 非常好奇,那個是做什麼用的?往下面的答案下的留言(沒有答案有解釋,都是在留言中找到的)發現有人提到:

With git, if you’re unsure, always prefix all files and directories with the special argument --.

-- is not a git command and not special to git. It is a bash built-in to signify the end of command options. You can use it with many other bash commands too.

No, -- is not a builtin special word in bash. But it is a common convention supported by many commandline parsers and used by many CLIs, including git.

嗯……就算用了翻譯也不是很明白意思,不過可以看出這個不是 bash 內建的語法。繼續看看其他留言:

The -- is not only a git convention, but something you find in various places in on the *nix commandline. rm -- -f (remove a file named -f) seems to be the canonical example.

到了這邊總算明白意思了,這個 -- 是分隔指令和參數用的,用意在告訴程式說「在 -- 之後的東西是我的參數」而且並非內建或是 git 獨有的,他是一種慣例的表達方式。所以這個表達方式有沒有用,要看該程式是否有支援。

參考資料