Appearance
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的高级功能,需要谨慎使用。在共享仓库上操作前,务必与团队沟通并做好备份。