Skip to content
On this page

Git 重写历史

重写历史是Git的高级功能,允许修改提交历史、删除敏感信息或重新组织提交。本章详细介绍重写历史的方法和注意事项。

重写历史基础概念

什么是历史重写

历史重写是修改Git提交历史的过程,包括修改提交信息、更改提交内容、删除提交或重新排序提交等操作。

重写历史的风险

  • 破坏协作:如果历史已被推送到共享仓库,重写历史会影响其他开发者
  • 丢失数据:不当操作可能导致数据丢失
  • 分支分叉:重写历史后,本地分支与远程分支会产生分歧

修改最后一次提交

修改提交信息

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

# 修改最后一次提交的信息(使用编辑器)
git commit --amend

添加内容到最后一次提交

bash
# 修改最后一次提交,添加新文件
git add new-file.txt
git commit --amend

# 修改最后一次提交,替换文件内容
echo "new content" > file.txt
git add file.txt
git commit --amend

删除最后一次提交

bash
# 删除最后一次提交(保留更改)
git reset --soft HEAD~1

# 删除最后一次提交(丢弃更改)
git reset --hard HEAD~1

# 删除最后一次提交(保留文件在工作区)
git reset HEAD~1

交互式变基

基本交互式变基

bash
# 交互式变基最近3次提交
git rebase -i HEAD~3

# 交互式变基到指定提交
git rebase -i commit-hash

交互式变基操作

在交互式变基编辑器中,可以执行以下操作:

bash
# pick:保留提交
pick abc1234 Add new feature

# reword:修改提交信息
reword def5678 Fix bug

# edit:修改提交内容
edit ghi9012 Update documentation

# squash:合并到前一个提交
squash jkl3456 Minor changes

# fixup:合并到前一个提交(丢弃提交信息)
fixup mno7890 Code cleanup

# drop:删除提交
drop pqr1234 WIP commit

# exec:执行shell命令
exec npm test

实际操作示例

bash
# 1. 启动交互式变基
git rebase -i HEAD~4

# 2. 编辑器中修改内容:
# pick abc1234 Add user authentication
# squash def5678 Fix auth bug
# reword ghi9012 Update auth docs
# drop jkl3456 WIP commit

# 3. 保存并退出,Git会按顺序执行操作

删除敏感信息

从历史中删除文件

bash
# 从所有提交中删除文件
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch path/to/sensitive-file' \
--prune-empty --tag-name-filter cat -- --all

# 使用BFG工具(更高效)
# 1. 下载BFG Repo-Cleaner
# 2. 运行命令
java -jar bfg.jar --delete-files id_rsa my-repo.git

# 使用git filter-repo(推荐)
pip install git-filter-repo
git filter-repo --path path/to/sensitive-file --invert-paths

从历史中删除大文件

bash
# 查找大文件
git rev-list --objects --all | \
grep "$(git verify-pack -v .git/objects/pack/*.idx | \
sort -k 3 -n | tail -10 | awk '{print $1}')"

# 删除大文件
git filter-repo --path path/to/large-file --invert-paths

# 或使用filter-branch
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch path/to/large-file' \
--prune-empty --tag-name-filter cat -- --all

从提交信息中删除敏感信息

bash
# 使用git filter-repo重写提交信息
git filter-repo --replace-text replacements.txt

# replacements.txt内容:
# old-secret-token=new-token
# old-email@example.com=new-email@example.com

重新组织提交

合并多个提交

bash
# 交互式变基合并提交
git rebase -i HEAD~5

# 将要合并的提交改为squash或fixup
# pick abc1234 Add user model
# squash def5678 Add user validation
# squash ghi9012 Add user tests

拆分提交

bash
# 使用edit操作拆分提交
git rebase -i HEAD~1

# 将操作改为edit
# edit abc1234 Add feature

# Git会在该提交处暂停
# 重置该提交
git reset HEAD^

# 重新添加文件并创建多个提交
git add file1.txt
git commit -m "Add file1"

git add file2.txt
git commit -m "Add file2"

重新排序提交

bash
# 在交互式变基中重新排列提交顺序
git rebase -i HEAD~3

# 原顺序:
# pick abc1234 Fix bug
# pick def5678 Add feature
# pick ghi9012 Update docs

# 新顺序:
# pick def5678 Add feature
# pick abc1234 Fix bug
# pick ghi9012 Update docs

修复提交

修改特定提交

bash
# 交互式变基到目标提交之前
git rebase -i target-commit^

# 将目标提交改为edit
# edit target-commit-message

# 修改文件后继续变基
git add .
git commit --amend
git rebase --continue

添加文件到旧提交

bash
# 找到需要添加文件的提交
git log --oneline

# 交互式变基到该提交之前
git rebase -i problematic-commit^

# 将该提交改为edit
# edit problematic-commit-message

# 添加新文件
git add new-file.txt

# 修复提交
git commit --amend
git rebase --continue

重写分支历史

变基到其他分支

bash
# 将当前分支变基到main分支
git rebase main

# 处理可能的冲突
# 解决冲突后
git add .
git rebase --continue

# 如果需要取消
git rebase --abort

重新设置分支基底

bash
# 将分支重新基于不同的提交
git rebase --onto new-base old-base feature-branch

# 例如:将feature分支从错误的base重新基于main
git rebase --onto main old-wrong-base feature-branch

历史重写的后果处理

推送重写的历史

bash
# 强制推送重写的分支(危险操作)
git push --force-with-lease origin feature-branch

# 强制推送所有分支
git push --force-with-lease --all

# 强制推送标签
git push --force-with-lease --tags

协作中的历史重写

bash
# 如果历史已被推送,通知团队成员
# 团队成员需要重置他们的分支
git fetch origin
git reset --hard origin/branch-name

# 或者重新克隆仓库
git clone <repository-url>

高级历史重写技术

使用git filter-repo

bash
# 安装git filter-repo
pip install git-filter-repo

# 删除路径
git filter-repo --path path/to/remove --invert-paths

# 重命名路径
git filter-repo --path old-path --to-path new-path

# 替换内容
git filter-repo --replace-text replacements.txt

# 过滤作者
git filter-repo --mailmap mailmap.txt

使用git subtree

bash
# 提取子目录为独立仓库
git subtree push --prefix=path/to/subdir new-repo-remote branch-name

# 从其他仓库拉取到子目录
git subtree add --prefix=path/to/subdir remote-name branch-name

安全和最佳实践

备份历史

bash
# 在重写历史前创建备份分支
git branch backup-branch

# 或者创建备份标签
git tag backup-tag

# 验证备份
git log --oneline backup-branch | head -10

验证重写结果

bash
# 检查提交历史
git log --oneline --graph

# 检查文件完整性
git fsck

# 验证标签
git tag -l | xargs -I {} git verify-tag {}

# 运行测试
npm test

团队协作最佳实践

bash
# 1. 避免重写已推送的历史
# 2. 在私有分支上进行历史重写
# 3. 完成后再推送到共享仓库
# 4. 通知团队成员关于历史重写

# 安全的历史重写流程
git checkout -b private-branch
# 进行历史重写
git checkout main
git merge --ff-only private-branch
git push origin main

常见场景和解决方案

清理提交历史

bash
# 清理包含WIP提交的历史
git rebase -i --root
# 将WIP提交标记为drop或fixup

# 合并相似的提交
git rebase -i HEAD~10
# 使用squash或fixup合并相关提交

修复错误的作者信息

bash
# 使用filter-repo修复作者信息
git filter-repo --mailmap mailmap.txt

# mailmap.txt内容:
# New Name <new.email@example.com> <old.email@example.com>
# New Name <new.email@example.com> Old Name <old.email@example.com>

从提交中删除敏感数据

bash
# 使用BFG删除密码
java -jar bfg.jar --replace-text replacements.txt my-repo.git

# replacements.txt:
# password=***REMOVED***
# secret=***REMOVED***

恢复误操作

恢复重置操作

bash
# 查看引用日志
git reflog

# 恢复到特定状态
git reset --hard HEAD@{1}

# 恢复特定分支
git reset --hard refs/original/refs/heads/main

恢复删除的提交

bash
# 查找删除的提交
git fsck --full --unreachable | grep commit

# 恢复提交
git merge unreachable-commit-hash

重写历史是Git的高级功能,需要谨慎使用。在共享仓库上操作前,务必与团队沟通并做好备份。