Appearance
重写历史
概述
Git 允许修改提交历史,这在整理提交记录、修复错误、清理敏感信息时非常有用。但重写历史需要谨慎使用,特别是在已推送的提交上。
修改最近提交
修改提交信息
bash
# 修改最近一次提交的信息
git commit --amend
# 直接指定新的提交信息
git commit --amend -m "新的提交信息"
# 修改提交信息但不改变提交内容
git commit --amend --no-edit修改提交内容
bash
# 添加遗漏的文件到最后一次提交
git add forgotten-file.txt
git commit --amend --no-edit
# 修改最后一次提交的文件
# 1. 修改文件
vim src/file.js
# 2. 暂存修改
git add src/file.js
# 3. 修改提交
git commit --amend --no-edit修改作者信息
bash
# 修改最后一次提交的作者
git commit --amend --author="新作者 <new@email.com>"
# 修改作者和提交者
git commit --amend --author="新作者 <new@email.com>" --committer="提交者 <committer@email.com>"
# 使用配置的作者信息
git commit --amend --reset-author --no-edit修改提交时间
bash
# 修改提交时间
git commit --amend --date="2024-01-15 10:00:00"
# 使用相对时间
git commit --amend --date="now"
# 修改作者日期和提交日期
GIT_AUTHOR_DATE="2024-01-15 10:00:00" GIT_COMMITTER_DATE="2024-01-15 10:00:00" git commit --amend --no-edit修改历史提交
使用 git rebase -i
bash
# 交互式变基最近 3 次提交
git rebase -i HEAD~3
# 从某个提交开始变基
git rebase -i <commit-hash>
# 编辑特定范围的提交
git rebase -i <start-commit>^ <end-commit>交互式变基命令
执行 git rebase -i 后会打开编辑器:
bash
pick abc1234 第一次提交
pick def5678 第二次提交
pick ghi9012 第三次提交
# 命令说明:
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交信息
# e, edit = 使用提交,但停下来修改
# s, squash = 使用提交,但合并到前一个提交
# f, fixup = 使用提交,但丢弃提交信息
# d, drop = 删除提交修改历史提交信息
bash
# 1. 开始交互式变基
git rebase -i HEAD~3
# 2. 将需要修改的提交从 pick 改为 reword
reword abc1234 第一次提交
pick def5678 第二次提交
pick ghi9012 第三次提交
# 3. 保存并退出,Git 会让你编辑每个 reword 提交的信息编辑历史提交内容
bash
# 1. 开始交互式变基
git rebase -i HEAD~3
# 2. 将需要编辑的提交改为 edit
edit abc1234 第一次提交
pick def5678 第二次提交
pick ghi9012 第三次提交
# 3. 保存退出后,Git 会停在该提交
# 4. 修改文件
vim src/file.js
# 5. 暂存修改
git add src/file.js
# 6. 修改提交
git commit --amend
# 7. 继续变基
git rebase --continue合并历史提交
bash
# 1. 开始交互式变基
git rebase -i HEAD~3
# 2. 使用 squash 合并提交
pick abc1234 第一次提交
squash def5678 第二次提交
squash ghi9012 第三次提交
# 3. 保存后会让你编辑合并后的提交信息删除历史提交
bash
# 方法一:使用 drop
git rebase -i HEAD~3
# 将要删除的提交改为 drop
drop abc1234 要删除的提交
pick def5678 保留的提交
# 方法二:直接删除
git rebase --onto <commit>^ <commit> HEAD
# 方法三:使用 revert(保留历史)
git revert <commit-hash>重新排序提交
bash
# 交互式变基时直接调整顺序
git rebase -i HEAD~3
# 调整顺序
pick def5678 第二次提交
pick abc1234 第一次提交
pick ghi9012 第三次提交git filter-branch
警告
git filter-branch 已被弃用,推荐使用 git filter-repo。但了解它仍然有用。
从所有提交中删除文件
bash
# 从所有提交中删除敏感文件
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch path/to/sensitive-file' \
--prune-empty --tag-name-filter cat -- --all
# 删除整个目录
git filter-branch --force --index-filter \
'git rm -rf --cached --ignore-unmatch path/to/directory' \
--prune-empty -- --all修改作者信息
bash
# 修改所有提交的作者
git filter-branch --env-filter '
OLD_EMAIL="old@email.com"
CORRECT_NAME="新名字"
CORRECT_EMAIL="new@email.com"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_COMMITTER_NAME="$CORRECT_NAME"
export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_AUTHOR_NAME="$CORRECT_NAME"
export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags清理仓库
bash
# 清理 filter-branch 的备份
rm -rf .git/refs/original/
# 清理 reflog
git reflog expire --expire=now --all
# 运行垃圾回收
git gc --prune=now --aggressivegit filter-repo
git filter-repo 是推荐的仓库重写工具,比 filter-branch 更快更安全。
安装
bash
# macOS
brew install git-filter-repo
# Linux
pip install git-filter-repo
# 或从源码安装
pip install git-filter-repo删除敏感文件
bash
# 从历史中删除文件
git filter-repo --path path/to/sensitive-file --invert-paths
# 删除整个目录
git filter-repo --path path/to/directory --invert-paths
# 删除匹配模式的文件
git filter-repo --path-glob '*.env' --invert-paths保留特定文件
bash
# 只保留特定路径
git filter-repo --path src/ --path-rename src/:project/
# 保留多个路径
git filter-repo --path src/ --path lib/ --path docs/修改邮箱和用户名
bash
# 创建映射文件 mailmap.txt
# old@email.com new@email.com
# another@old.com correct@email.com
git filter-repo --mailmap mailmap.txt使用回调函数
bash
# 修改提交信息
git filter-repo --message-callback '
return message.replace(b"bug", b"fix")
'
# 修改作者信息
git filter-repo --name-callback '
return name.replace(b"Old Name", b"New Name")
'
# 修改邮箱
git filter-repo --email-callback '
return email.replace(b"old.com", b"new.com")
'高级用法
bash
# 删除空提交
git filter-repo --prune-empty always
# 保留特定分支
git filter-repo --refs refs/heads/main refs/heads/develop
# 从特定提交开始
git filter-repo --refs abc1234..HEAD
# 强制推送到远程
git filter-repo --force重写历史后的处理
强制推送
bash
# 重写历史后需要强制推送
git push --force origin main
# 更安全的方式(推荐)
git push --force-with-lease origin main通知团队成员
重写已推送的历史后,团队成员需要:
bash
# 团队成员需要重新获取仓库
git fetch origin
# 重置本地分支
git reset --hard origin/main
# 或者变基
git rebase origin/main清理远程引用
bash
# 清理远程跟踪分支
git remote prune origin
# 清理所有过时的引用
git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin常见场景
场景一:删除意外提交的密码
bash
# 1. 找到包含密码的文件
git log --all --full-history -- config/secrets.yml
# 2. 使用 filter-repo 删除
git filter-repo --path config/secrets.yml --invert-paths
# 3. 清理并强制推送
git gc --prune=now
git push --force-with-lease origin main
# 4. 更改密码!(最重要)场景二:整理混乱的提交历史
bash
# 1. 交互式变基
git rebase -i HEAD~10
# 2. 合并相关提交
pick abc1234 功能 A 第一部分
squash def5678 功能 A 第二部分
pick ghi9012 功能 B
squash jkl3456 功能 B 修复
# 3. 重写提交信息
# 4. 强制推送
git push --force-with-lease origin feature-branch场景三:分割大仓库
bash
# 使用 filter-repo 提取子目录为新仓库
git filter-repo --path src/module-a/ --path-rename src/module-a/:安全警告
何时可以重写历史
✅ 可以重写:
- 本地未推送的提交
- 个人分支上的提交
- 团队约定可以重写的分支
❌ 不要重写:
- 主分支(main/master)
- 已共享的分支
- 有其他人在使用的分支
重写历史的风险
bash
# 重写历史会导致:
# 1. 提交哈希改变
# 2. 其他人的本地分支出现冲突
# 3. 可能丢失提交
# 4. CI/CD 历史可能混乱最佳实践
1. 重写前备份
bash
# 创建备份分支
git branch backup-branch
# 或创建整个仓库的备份
cp -r .git .git-backup2. 使用 --force-with-lease
bash
# 不推荐
git push --force origin main
# 推荐
git push --force-with-lease origin main3. 小范围修改
bash
# 只修改必要的提交
git rebase -i HEAD~3 # 而不是 HEAD~504. 及时沟通
重写共享分支历史前,通知所有团队成员。
总结
| 操作 | 命令 | 说明 |
|---|---|---|
| 修改最近提交 | git commit --amend | 修改最后一次提交 |
| 交互式变基 | git rebase -i | 修改历史提交 |
| 删除文件 | git filter-repo --path ... --invert-paths | 从历史删除文件 |
| 修改作者 | git filter-repo --mailmap | 批量修改作者信息 |
| 强制推送 | git push --force-with-lease | 安全地强制推送 |
使用建议:
- 重写前先备份
- 只在必要时重写历史
- 优先使用
filter-repo而非filter-branch - 重写后及时通知团队成员
