跳轉到內容

Git 版本回退 恢復歷史版本 Git 回退操作

Git Version Rollback

在開發過程中,我們經常會遇到需要回退到之前某個版本的情況:可能是引入了嚴重的 Bug,可能是提交了敏感信息,或者只是想撤銷最近的更改。Git 提供了強大的工具來幫助我們安全地完成這些操作。

Reset vs Revert 對比

命令特點適用場景是否改寫歷史安全性
reset該命令會強行覆蓋當前版本和要回退的版本之間的其他版本(不太建議)本地分支、未推送的提交✅ 是⚠️ 中風險
revert再當前版本的基礎上新增一個版本,不影響以前的代碼已推送到遠程的提交❌ 否✅ 安全

選擇指南

bash
# 使用 reset 的情況:
# ✓ 僅在本地分支操作
# ✓ 還沒有推送到遠程
# ✓ 團隊成員尚未基於這些提交工作

# 使用 revert 的情況:
# ✓ 已經推送到共享分支
# ✓ 多人協作的項目
# ✓ 需要保留完整的歷史記錄

Reset 的使用方法

git reset 是一個強大但危險的工具,它可以移動 HEAD 指針並改變分支狀態。

查看版本,確定要回退的時刻

sh
# 查看詳細日誌
git log

# 簡潔的單行顯示
git log --pretty=oneline

# 圖形化顯示
git log --graph --oneline --all

# 查看最近 10 條記錄
git log -n 10 --oneline

# 示例輸出:
# a1b2c3d (HEAD -> main) feat: Add payment gateway
# e4f5g6h fix: Resolve login bug
# i7j8k9l docs: Update API documentation
# m0n1o2p feat: Implement user authentication

Reset 的三種模式

1. Soft Reset(最溫和)

bash
# 語法
git reset --soft <commit-hash>

# 示例:回退到前一個提交
git reset --soft HEAD~1

# 或指定具體 commit
git reset --soft abc1234

效果:

  • ✅ 移動 HEAD 到目標提交
  • ✅ 保留所有更改在暫存區
  • ✅ 工作區文件不變

使用場景:

  • 合併多次提交為一個
  • 修改最後一次提交的內容
  • 重新組織提交

示例:

bash
# 當前狀態:3 個相關的小提交
# commit3: fix: typo
# commit2: fix: add semicolon  
# commit1: feat: add feature

# 合併為一個提交
git reset --soft HEAD~3
git commit -m "feat: Complete feature implementation"

2. Mixed Reset(默認模式)

bash
# 語法(--mixed 是默認值,可以省略)
git reset --mixed <commit-hash>
git reset <commit-hash>  # 等價

# 示例
git reset HEAD~1

效果:

  • ✅ 移動 HEAD 到目標提交
  • ✅ 清空暫存區
  • ✅ 保留工作區的文件修改

使用場景:

  • 取消 git add 操作
  • 重新選擇要提交的文件
  • 撤銷提交但保留修改

示例:

bash
# 誤提交了太多文件
git commit -m "WIP"

# 撤銷提交,文件回到未暫存狀態
git reset HEAD~1

# 現在可以選擇性地添加文件
git add important-file.js
git commit -m "feat: Add important feature"

3. Hard Reset(最危險)

bash
# 語法
git reset --hard <commit-hash>

# 示例
git reset --hard HEAD~1
git reset --hard abc1234

效果:

  • ✅ 移動 HEAD 到目標提交
  • ✅ 清空暫存區
  • 刪除工作區的所有修改(不可恢復!)

⚠️ 警告:

  • 此操作會永久丟失未提交的更改
  • 使用前務必確認不需要當前的修改
  • 建議先備份或使用 stash

使用場景:

  • 完全放棄最近的更改
  • 回到乾淨的已知狀態
  • 丟棄實驗性代碼

示例:

bash
# 實驗失敗,想完全回到之前的狀態
git reset --hard HEAD~1

# 或者回到特定的穩定版本
git reset --hard v1.0.0

回退操作

sh
# 基本回退語法
git reset --hard <目標版本>

# 實際示例
git reset --hard HEAD~1      # 回退 1 個提交
git reset --hard HEAD~3      # 回退 3 個提交
git reset --hard abc1234     # 回退到特定 commit
git reset --hard main~2      # 回退 main 分支 2 個提交

相對引用符號

bash
# HEAD 表示當前提交
HEAD       # 當前提交
HEAD~1     # 前 1 個提交
HEAD~2     # 前 2 個提交
HEAD^      # 等同於 HEAD~1
HEAD^^     # 等同於 HEAD~2

# 分支引用
main~1     # main 分支的前一個提交
feature~3  # feature 分支的前 3 個提交

# 標籤引用
v1.0~1     # v1.0 標籤的前一個提交

在回退成功後,又想回到回退之前的狀態,則需要使用指令查看歷史提交信息

這就是 Git 的後悔藥 —— reflog

sh
# 查看所有操作歷史
git reflog

# 示例輸出:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: feat: Add new feature
# i7j8k9l HEAD@{2}: commit: fix: Bug fix
# m0n1o2p HEAD@{3}: checkout: moving from feature to main

# 恢復到回退前的狀態
git reset --hard HEAD@{1}
# 或
git reset --hard e4f5g6h

Reflog 保留時間:

  • 默認保留 90 天
  • 可以通過配置修改:
    bash
    git config gc.reflogExpire 180.days.ago

強制提交到遠程

如果你已經在本地執行了 reset,並且需要將這個更改同步到遠程倉庫:

sh
# ⚠️ 警告:這會改寫遠程歷史!
git push -f origin <branch name>

# 更安全的做法(推薦)
git push --force-with-lease origin <branch name>

--force-with-lease vs -f

  • -f--force):無條件強制推送,可能覆蓋他人的提交
  • --force-with-lease:檢查遠程是否有新提交,如果有則拒絕推送

團隊協作時的正確流程:

bash
# 1. 通知團隊成員你要重寫歷史
echo "注意:我將重置 main 分支,請大家暫停推送"

# 2. 執行重置
git reset --hard abc1234

# 3. 強制推送
git push --force-with-lease origin main

# 4. 通知團隊成員重新克隆或重置
echo "已完成重置,請大家執行:git fetch && git reset --hard origin/main"

Revert 的使用方法

git revert 是更安全的方式,它通過創建新的提交來撤銷之前的更改,不會改寫歷史。

基本用法

sh
# 查看提交歷史找到要回退的版本號
git log --oneline

# 回退單個提交
git revert <commit-hash>

# 示例
git revert abc1234

執行過程:

bash
$ git revert abc1234

# Git 會打開編輯器讓你確認提交信息
# 默認消息:Revert "Original commit message"
# 保存並關閉編輯器即可完成回退

# 自動創建一個新的提交
# [main b2c3d4e] Revert "feat: Add problematic feature"

回退多個提交

bash
# 回退連續的多個提交
git revert OLDER_COMMIT..NEWER_COMMIT

# 示例:回退最近 3 個提交
git revert HEAD~3..HEAD

# 或者指定範圍
git revert abc1234..def5678

注意: 範圍的寫法是 OLDER..NEWER,不包含 OLDER 本身。

不自動提交

bash
# 執行回退但不自動提交(用於手動調整)
git revert --no-commit abc1234

# 或者縮寫
git revert -n abc1234

# 現在可以編輯文件或添加更多更改
git add .
git commit -m "Revert and modify"

處理合併提交

bash
# 回退合併提交需要指定父分支
git revert -m 1 <merge-commit-hash>

# -m 1 表示保留第一個父分支(通常是當前分支)
# -m 2 表示保留第二個父分支(被合併的分支)

中止回退

bash
# 如果在 revert 過程中出現衝突,可以中止
git revert --abort

# 或者繼續(解決衝突後)
git revert --continue

TIP

這裡可能會出現衝突,那麼需要手動修改衝突的文件

然後就正常的提交流程就可以了,會生成一個新的版本在最新,不會影響到以前的版本

提交到遠程

由於 revert 創建了新的提交而不是改寫歷史,所以可以正常推送:

sh
# 正常推送即可
git push origin <branch name>

# 無需使用 -f 或 --force

實際應用場景

場景 1:撤銷最後一次提交

bash
# 方法 1:使用 reset(未推送時)
git reset --soft HEAD~1
# 或
git reset --hard HEAD~1  # 如果也想丟棄文件修改

# 方法 2:使用 amend(修改最後一次提交)
git add corrected-files
git commit --amend -m "Corrected commit message"

# 方法 3:使用 revert(已推送時)
git revert HEAD

場景 2:移除誤提交的敏感文件

bash
# 1. 從歷史中徹底刪除文件(使用 filter-repo)
git filter-repo --path password.txt --invert-paths --force

# 2. 強制推送
git push --force-with-lease origin main

# 3. 通知所有團隊成員重新克隆

場景 3:回退到某個標籤

bash
# 查看標籤
git tag -l

# 回退到標籤
git reset --hard v1.0.0

# 或者創建新分支
git checkout -b hotfix v1.0.0

場景 4:撤銷 merge

bash
# 方法 1:使用 reset(如果還沒推送)
git reset --hard HEAD~1

# 方法 2:使用 revert(如果已推送)
git revert -m 1 <merge-commit-hash>

場景 5:回到任意歷史狀態

bash
# 1. 找到目標 commit
git log --oneline

# 2. 查看該提交的快照
git show abc1234

# 3. 基於該提交創建新分支
git checkout -b restore-point abc1234

# 4. 或者直接重置
git reset --hard abc1234

高級技巧

1. 交互式重置

bash
# 逐步選擇要重置的文件
git reset -p HEAD~1

# Git 會逐個詢問每個變更塊是否要重置

2. 使用 stash 備份

bash
# 在重置前備份當前工作
git stash push -m "Backup before reset"

# 執行重置
git reset --hard HEAD~1

# 如果需要,可以恢復備份
git stash pop

3. 部分文件重置

bash
# 只重置特定文件到之前的版本
git checkout HEAD~1 -- file1.txt file2.txt

# 或者重置整個目錄
git checkout HEAD~2 -- src/

4. 比較後再重置

bash
# 先查看差異
git diff HEAD~1

# 確認無誤後再執行
git reset --hard HEAD~1

常見問題排查

問題 1:Reset 後無法推送

bash
# 錯誤:rejected non-fast-forward

# 解決方案 1:強制推送(謹慎)
git push --force-with-lease origin main

# 解決方案 2:先拉取再合併
git pull origin main
# 解決可能的衝突
git push origin main

問題 2:Revert 出現衝突

bash
# 1. 查看衝突文件
git status

# 2. 手動解決衝突
# 編輯文件,保留需要的內容

# 3. 標記解決
git add resolved-file.txt

# 4. 繼續 revert
git revert --continue

# 或者中止
git revert --abort

問題 3:找不到想要的提交

bash
# 使用 reflog 查找
git reflog

# 搜索特定的提交
git log --all --grep="keyword"
git log --all --author="name"

# 按文件查找
git log --follow -- filename.txt

問題 4:重置後工作區混亂

bash
# 清理未跟蹤的文件
git clean -fd

# 重置到乾淨狀態
git reset --hard HEAD

# 驗證狀態
git status

最佳實踐

1. 優先使用 Revert

bash
# ✅ 推薦:對已推送的提交使用 revert
git revert abc1234
git push origin main

# ❌ 避免:對共享分支使用 reset
git reset --hard abc1234
git push -f origin main

2. 重置前備份

bash
# 創建備份分支
git branch backup-before-reset

# 或使用 tag
git tag backup-$(date +%Y%m%d)

# 執行重置
git reset --hard HEAD~1

# 如果需要恢復
git reset --hard backup-before-reset

3. 團隊溝通

bash
# 在重置共享分支前通知團隊
# 在 Slack/Teams 等發送消息:
"⚠️ 我即將重置 main 分支到 abc1234,請大家暫停推送"

# 重置完成後通知:
"✅ 重置完成,請執行:git fetch && git reset --hard origin/main"

4. 使用保護分支

在 GitHub/GitLab 上啟用分支保護:

  • 禁止強制推送
  • 要求 Pull Request
  • 要求 CI 檢查通過

總結

Git 版本回退有兩種主要方式:

Reset(強力但危險)

  • ✅ 適合本地分支
  • ✅ 可以完全清除歷史
  • ❌ 會改寫提交歷史
  • ⚠️ 團隊協作時需格外小心

Revert(安全但冗長)

  • ✅ 適合共享分支
  • ✅ 保留完整歷史
  • ✅ 不會產生衝突
  • ⚠️ 會增加提交數量

選擇原則:

  1. 未推送到遠程 → 使用 reset
  2. 已推送到共享分支 → 使用 revert
  3. 不確定時 → 優先使用 revert
  4. 操作前 → 務必備份或創建標籤

下一步學習:

記住:Git 的強大之處在於它幾乎總能幫你找回丟失的代碼,但預防勝於治療,謹慎操作!🛡️

最後更新於: