filter-branch 和 filter-repo 的工作原理

高级
视频演示

filter-branch 和 filter-repo 的工作原理

🤔 你的问题

使用 git filter-branchgit filter-repo,是不是需要在本地 Git 服务器(本地仓库)删除所有关于这个文件的引用以及源文件,这样才可以删除这个文件?

🎯 核心答案

你的理解基本正确,但需要澄清一些细节:

  1. 这些工具会自动删除所有引用
  2. 但文件内容(对象)可能还在 .git/objects/
  3. 需要垃圾回收才能真正删除文件内容
  4. 然后需要强制推送到远程

📊 filter-branch/filter-repo 的工作流程

完整流程

1. 重写历史(删除文件引用)
   ↓
2. 文件引用被删除(所有提交中)
   ↓
3. 文件内容(对象)可能还在 .git/objects/ 中
   ↓
4. 垃圾回收(git gc)删除未被引用的对象
   ↓
5. 强制推送到远程

🔍 详细步骤分析

步骤 1: 重写历史(删除文件引用)

使用 git filter-branch

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch unwanted_file.txt' \
  --prune-empty --tag-name-filter cat -- --all

这个命令做了什么:

  1. 遍历所有提交--all
  2. 对每个提交执行--index-filter):
    • git rm --cached --ignore-unmatch unwanted_file.txt
    • 从暂存区删除文件引用
    • --ignore-unmatch:如果文件不存在,不报错
  3. 删除空提交--prune-empty
  4. 更新所有标签--tag-name-filter cat

结果:

  • ✅ 所有提交中的文件引用被删除
  • ✅ 历史被重写(所有提交的 SHA-1 哈希改变)
  • ⚠️ 但文件内容(对象)可能还在 .git/objects/

步骤 2: 清理引用

# 删除 filter-branch 创建的备份引用
git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
 
# 清理 reflog
git reflog expire --expire=now --all
 
# 垃圾回收
git gc --prune=now

这些命令做了什么:

  1. 删除备份引用refs/original/

    • filter-branch 会创建备份
    • 需要手动删除
  2. 清理 reflog

    • reflog 记录了所有操作历史
    • 可能还包含对旧对象的引用
  3. 垃圾回收git gc --prune=now

    • 删除未被引用的对象
    • 这时文件内容才真正被删除

步骤 3: 强制推送到远程

git push --force --all
git push --force --tags

这个命令做了什么:

  1. 强制推送所有分支--all
  2. 强制推送所有标签--tags
  3. 覆盖远程历史

结果:

  • ✅ 远程历史被重写
  • ✅ 文件从远程历史中删除

📋 完整操作流程

使用 git filter-branch

# 1. 从所有历史中删除文件引用
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch unwanted_file.txt' \
  --prune-empty --tag-name-filter cat -- --all
 
# 2. 删除备份引用
git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
 
# 3. 清理 reflog
git reflog expire --expire=now --all
 
# 4. 垃圾回收(真正删除文件内容)
git gc --prune=now
 
# 5. 强制推送到远程
git push --force --all
git push --force --tags

使用 git filter-repo(推荐)

# 1. 从所有历史中删除文件(自动处理所有步骤)
git filter-repo --path unwanted_file.txt --invert-paths
 
# 2. 强制推送到远程
git push --force --all
git push --force --tags

优点:

  • ✅ 自动删除备份引用
  • ✅ 自动清理 reflog
  • ✅ 自动垃圾回收
  • ✅ 更安全、更快

🔍 文件内容的删除时机

Git 对象存储

.git/objects/
├── <hash1>/  ← 文件内容(即使引用被删除,内容可能还在)
├── <hash2>/
└── ...

对象删除的时机

1. 引用被删除后

提交对象 → 树对象 → 文件引用
如果文件引用被删除:
- 文件引用不存在了
- 但文件内容(对象)可能还在

2. 垃圾回收时

git gc --prune=now

这会:

  • 扫描所有对象
  • 找出未被引用的对象
  • 删除未被引用的对象
  • 这时文件内容才真正被删除

📊 状态变化图

操作前

本地仓库:
├── 提交1: 包含 unwanted_file.txt 的引用
├── 提交2: 包含 unwanted_file.txt 的引用
└── 提交3: 包含 unwanted_file.txt 的引用

.git/objects/:
├── <hash1>/  ← unwanted_file.txt 的内容
└── ...

filter-branch 后(步骤 1)

本地仓库:
├── 提交1: 不包含 unwanted_file.txt 的引用(重写)
├── 提交2: 不包含 unwanted_file.txt 的引用(重写)
└── 提交3: 不包含 unwanted_file.txt 的引用(重写)

.git/objects/:
├── <hash1>/  ← unwanted_file.txt 的内容(还在!)
└── ...

垃圾回收后(步骤 4)

本地仓库:
├── 提交1: 不包含 unwanted_file.txt 的引用
├── 提交2: 不包含 unwanted_file.txt 的引用
└── 提交3: 不包含 unwanted_file.txt 的引用

.git/objects/:
└── ...  ← unwanted_file.txt 的内容被删除了

强制推送后(步骤 5)

远程仓库:
├── 提交1: 不包含 unwanted_file.txt 的引用
├── 提交2: 不包含 unwanted_file.txt 的引用
└── 提交3: 不包含 unwanted_file.txt 的引用

远程 .git/objects/:
└── ...  ← unwanted_file.txt 的内容被删除了

🎯 关键理解

你的理解是对的

是的,需要:

  1. 删除所有引用

    • 所有提交中的文件引用
    • filter-branch/filter-repo 会自动完成
  2. 删除文件内容(对象)

    • 需要垃圾回收(git gc
    • filter-repo 会自动完成
    • filter-branch 需要手动执行
  3. 推送到远程

    • 强制推送覆盖远程历史
    • 远程也会删除引用和内容

💡 为什么需要这些步骤?

步骤的必要性

1. 删除引用(filter-branch/filter-repo)

为什么需要:

  • Git 通过引用找到对象
  • 如果引用还在,对象就不会被删除
  • 必须删除所有引用

2. 清理备份引用(filter-branch)

为什么需要:

  • filter-branch 会创建备份(refs/original/
  • 备份引用仍然指向旧对象
  • 必须删除备份引用

3. 清理 reflog

为什么需要:

  • reflog 记录了所有操作历史
  • 可能还包含对旧对象的引用
  • 必须清理 reflog

4. 垃圾回收

为什么需要:

  • 即使引用被删除,对象还在 .git/objects/
  • 垃圾回收会删除未被引用的对象
  • 这时文件内容才真正被删除

5. 强制推送

为什么需要:

  • 远程历史还是旧的(包含文件)
  • 必须强制推送覆盖远程历史
  • 远程也会删除引用和内容

📋 总结

你的理解是对的

是的,需要:

  1. 在本地仓库删除所有引用

    • filter-branch/filter-repo 会自动完成
    • 重写所有提交,删除文件引用
  2. 删除文件内容(对象)

    • 需要垃圾回收(git gc
    • filter-repo 会自动完成
    • filter-branch 需要手动执行
  3. 推送到远程

    • 强制推送覆盖远程历史
    • 远程也会删除引用和内容

关键点

  • 不是"本地 Git 服务器",而是"本地仓库"
  • 这些工具会自动删除引用
  • 但文件内容(对象)可能还在,需要垃圾回收
  • 然后需要强制推送到远程

记住

  • filter-branch/filter-repo:删除所有引用(自动)
  • 垃圾回收:删除文件内容(对象)
  • 强制推送:同步到远程

简单记忆:filter-branch/filter-repo 会自动删除所有引用,但文件内容需要垃圾回收才能真正删除,然后强制推送到远程!