救回刪除的檔案
有時候會手滑不小心刪除了某些檔案或資料夾,
或者刪除後才發現還是留下來比較好,這些狀況就是我們為什麼要用版控軟體的其中一個原因。
還記得我們之前在刪除檔案後使用退版的方式回到檔案還沒被刪的狀態吧。
但如果刪除了還沒提交到儲存庫的話,是有辦法可以直接把他們救回來的。
我們先來把所有附檔名是 txt 的檔案全部刪掉
這邊再提醒一下,使用檔案管理刪檔案也是一樣的。
# 看一下目前工作目錄有什麼檔案
$ ls
hello.txt log.txt look look.txt test.py
# 刪掉所有附檔名為 txt 的檔案
$ rm *.txt
# 確認一下都刪掉了
$ ls
look test.py
# 看一下狀態
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: hello.txt
deleted: log.txt
deleted: look.txt
no changes added to commit (use "git add" and/or "git commit -a")
看得出來有三個檔案被刪了。
使用 checkout
可以救回檔案,我們救回 hello.txt
$ git checkout hello.txt
Updated 1 path from the index
$ ls
hello.txt look test.py
跟 add 一樣,可以使用 .
就可以救回所有的檔案,範圍限於還沒提交的這段期間,
也就是我們看狀態時看到的那些被標紀刪除的檔案。
$ git checkout .
Updated 2 paths from the index
$ ls
hello.txt log.txt look look.txt test.py
如果是被修改過的檔案後悔了,也是可以這樣做,他會回復到最近一次 commit 的狀態。
所以,如果還沒提交前,有檔案被刪或被修改,使用 .
的話會把那些異動的檔案全部變回之前提交的狀態,
就像什麼事都沒做一樣。
我們經常提醒大家要看紀錄或狀態,就是避免不知道目前的狀態或誤解指令的意思而弄錯,
當然有 git 紀錄在原則上都可以整救,只是複不複雜的問題而已。
原理就是 git 會到暫存區去把檔案複製一份回來蓋掉現有工作目錄下的檔案,同時暫存區的紀錄也會改變。
git checkout HEAD~2 hello.txt
就是讓 hello.txt 內容回到兩個 commit 前的狀態。
那如果刪除或更改的狀況又已經提交了該怎麼救回來呢?請看下篇。
回到過去
相對與絕對
操作的過程中誰不會犯錯,人生無法重來,但 git 可以讓我們回到過去的時間點。
我們列出目前的紀錄,然後使用 reset
回到之前的提交狀態中。
$ git log --oneline git reset 7cce3b7 (HEAD -> master) 刪除 look.txt
6bd2c8c 產生了3個檔案
b7df611 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
$ git reset 7cce3b7~
Unstaged changes after reset:
D look.txt
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: look.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git log --oneline
6bd2c8c (HEAD -> master) 產生了3個檔案
b7df611 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
$ ls
hello.txt log.txt look test.py
上述指令,在 reset 後面是我們目前 HEAD -> master
所在的版本號,
加上一個 ~
就表示退到這個版本的前一個版本。
所以 ~4
就是往前退四個版本。
從上面可以看出目前所在的版號已經變更,且原來最新的版號不見了。
那因為我們調整的是 HEAD -> master
所以不想先查版本號的話也可以使用以下指令替代:
- git reset master~
- git reset HEAD~
以上的指令都是回到前一個版本的意思。
上述方式是以相對這個版本的角度出發下的指令。
還有絕對一點的方式。
也就是可以直接指定版本號來跳到該版本,不然一直算有幾個也太累了。
$ git reset 7cce3b7
$ git log --oneline
7cce3b7 (HEAD -> master) 刪除 look.txt
6bd2c8c 產生了3個檔案
b7df611 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
又回到之前提交的版本中了。
三種模式
接著我們探討一下那些被丟掉的 commit 跑哪裡去了。
事實上 reset 提供三種模式,分別為:
- --mixed (預設)
- --soft
- --hard
每種模式對於拆掉的 commit 檔案處理方式不同:
--mixed | --soft | --hard | |
---|---|---|---|
拆掉的 commit | 丟回工作目錄 | 丟到暫存區 | 直接丟棄 |
reset 的意義
reset 指令比較像是前往或移到某個版本,並不是我們習慣理解成重設的意思。
既然是前往或移動,所以隨時可以前往任何一個版本,也因此不見的 commit 依然存在。
不會因為我們 reset 到某個版本造成原本的 commit 不見。
所以就算使用 --hard
參數,一然可以再後悔回到其他版本。
只要知道版本號都可以隨時切換,就算 commit 紀錄因為我們切換到舊版而隱藏也沒差。
後悔可以無限次
那問題來了,既然那些 commit 紀錄隱藏了,那該怎麼查版本號呢?需要使用 reflog
指令列出完整的 log 紀錄:
$ git log --oneline
b7df611 (HEAD -> master) 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
目前所在的版本會發現很多比這個版本更新的 commit 紀錄都隱藏了:
$ git reflog
b7df611 (HEAD -> master) HEAD@{0}: reset: moving to b7df611
7cce3b7 HEAD@{1}: reset: moving to HEAD~
b009ebb HEAD@{2}: commit: 刪除 look
7cce3b7 HEAD@{3}: reset: moving to 7cce3b7
b7df611 (HEAD -> master) HEAD@{4}: reset: moving to HEAD^
6bd2c8c HEAD@{5}: reset: moving to 7cce3b7^
6bd2c8c HEAD@{6}: reset: moving to 7cce3b7~
7cce3b7 HEAD@{7}: commit: 刪除 look.txt
6bd2c8c HEAD@{8}: commit (amend): 產生了3個檔案
74de22f HEAD@{9}: commit: 產生了 look 與 look.txt 檔案
b7df611 (HEAD -> master) HEAD@{10}: commit: 在 hello.txt 最後加了一行字
02d832e HEAD@{11}: commit: 加上 .gitignore
d321f73 HEAD@{12}: commit (amend): 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
25c6264 HEAD@{13}: commit (amend): 將 welcome.txt 更名為 hello.txt
9f2f6dd HEAD@{14}: commit (amend): 將 welcome.txt 更名為 hello.txt
93bb4ec HEAD@{15}: commit: 特種兵要我改檔名,讓我很不爽,明明不懂還裝懂
462682d HEAD@{16}: reset: moving to HEAD~
c708443 HEAD@{17}: commit: 刪除 welcome.txt
462682d HEAD@{18}: commit: 沒東西
0fdb52f HEAD@{19}: commit (initial): 加入git並新增welcome.txt檔案
# 都出來囉
$ git reset --hard 7cce3b7
HEAD is now at 7cce3b7 刪除 look.txt
$ git status
On branch master
nothing to commit, working tree clean
$ git log --oneline
7cce3b7 (HEAD -> master) 刪除 look.txt
6bd2c8c 產生了3個檔案
b7df611 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
$ ls
hello.txt log.txt look test.py
$ git reset --hard b7df611
HEAD is now at b7df611 在 hello.txt 最後加了一行字
$ git log --reflog --oneline
b009ebb 刪除 look
7cce3b7 刪除 look.txt
6bd2c8c 產生了3個檔案
74de22f 產生了 look 與 look.txt 檔案
b7df611 (HEAD -> master) 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
25c6264 將 welcome.txt 更名為 hello.txt
9f2f6dd 將 welcome.txt 更名為 hello.txt
93bb4ec 特種兵要我改檔名,讓我很不爽,明明不懂還裝懂
c708443 刪除 welcome.txt
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
$ git reset --hard b009ebb
HEAD is now at b009ebb 刪除 look
$ git log --oneline
b009ebb (HEAD -> master) 刪除 look
7cce3b7 刪除 look.txt
6bd2c8c 產生了3個檔案
b7df611 在 hello.txt 最後加了一行字
02d832e 加上 .gitignore
d321f73 將 welcome.txt 更名為 hello.txt 並加入 test.py 檔案
462682d 沒東西
0fdb52f 加入git並新增welcome.txt檔案
$ git status
On branch master
nothing to commit, working tree clean
使用 --hard
會強迫放棄之前 reset 回來之後所做的未提交的修改。
在 reflog
中包括 HEAD 的移動也會紀錄下來。
經過幾次的切來切去,我們又回到了最新的提交中,證明使用 --hard
來切換也不會丟失 commit 紀錄。
其實使用 git log -g
也有 reflog
的效果。
最後更新:2020-03-26 23:20:24
From: 122.116.71.150
By: 阿慶