文章

Git 学习

Git 学习

git filter-branch

参考: 处理.git文件夹过大出现臃肿问题-filter-branch和BFG工具

  1. 使用git filter-branch清理历史记录
    filter-branch文档
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     $ git verify-pack -v .git/objects/pack/pack-xxx.idx | sort -k 3 -n | tail -3 # 查看大文件信息
     > 73671b13992abba02a7fa56d37735d4ac01803b1 blob   62992368 62936889 132214388
    
     git rev-list --objects --all | grep 73671b13992abba02a7fa56d37735d4ac01803b1 # 查看大文件的路径
    
     git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk '{print$1}')" # 查询前10个大文件
    
     git filter-branch --force --index-filter "git rm -rf --cached --ignore-unmatch 文件名" --prune-empty --tag-name-filter cat -- --all # 在所有分支上删除该文件
     git filter-branch --force --index-filter "git rm -rf --cached --ignore-unmatch 文件名" --prune-empty --tag-name-filter cat -- HEAD # 只在当前分支删除该文件
    
  2. 删除和重建索引
    1
    2
    3
    4
    
     git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin # 删除原始备份
     git reflog expire --expire=now --all #所有未关联对象过期时间为现在, 默认为90天
     git gc --aggressive --prune=now # 通过gc清理文件并优化本地存储库
     git push --all --force origin # 强制提交
    
  3. 检查结果
    1
    2
    
     du -sh .git # 查看.git文件夹大小
     git count-objects -vH # 查看.git文件夹大小
    

git 原理

参考: Understanding Git Filter-branch and the Git Storage Model

git show 能够查看每个 commit 的内容, 底层使用了 deltas in packfiles 和 copy-on-write forking, 但是实际上每次 commit 均保存了整个 repo 的所有内容(snapshot). 这个 snapshot 可以用 git cat-file -p COMMIT_ID 来查看, 也可以用 git cat-file -p COMMIT_ID:PATH/TO/FILE 来查看某个文件的内容.
snapshot 记录了所有文件的内容, 但是并没有记录文件名, 而是用一个 tree 对象来记录文件名和文件内容的对应关系. 每个存储的文件都经过哈希处理, 并且以压缩格式存储, 按哈希值编制索引. 如

1
2
3
4
5
6
7
8
$ git cat-file -p ec7cd2821d4bcbafe08f3eca6ea60487bfdc1b52
100644 blob 5d1d2bb79e1521b28dd1b8ff67f9b04f38d83620    index.md
040000 tree b7160f7d05d5b5bfe28bad029b1b490e310cff22    redirects
040000 tree d5672dd9ef15adcd1527813df757847d745e299a    second-edition
$ git cat-file -p d219be3c5010f64960ddb609a849fc42a01ad31b # the tree
100644 blob 5d1d2bb79e1521b28dd1b8ff67f9b04f38d83620    index.md
040000 tree b7160f7d05d5b5bfe28bad029b1b490e310cff22    redirects
040000 tree d48b2e06970cf3a6ae65655c340922ae69723989    second-edition

只有 second-edition 的 hash 不同, 说明两个提交之间该文件发生了修改.

  • worktree: 工作区, 也就是我们平时看到的文件夹
  • index: 暂存区, 也就是我们平时用 git add 添加的文件
  • HEAD: 当前分支的最新提交

filter-repo 工具

repo: newren/git-filter-repo

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
37
38
import git_filter_repo as fr

# git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -20 | awk '{print$1}')" | cut -d " " -f 2 | sort -u
# git cat-file -p COMMIT_ID:PATH/TO/FILE | git hash-object -w --stdin
filenames_m = {
    b"ECG_routine/inference_model/models/single_channel/class_model.pb": r"D:/code/ecg_routine/models/single_channel/class_model.pb",
    b"ECG_routine/inference_model/models/single_channel/cylen_model.pb": r"D:/code/ecg_routine/models/single_channel/cylen_model.pb",
}
filenames_r = [
    ["ECG_routine/inference_model/models/single_TU_model.pb", "ECG_routine/inference_model/models/single_T_model.pb"],
    ["ECG_routine/inference_model/models/single_cylen_model.pb", "ECG_routine/inference_model/models/single_channel/cylen_model.pb"],
]
filenames_d = []
modify_blob = {k: [] for k in filenames_m}


def get_blobid(commit: fr.Commit, _): # 获取文件的所有历史blob_id
    for file_change in commit.file_changes:
        if file_change.filename in modify_blob:
            modify_blob[file_change.filename].append(file_change.blob_id)


def set_blob(blob: fr.Blob, _): # 将文件的所有blob_id都设置为同一个, 即最后一个
    for k, v in modify_blob.items():
        if blob.original_id in v[:-1]:
            blob.original_id = v[-1]
            with open(filenames_m[k], "rb") as f:
                blob.data = f.read()
            break


args = fr.FilteringOptions.parse_args(["--force", "--refs", "refs/heads/backup", "--invert-paths"] + " ".join([f"--path-rename {i[1]}:{i[1]}.new" for i in filenames_r]).split(" ") + " ".join([f"--path-rename {i[0]}:{i[1]}" for i in filenames_r]).split(" ") + " ".join([f"--path-rename {i[1]}.new:{i[1]}" for i in filenames_r]).split(" "))
fr.RepoFilter(args).run() # 将更改文件名的提交从一开始就更改文件名

args = fr.FilteringOptions.parse_args(["--force", "--refs", "refs/heads/backup2"])
fr.RepoFilter(args, commit_callback=get_blobid).run()
args = fr.FilteringOptions.parse_args(["--force", "--refs", "refs/heads/backup2"])
fr.RepoFilter(args, blob_callback=set_blob).run()

git command

比较文件在不同 commit 下的差异

排除所有空格导致的差异

1
$ git diff -w -b $COMMIT_1 $COMMIT_2 -- $FILE
本文由作者按照 CC BY 4.0 进行授权