Git + GitHub 版本控制教學 (4) - 回復到之前的版本:checkout、reset、revert
既然我們在 git 裡面獲得了存檔(commit)的功能,就表示 git 也提供了讓我們可以回到過去的能力。使用 reset、revert、checkout 可以讓我們回到過去的版本,本篇文章會介紹這些指令的差異以及使用的時機。
既然我們在 git 裡面獲得了存檔(commit)的功能,就表示 git 也提供了讓我們可以回到過去的能力。使用 reset、revert、checkout 可以讓我們回到過去的版本,本篇文章會介紹這些指令的差異以及使用的時機。
HEAD 以及 commit id
在開發到一個段落以後,累積了一定的 commit 量。我們需要類似指標的東西讓我們得以分辨每一個版本。最直覺的就是版本編號: commit id,每個版本都會有它獨特的 commit id,長得會類似這樣:
6be9cb5e8d1ed532400ec6a2daa00e83357a96bf
幾乎在任何情況下我們都不會使用完整的 commit id,我們只需要使用最前面幾個字表示 git 就能自動判別。上面的例子來說:縮短版可以是 6be9cb5
、6be9cb5e
、6be9cb5e8
等。
HEAD 是一個指標,告訴我們現在是在整個 git 歷史的哪個位置、哪個分支。 HEAD 會指到你當前分支最新的那個 commit。但是你也可以讓 HEAD 指到任何一個 commit,這種情況下我們稱它為 detached HEAD。
我們可以在 project 底下查看 .git/HEAD
來得知我們目前的 HEAD 在哪裏:
# 在cmd上下cat指令:
cat .git/HEAD
# 結果有可能如下:
# 顯示你正在 master 這個 branch
ref: refs/heads/master
# 顯示你正在 feature1 這個 branch
ref: refs/heads/feature1
# 顯示你正在 feature1 這個 branch
ref: refs/heads/feature1
# 顯示你正在以下這個 commit (detached HEAD)
6be9cb5e8d1ed532400ec6a2daa00e83357a96bf
git checkout
了解了 HEAD 以後, checkout
的概念就變得非常簡單。使用 checkout
其實就是移動 HEAD 的位置。
# 切換到 master 這個 branch
# HEAD 指到 master branch 最新的 commit
git checkout master
# 切換到 feature 這個 branch
# HEAD 指到 feature branch 最新的 commit
git checkout feature
# 切換到 commit 6be9cb5e8d1ed532400ec6a2daa00e83357a96bf
# HEAD 只到這個 commit (detached HEAD)
git checkout 6be9cb5
因為 checkout
做的事情只是移動 HEAD,所以並不會對既有的 commit 作出任何修改,可以把它想像成“查看”的動作。如上圖指令所示,你可以用 checkout
切換你現在正在的 branch,也可以切換到任一個 commit。 detached HEAD 有點像是暫時的 branch,離開時會被刪除。
![](https://june.monster/content/images/2021/08/Screen-Shot-2021-08-14-at-1.53.12-AM.png)
git reset
reset
一樣可以回復到之前的版本,但它不像 checkout
會開一個暫時的 detached HEAD,它會實際把當下的 branch 版本退回到你要回到的地方(移動 HEAD)。通常使用 reset 的情境是:我最近的一些程式改動突然某些原因不想要了,但我已經提交了幾個 commit,這時候就可以使用 reset
回到這些 commit 前的版本。
# 可以用 ~n 表示回到HEAD的前n個版本
git reset HEAD~1
git reset HEAD~2
...
# HEAD^ 也意指HEAD的前一個版本
git reset HEAD^
# 可以直接使用 commit id
git reset 6be9cb5
用了 reset
以後會察覺 code 沒有並沒有變。這是因為 git 預設會做 git reset --mixed xxx
。雖然 commit 回到過去的版本,但版本之後的這些改動並不會消失,而是回到了 working directory (working directory 參考 https://june.monster/git-commit/)。我認為這是一種保護機制,可以先讓你看看兩個版本的差異。
![](https://june.monster/content/images/2021/08/Screen-Shot-2021-08-14-at-1.50.51-AM.png)
如果要真正的回到過去的某個 commit ,可以使用 git reset --hard xxx
。 --hard
不會把程式改動再放回 working directory。
git revert
revert
跟另外兩個指令又有不同,它回復版本的方式並不是去移動 HEAD。它會幫我們製造一個新的 commit,這個 commit 會把我們要 revert 的 commit 的內容給逆向操作一遍。
# 把目前所在的 branch 裡最新的 commit 給逆向操作一遍
# 做完後會發現多一個 commit,裡面內容是復原最新 commit 的改動
git revert HEAD
# 新增一個 commit,內容是反向去復原 6be9cb5 做的改動
git revert 6be9cb5
![](https://june.monster/content/images/2021/08/Screen-Shot-2021-08-14-at-1.56.53-AM.png)
上面例子可以看到:原本最新的 commit 是 bb806ee (message: test commit), revert
後 git 自動幫我們加上 b0fa88c (message: Revert "test commit") 這個新的 commit。
如果是個人 project 的話,用 revert
其實有些 over kill。我們可以用 reset
讓 commit history 看起來更乾淨。使用 revert
的時機是當我們跟他人合作時,我們會共同對遠端的一個 repo (可能是 Github、Gitlab...) 做貢獻。一但你的 commit 已經推到了遠端後再用 reset
,會改變原本 branch 的歷史。 這時候用 revert
就是很好的選擇。
我們最常用到的地方會是 Production 的 rollback。當團隊裡有人的程式改動造成嚴重 bug 而且沒法立刻找出原因的話,我們就會把這個有問題的 commit 給 revert 而達到 rollback 的效果。