如何用filter-repo清理Git历史大文件并保留LFS指针?
解决Git历史大文件的LFS迁移/清理方案
针对你的问题,这里提供两个精准的解决方案,都是基于git filter-repo来避免误删LFS指针的问题:
方案1:将历史中的大文件批量转为Git LFS指针
核心思路是用--blob-callback区分原始大文件和已存在的LFS指针文件,只对前者做替换处理,保留指针文件。
- 先确认你的Git LFS跟踪规则(比如
.gitattributes里的配置,例如*.psd filter=lfs diff=lfs merge=lfs -text) - 编写Python回调脚本(命名为
convert_to_lfs.py):
import sys import os import subprocess # 匹配你的LFS跟踪文件后缀 LFS_EXTENSIONS = {'.psd', '.zip', '.tar.gz', '.exe'} # 大文件阈值(小于此大小的文件不处理,避免小文件转为LFS) SIZE_THRESHOLD = 1024 * 100 # 100KB def callback(blob): # 跳过无路径的blob或不属于LFS跟踪类型的文件 if not blob.path: return ext = os.path.splitext(blob.path)[1].lower() if ext not in LFS_EXTENSIONS: return # 跳过已经是LFS指针的文件(指针内容以特定版本头开头) if blob.data.startswith(b'version https://git-lfs.github.com/spec/'): return # 跳过小于阈值的小文件 if len(blob.data) < SIZE_THRESHOLD: return # 将原始文件写入临时文件,生成LFS指针 temp_file = '/tmp/temp_lfs_blob' with open(temp_file, 'wb') as f: f.write(blob.data) # 生成LFS指针内容 pointer_result = subprocess.run( ['git', 'lfs', 'pointer', 'generate', '--file', temp_file], capture_output=True, text=True ) if pointer_result.returncode != 0: print(f"Failed to create pointer for {blob.path}: {pointer_result.stderr}", file=sys.stderr) os.unlink(temp_file) return # 替换当前blob为LFS指针数据 blob.data = pointer_result.stdout.encode('utf-8') os.unlink(temp_file)
- 执行filter-repo命令:
git filter-repo --blob-callback "$(cat convert_to_lfs.py)"
执行完成后,所有历史中符合条件的原始大文件都会被替换为LFS指针,之后记得运行git lfs push --all origin将LFS对象上传到远程仓库。
方案2:删除LFS迁移前的旧文件(保留后续的LFS指针)
如果只想清理LFS切换前的历史大文件,保留切换后的指针,关键是定位首次引入LFS指针的提交节点,针对性过滤历史。
- 找到LFS迁移的基准提交:
比如通过查找修改.gitattributes的提交(通常是启用LFS的节点):
git log --oneline -- .gitattributes
假设找到的提交ID是abc123,该提交之后的文件都是LFS指针,之前的是原始大文件。
- 编写Python回调脚本(命名为
remove_old_lfs_files.py):
import sys import subprocess # 替换为你的LFS迁移提交ID LFS_MIGRATION_COMMIT = 'abc123' # 匹配你的LFS跟踪文件后缀 LFS_EXTENSIONS = {'.psd', '.zip', '.tar.gz'} def callback(commit): # 判断当前提交是否在LFS迁移之后:如果提交能被追溯到迁移提交之后,直接保留 try: rev_list_result = subprocess.run( ['git', 'rev-list', f'{LFS_MIGRATION_COMMIT}..{commit.id}'], capture_output=True, text=True ) # 如果输出非空,说明当前提交在迁移之后,跳过处理 if rev_list_result.stdout.strip(): return except Exception as e: print(f"Error checking commit {commit.id}: {e}", file=sys.stderr) return # 迁移前的提交:删除所有LFS跟踪类型的文件 to_remove = [] for path in commit.file_changes.keys(): if not path: continue ext = os.path.splitext(path)[1].lower() if ext in LFS_EXTENSIONS: to_remove.append(path) for path in to_remove: del commit.file_changes[path]
- 执行filter-repo命令:
git filter-repo --commit-callback "$(cat remove_old_lfs_files.py)"
这个命令会删除abc123提交之前所有历史中的大文件,完全保留该提交及之后的LFS指针。
关键注意事项
- 执行
git filter-repo前必须备份仓库,因为它会永久重写历史 - 如果是协作仓库,重写后需要通知所有协作者重新克隆仓库,避免历史冲突
- 处理完成后,运行
git lfs fetch和git lfs checkout确保LFS文件正常拉取
内容的提问来源于stack exchange,提问作者Mickey Cohen
相关产品推荐
相关产品推荐

