[檔案管理] git 教學:妙手回春--回到過去的版本 - 精華區

[檔案管理] git 教學:妙手回春--回到過去的版本

阿慶

阿慶圖像

2020-03-24 23:46:03

From:122.116.71.150

救回刪除的檔案

有時候會手滑不小心刪除了某些檔案或資料夾,

或者刪除後才發現還是留下來比較好,這些狀況就是我們為什麼要用版控軟體的其中一個原因。

還記得我們之前在刪除檔案後使用退版的方式回到檔案還沒被刪的狀態吧。

但如果刪除了還沒提交到儲存庫的話,是有辦法可以直接把他們救回來的。

我們先來把所有附檔名是 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: 阿慶