Skip to content

Git Undo

在使用 Git 的过程中,难免会遇到需要撤销操作的情况。本章将详细介绍各种撤销和恢复操作,帮助你安全地处理各种"后悔"的场景。

撤销的基本概念

Git 的三个区域

理解撤销操作前,需要明确 Git 的三个主要区域:

工作区 (Working Directory)
    ↓ git add
暂存区 (Staging Area)
    ↓ git commit
版本库 (Repository)

不同区域的撤销操作方法不同,影响范围也不同。

撤销操作的安全性

  • 🟢 安全操作:不会丢失数据,可以恢复
  • 🟡 谨慎操作:可能丢失未提交的更改
  • 🔴 危险操作:会永久丢失数据,需要特别小心

工作区的撤销

撤销文件修改

bash
# 撤销单个文件的修改 🟢
git checkout -- filename.txt

# 撤销多个文件的修改 🟢
git checkout -- file1.txt file2.txt

# 撤销所有文件的修改 🟡
git checkout -- .

# 使用新语法(Git 2.23+)🟢
git restore filename.txt
git restore .

演示撤销文件修改

bash
# 创建演示环境
mkdir git-undo-demo
cd git-undo-demo
git init

# 创建初始文件
echo "原始内容" > file.txt
git add file.txt
git commit -m "初始提交"

# 修改文件
echo "修改后的内容" > file.txt
echo "新增内容" >> file.txt

# 查看修改
git diff

# 撤销修改
git checkout -- file.txt

# 验证撤销结果
cat file.txt  # 应该显示"原始内容"

删除未跟踪的文件

bash
# 查看未跟踪的文件
git clean -n

# 删除未跟踪的文件 🟡
git clean -f

# 删除未跟踪的文件和目录 🟡
git clean -fd

# 交互式删除 🟢
git clean -i

# 删除被忽略的文件 🟡
git clean -fX

# 删除所有未跟踪的文件(包括被忽略的)🔴
git clean -fx

暂存区的撤销

撤销暂存的文件

bash
# 撤销单个文件的暂存 🟢
git reset HEAD filename.txt

# 撤销所有文件的暂存 🟢
git reset HEAD

# 使用新语法(Git 2.23+)🟢
git restore --staged filename.txt
git restore --staged .

演示撤销暂存

bash
# 修改文件并添加到暂存区
echo "新的修改" >> file.txt
git add file.txt

# 查看状态
git status

# 撤销暂存
git reset HEAD file.txt

# 查看状态(文件仍然被修改,但不在暂存区)
git status

部分撤销暂存

bash
# 交互式撤销部分暂存内容
git reset -p

# 或使用 restore
git restore --staged -p filename.txt

提交的撤销

修改最后一次提交

bash
# 修改最后一次提交的信息 🟢
git commit --amend

# 修改提交信息并保持文件不变 🟢
git commit --amend -m "新的提交信息"

# 添加文件到最后一次提交 🟢
git add forgotten_file.txt
git commit --amend --no-edit

演示修改提交

bash
# 创建提交
echo "功能A" > feature_a.txt
git add feature_a.txt
git commit -m "添加功能A"

# 发现遗漏了文件
echo "功能A的配置" > feature_a_config.txt
git add feature_a_config.txt

# 修改最后一次提交
git commit --amend -m "添加功能A及其配置"

# 查看历史
git log --oneline

撤销提交但保留更改

bash
# 撤销最后一次提交,保留更改在工作区 🟢
git reset --soft HEAD~1

# 撤销最后一次提交,保留更改在暂存区 🟢
git reset --mixed HEAD~1
# 或简写为
git reset HEAD~1

# 撤销最后一次提交,丢弃所有更改 🔴
git reset --hard HEAD~1

撤销多个提交

bash
# 撤销最近3次提交 🟡
git reset --soft HEAD~3

# 撤销到特定提交 🟡
git reset --soft commit_hash

# 查看可撤销的提交
git log --oneline -10

使用 revert 安全撤销

revert vs reset

  • reset:移动分支指针,改写历史 🟡
  • revert:创建新提交来撤销更改,保留历史 🟢
bash
# 撤销特定提交(推荐用于已推送的提交)🟢
git revert commit_hash

# 撤销最后一次提交 🟢
git revert HEAD

# 撤销合并提交 🟢
git revert -m 1 merge_commit_hash

演示 revert 操作

bash
# 创建几个提交
echo "功能1" > feature1.txt
git add feature1.txt
git commit -m "添加功能1"

echo "功能2" > feature2.txt
git add feature2.txt
git commit -m "添加功能2"

echo "功能3" > feature3.txt
git add feature3.txt
git commit -m "添加功能3"

# 撤销功能2的提交
git log --oneline  # 找到功能2的提交哈希
git revert <功能2的提交哈>

# 查看结果
git log --oneline
ls  # 功能2的文件应该被删除了

批量 revert

bash
# 撤销一系列提交
git revert commit1..commit3

# 撤销多个不连续的提交
git revert commit1 commit2 commit4

# 只创建撤销更改,不自动提交
git revert --no-commit commit_hash

恢复删除的文件

恢复已删除但未提交的文件

bash
# 恢复被删除的文件 🟢
git checkout HEAD -- deleted_file.txt

# 或使用 restore
git restore deleted_file.txt

恢复已提交删除的文件

bash
# 查找文件被删除的提交
git log --oneline --follow -- deleted_file.txt

# 从删除前的提交恢复文件 🟢
git checkout commit_hash~1 -- deleted_file.txt

# 提交恢复的文件
git add deleted_file.txt
git commit -m "恢复被删除的文件"

演示文件恢复

bash
# 创建并提交文件
echo "重要数据" > important.txt
git add important.txt
git commit -m "添加重要文件"

# 删除文件并提交
git rm important.txt
git commit -m "删除重要文件"

# 发现需要恢复文件
git log --oneline

# 恢复文件
git checkout HEAD~1 -- important.txt
git add important.txt
git commit -m "恢复重要文件"

恢复丢失的提交

使用 reflog 查找丢失的提交

bash
# 查看引用日志 🟢
git reflog

# 查看特定分支的 reflog
git reflog show branch_name

# 查看详细的 reflog
git reflog --all --graph --decorate --oneline

恢复丢失的提交

bash
# 从 reflog 恢复提交 🟢
git checkout commit_hash
git branch recovered-commits

# 或者直接重置到丢失的提交
git reset --hard commit_hash

演示提交恢复

bash
# 创建一些提交
echo "提交1" > commit1.txt
git add commit1.txt
git commit -m "提交1"

echo "提交2" > commit2.txt
git add commit2.txt
git commit -m "提交2"

# 记录当前提交哈希
current_commit=$(git rev-parse HEAD)

# 意外重置
git reset --hard HEAD~2

# 查看 reflog 找回提交
git reflog

# 恢复丢失的提交
git reset --hard $current_commit

分支的撤销和恢复

恢复删除的分支

bash
# 查找被删除分支的最后提交
git reflog | grep "branch_name"

# 恢复分支 🟢
git checkout -b recovered_branch commit_hash

撤销合并

bash
# 撤销最后一次合并(未推送)🟡
git reset --hard HEAD~1

# 撤销已推送的合并 🟢
git revert -m 1 merge_commit_hash

演示分支恢复

bash
# 创建并切换到新分支
git checkout -b feature-branch
echo "功能代码" > feature.txt
git add feature.txt
git commit -m "实现新功能"

# 记录分支的提交哈希
feature_commit=$(git rev-parse HEAD)

# 切换回主分支并删除功能分支
git checkout main
git branch -D feature-branch

# 恢复分支
git checkout -b recovered-feature $feature_commit

高级撤销技巧

交互式 rebase 撤销

bash
# 交互式修改历史 🟡
git rebase -i HEAD~3

# 在编辑器中可以:
# pick   -> 保留提交
# reword -> 修改提交信息
# edit   -> 修改提交内容
# squash -> 合并到前一个提交
# drop   -> 删除提交

使用 cherry-pick 选择性恢复

bash
# 选择特定提交应用到当前分支 🟢
git cherry-pick commit_hash

# 选择多个提交
git cherry-pick commit1 commit2 commit3

# 选择提交范围
git cherry-pick start_commit..end_commit

演示 cherry-pick

bash
# 在功能分支上创建提交
git checkout -b feature
echo "修复1" > fix1.txt
git add fix1.txt
git commit -m "修复bug1"

echo "修复2" > fix2.txt
git add fix2.txt
git commit -m "修复bug2"

# 切换到主分支,只应用修复1
git checkout main
git cherry-pick <修复1的提交哈>

撤销操作的脚本

安全撤销脚本

bash
#!/bin/bash
# 安全撤销脚本

safe_undo() {
    echo "=== Git 安全撤销工具 ==="
    echo "1. 撤销工作区修改"
    echo "2. 撤销暂存区修改"
    echo "3. 撤销最后一次提交(保留更改)"
    echo "4. 撤销最后一次提交(丢弃更改)"
    echo "5. 恢复删除的文件"
    echo "6. 查看 reflog"
    echo "0. 退出"
    
    read -p "请选择操作 (0-6): " choice
    
    case $choice in
        1)
            echo "撤销工作区修改..."
            git status
            read -p "确认撤销所有工作区修改? (y/N): " confirm
            if [[ $confirm =~ ^[Yy]$ ]]; then
                git checkout -- .
                echo "工作区修改已撤销"
            fi
            ;;
        2)
            echo "撤销暂存区修改..."
            git reset HEAD
            echo "暂存区修改已撤销"
            ;;
        3)
            echo "撤销最后一次提交(保留更改)..."
            git reset --soft HEAD~1
            echo "提交已撤销,更改保留在暂存区"
            ;;
        4)
            echo "撤销最后一次提交(丢弃更改)..."
            read -p "警告:这将永久丢失更改!确认? (y/N): " confirm
            if [[ $confirm =~ ^[Yy]$ ]]; then
                git reset --hard HEAD~1
                echo "提交和更改已完全撤销"
            fi
            ;;
        5)
            read -p "请输入要恢复的文件名: " filename
            if [ -n "$filename" ]; then
                git checkout HEAD -- "$filename"
                echo "文件 $filename 已恢复"
            fi
            ;;
        6)
            echo "显示 reflog..."
            git reflog --oneline -10
            ;;
        0)
            echo "退出"
            return 0
            ;;
        *)
            echo "无效选择"
            ;;
    esac
}

# 调用函数
safe_undo

提交恢复脚本

bash
#!/bin/bash
# 提交恢复脚本

recover_commit() {
    echo "=== 提交恢复工具 ==="
    
    # 显示 reflog
    echo "最近的操作历史:"
    git reflog --oneline -20
    
    echo ""
    read -p "请输入要恢复的提交哈希: " commit_hash
    
    if [ -z "$commit_hash" ]; then
        echo "错误:请提供提交哈希"
        return 1
    fi
    
    # 验证提交是否存在
    if ! git cat-file -e "$commit_hash" 2>/dev/null; then
        echo "错误:提交 $commit_hash 不存在"
        return 1
    fi
    
    echo "提交信息:"
    git show --stat "$commit_hash"
    
    echo ""
    echo "恢复选项:"
    echo "1. 创建新分支指向此提交"
    echo "2. 重置当前分支到此提交"
    echo "3. Cherry-pick 此提交到当前分支"
    echo "0. 取消"
    
    read -p "请选择 (0-3): " choice
    
    case $choice in
        1)
            read -p "请输入新分支名: " branch_name
            if [ -n "$branch_name" ]; then
                git checkout -b "$branch_name" "$commit_hash"
                echo "已创建分支 $branch_name 并切换到该分支"
            fi
            ;;
        2)
            read -p "警告:这将改变当前分支历史!确认? (y/N): " confirm
            if [[ $confirm =~ ^[Yy]$ ]]; then
                git reset --hard "$commit_hash"
                echo "当前分支已重置到 $commit_hash"
            fi
            ;;
        3)
            git cherry-pick "$commit_hash"
            echo "已将提交 $commit_hash 应用到当前分支"
            ;;
        0)
            echo "取消操作"
            ;;
        *)
            echo "无效选择"
            ;;
    esac
}

# 调用函数
recover_commit

撤销操作的最佳实践

1. 撤销前的检查

bash
# 检查当前状态
git status

# 查看未提交的更改
git diff
git diff --staged

# 查看最近的提交
git log --oneline -5

# 检查是否有未推送的提交
git log origin/main..HEAD

2. 选择合适的撤销方法

bash
# 工作区修改 -> checkout/restore
git checkout -- file.txt

# 暂存区修改 -> reset/restore --staged
git reset HEAD file.txt

# 本地提交 -> reset
git reset --soft HEAD~1

# 已推送提交 -> revert
git revert commit_hash

3. 备份重要更改

bash
# 创建备份分支
git branch backup-$(date +%Y%m%d-%H%M%S)

# 或者使用 stash
git stash push -m "备份当前更改"

# 导出补丁
git diff > backup.patch

4. 团队协作中的撤销

bash
# 已推送的提交使用 revert
git revert commit_hash
git push origin main

# 未推送的提交可以使用 reset
git reset --hard HEAD~1

# 强制推送需要团队同意(危险)
git push --force-with-lease origin main

故障排除

常见撤销问题

bash
# 问题1: reset 后找不到提交
# 解决: 使用 reflog 查找
git reflog
git reset --hard commit_hash

# 问题2: 撤销后出现冲突
# 解决: 手动解决冲突
git status
git mergetool
git commit

# 问题3: 误用 --hard 丢失更改
# 解决: 检查 reflog 和工作区备份
git reflog
git fsck --lost-found

# 问题4: revert 合并提交失败
# 解决: 指定正确的父提交
git revert -m 1 merge_commit_hash

数据恢复

bash
# 查找悬空的提交
git fsck --lost-found

# 查看悬空提交的内容
git show dangling_commit_hash

# 恢复悬空提交
git branch recovered dangling_commit_hash

总结

Git 撤销操作的核心要点:

撤销层级

工作区   -> git checkout/restore
暂存区   -> git reset/restore --staged  
版本库   -> git reset/revert

安全等级

  • 🟢 安全:checkout, restore, revert
  • 🟡 谨慎:reset --soft/mixed, clean
  • 🔴 危险:reset --hard, clean -fx

选择原则

  • 未推送:可以使用 reset 改写历史
  • 已推送:使用 revert 保持历史完整
  • 团队协作:优先使用安全的撤销方法

恢复策略

  • 📋 定期查看 reflog
  • 💾 重要操作前创建备份
  • 🔍 使用 fsck 查找丢失的数据
  • 🛡️ 配置自动备份和钩子

掌握撤销操作后,你就能够:

  • 🔄 安全地撤销各种错误操作
  • 💾 恢复意外丢失的数据
  • 🛠️ 灵活地修改项目历史
  • 👥 在团队协作中正确处理撤销

在下一章中,我们将学习 Git 的进阶操作技巧。

本站内容仅供学习和研究使用。