或许,你初学 Git 的时候,并不知道有 .gitingore 这个文件或者写的不够全面,导致你的 Git Repo 中有很多不必要的文件。

又或者,你曾经把一些大文件提交到了 Git Repo 中,导致 Repo 的体积变得很大,即使你已经删除了这些文件,Repo 的体积也没有减小。

又又或者,你曾经在你的仓库里写过一些十分羞耻的文章 or 十分重要的密码,你不希望有人能够直接通过 reset 或者 revert 来看到这些文件。

又又又或者,因为各种原因,你的 git log 有各种个各样的合并、变基、重置,导致你的 Repo 的历史变得十分混乱。

那么,这时候,不妨试一下 git-filter-repo 这个工具。

简介与安装 blabla

其作者在 仓库 的介绍中写道:

git filter-repo 是一个多功能的历史重写工具,包含了我在其他地方找不到的功能。它大致与 git filter-branch 属于同一类工具,但没有令人沮丧的性能问题,功能更多,设计上在可用性方面超越了简单的重写案例。git 官方现在推荐使用 git filter-repo 而不是 git filter-branch。

虽然大多数用户可能只会将 filter-repo 作为一个简单的命令行工具使用(并且可能只使用它的部分功能),但在其核心,filter-repo 包含了一个用于创建历史重写工具的库。因此,有特殊需求的用户可以利用它快速创建全新的历史重写工具。

至于安装,可以随意使用你喜欢的包管理器,比如说:

1
pip install git-filter-repo

分析

切换到你的 repo 的根目录,然后运行:

1
git filter-repo --analyze

出于各种考量,git-filter-repo 默认只会在全新 clone 的 repo 中生效,如果你想在已有的 repo 中使用,你需要在 git-filter-repo 的命令后面加上 --force 参数。

这个命令会生成 .git/filter-repo/analysis

1
2
3
4
5
6
7
8
9
blob-shas-and-paths.txt
directories-all-sizes.txt
directories-deleted-sizes.txt
extensions-all-sizes.txt
extensions-deleted-sizes.txt
path-all-sizes.txt
path-deleted-sizes.txt
README
renames.txt

其中,path-deleted-sizes.txt 会列出所有已经被删除的文件,以及它们的大小。

例子:删除如今已经不在仓库中,但是历史记录中仍然存在的文件

这里的脚本,自然是在分析之后执行的,一句话就是,从 git 仓库删除 path-deleted-sizes.txt 所列出的文件 (自然,通常也包括相应的提交记录)。

也就是调用 git filter-repo --path <path> --invert-paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import subprocess
import sys
from pathlib import Path

def filter_repo_delete_path(repo_path, path):
"""Run git filter-repo to delete a specific path from the repository."""
subprocess.run(
["git", "filter-repo", "--path", path, "--invert-paths", "--force"],
cwd=repo_path,
check=True
)

def main(repo_path, analysis_path):
"""Main function to read paths from analysis file and delete them from the repository."""
try:
with open(analysis_path, "r") as f:
lines = f.readlines()[2:] # Skip the first two lines
except Exception as e:
print(f"Error reading file {analysis_path}: {e}")
return

paths = [line.split()[-1] for line in lines if line.strip()]

for path in paths:
print(f"Deleting {path}...")
try:
filter_repo_delete_path(repo_path, path)
except subprocess.CalledProcessError as e:
print(f"Error deleting {path}: {e}")

if __name__ == "__main__":
default_repo_path = Path(r"C:\Users\user\Documents\GitHub\my-repo")
repo_path = Path(sys.argv[1]) if len(sys.argv) > 1 else default_repo_path
analysis_path = Path(r".git\filter-repo\analysis\path-deleted-sizes.txt")

main(repo_path, analysis_path)

通过删除这些文件,大概可以实现开头提到的几个目的。

除了,重写历史。这个可以参考 阿里云的一个教程