NOTE: 就 Git 设计而言,对一个 commit 进行持久化修改,所有基于其上的 commits 必须接受变更。如果你遇到了 commit id 不变的情况,恭喜你成功在 git 上实现了哈希碰撞。

一、如果只是想减少 clone 下来的 .git 体积,考虑 shallow clone

# 对每一个分支/标签,只拉取其最新的 3 层提交
git clone --depth 3

优点:不变更历史

缺点:将一个现有的 full clone 库转化为 shallow clone 库或减少一个 shallow clone 的库的拉取深度(–depth)节省的空间量有限,如果做过 gc 节省的量就更有限了,如果做过 gc –aggressive ,占用空间不变。因此如果有强迫症需要一直维持最小空间占用就得不停的重新 shallow clone 。

实现相关:.git/info/shallow

二、抛弃全部历史,考虑直接重建备份执行以下

rm -rf .git
git init
git add .
git commit "first commit"
git push -f -u origin master

优点:爽!

缺点:变更历史;丢失所有当前未合并的分支(亦即未完成工作)。

三、抛弃部分历史,考虑 git replace + git filter-branch

1、备份(精通 90 的玩家可以不用备份,filter-branch 会将旧引用备份于 refs/original,且在你执行 STAGE 2 之前数据仍可追踪)

2、移除所有已合并分支

3、选取一个历史 commit ,该 commit 必须直接或间接 parent 于任意两分支的 merge base

4、最终选取的 commit 记为 T

5、执行以下 snippets## STAGE 1

# 创建一个 orphan commit (初始提交即为最常见的 orphan commit)
git replace --graft T
# 世界和平
git filter-branch -- --all
# 删除之前的 git-replace 关联
git replace -d T

## STAGE 2
# 清理 reflog
git reflog expire --expire=all --all
# 重新打包,所有关联对象合并入包中,所有非关联对象移出包成为松散文件
git gc --aggressive
# 清理所有松散文件
git prune --expire now

优点:保留所有分支信息(包括当前正在开发/未完成的工作);真正的历史截断。

缺点:变更历史;尽管保留了分支信息,但依旧需要开发者重新将未 push 的 commits rebase 至新的分支上。

四、谐星可以考虑的方案

1、以上一个方案为模板将

2、snippets 中的 STAGE 1 阶段修改为以下操作

# 创建一个 orphan commit 并用分支 temp 引用之
git checkout --orphan temp T
git commit -m "Truncated Point"
# 将所有截断点的 child commit 重新应用在 temp 上
git rebase -p --onto temp T master
# 现在 master 就是你新的世界,它包含 temp
git branch -d temp

优点:无。

缺点:在包含上个方案所有缺点的基础上:解决过的合并冲突必须再解决一次(除非所有的冲突都用 rerere 记录过);所有标签信息丢失;若要保留 master 以外其他的分支,需要找到对应的 rebase base 然后挨个 rebase 过来。