⚠️ 免责声明:本工具按现状(AS IS)提供,不作任何明示或暗示的担保。使用本工具的风险由用户自行承担。 如遇问题或有改进建议,欢迎提 Issue 或 PR。Windows 原生环境(CMD / PowerShell)目前未经作者实际测试,如遇 Bug 欢迎反馈。
Git Object Pool Wrapper 是一个 Git 增强工具,以对用户透明为首要设计目标:它在底层完全依赖原生 Git 实现,不引入任何私有存储格式或协议,所有原生 Git 命令保持原有语义不变,在 Git 之上仅扩展了一条子命令 git migrate。具体而言,本工具通过拦截常见 Git 操作,在本机维护一个全局唯一的裸仓对象池(~/.git-pool/pool.git),并结合 Git alternates 与 --reference 机制,让多个 worktree、clone 结果和 submodule 共享同一批 Git objects,从而降低重复下载与磁盘占用。
你在使用 Git 时是否经常遇到这样的问题:
- 需要克隆多份仓库以并行开发或维护不同特性分支,相同对象被反复下载并重复占用磁盘空间。
- 仓库依赖树复杂,存在菱形依赖;多个上游仓库共享同一底层依赖仓库作为 submodule,导致相同对象在本地重复存储。
- 克隆了同一项目的不同 fork 仓库,这些仓库来自不同团队或个人,对象大量重叠却各自独立存放。
- 从不同源克隆了同一项目的仓库,虽然 remote URL 不同但内容高度重叠,仍会耗费大量磁盘空间与下载时间。
如果你正在经历上述问题,本工具或许可以解决你的困扰。
- 拦截
git clone,自动先创建或更新全局唯一对象池,再使用--reference=<pool repo>clone 工作区。 - 拦截
git fetch,先执行原生 fetch;若当前仓库已迁移进池(alternates 指向全局池)则把新对象搬入池并repack,否则按当前分支 tracking remote(回退origin)的 URL 预热全局池。 - 拦截
git submodule update --init,自驱初始化 submodule,并支持递归 submodule。 - 支持 submodule 相对 URL 解析,统一映射到对象池路径。
- 支持同层 submodule 并发处理,兼容
--jobs。 - 支持
--depth、--filter等常见 submodule update 参数。 - 维护已注册 worktree / gitdir 列表,用于后续池 GC 依赖分析。
- 拦截
git gc,在执行原生 GC 前先维护对象池:先汇总所有依赖 worktree 的可达 commit 集合,并在池内为这些 commit 建立refs/object-pool/<sha>临时保护 ref,然后对对象池执行prune --expire=now与repack -Ad,prune 完成后再清理这些临时 ref,避免误删 worktree 仍依赖的对象;设有 1 小时频率限制,避免重复触发全池维护。 - 对本地路径、
file://、已有--reference、--shared、--bare、--mirror等场景自动透传原生 Git。
| 系统 | 适用性 | 说明 |
|---|---|---|
| Linux | 推荐使用 | 脚本依赖 Python 3、系统 Git、POSIX 路径语义和 fcntl.flock 文件锁。主流 Linux 发行版通常都满足这些条件。 |
| macOS | 推荐使用 | macOS 自带 /usr/bin/git 与 Python 3 环境可满足基本运行需求;如通过 Homebrew 安装 Git,可能需要把脚本中的 SYSTEM_GIT 调整为实际路径。 |
| Windows 原生环境 | 可使用(有限制) | 脚本已适配 fcntl → msvcrt 文件锁和 os.execvp → subprocess 进程替换,在 Python 3 + Git for Windows(提供 git.exe)环境下可运行。路径分隔符由 Python pathlib 处理,alternates 文件路径会使用 Windows 风格;需要通过 SYSTEM_GIT 环境变量或系统 PATH 指向 git.exe。CMD / PowerShell 下 shell function 注入方式与 Unix 不同,需手动配置调用方式(参见下方说明)。 |
| Windows WSL | 可使用 | 在 WSL 内部按 Linux 环境使用即可。建议对象池和工作区都放在 WSL 文件系统内,避免跨 /mnt/c 带来的路径、权限和性能问题。 |
| Git Bash / MSYS2 / Cygwin | 可使用 | 这类环境提供 POSIX 兼容层,Python 的 fcntl 通常可用(Git Bash 随 MSYS2 的 Python 提供)。建议通过 shell function 方式注入,路径使用 Unix 风格。 |
| 容器 / CI | 可使用 | 适合在 Linux 容器内使用。需要确保对象池目录挂载为持久卷,否则容器销毁后对象池也会丢失。 |
- Python 3。
- Git 命令行工具。
- 文件系统路径支持(Unix/macOS 使用 POSIX 路径语义;Windows 使用
pathlib跨平台路径处理)。 - 文件锁支持(Unix/macOS 使用
fcntl.flock,Windows 使用msvcrt.locking)。 - Git alternates 支持。
- macOS / Linux 是主要目标环境。
- 脚本优先从
SYSTEM_GIT环境变量读取系统 Git 路径;若未设置,自动通过PATH查找;如均不可用,回退到/usr/bin/git。Windows 用户建议通过SYSTEM_GIT环境变量明确指定git.exe路径(如C:\Program Files\Git\bin\git.exe)。 - 如果多个终端或 CI job 共享对象池,应确保
GIT_POOL指向同一个本地文件系统路径。 - 不建议把对象池放在网络文件系统上,文件锁、rename、权限和 Git 对象访问性能都可能受影响。
默认对象池目录为:
~/.git-pool也可以通过环境变量覆盖:
export GIT_POOL=/path/to/git-poolwrapper 维护一个全局唯一的对象池:
~/.git-pool/pool.git
不同远端 URL 不再各自分池,而是作为该唯一池的不同 remote 共存(remote 名由 URL 归一化生成)。首次遇到某个远端仓库时,wrapper 会初始化该池(git init --bare)并把远端登记为 remote 后只 fetch 该 remote;后续 clone / fetch / submodule 初始化会复用并更新这个唯一池。
以下命令将自动完成 clone、配置和补全脚本的安装,适合快速上手。若需手动控制安装路径或安装方式,参见下方方式一/方式二。
Bash:
git clone git@github.com:mm304321141/git-object-pool.git ~/git-object-pool && printf '\n# git object pool wrapper\nfunction git() { python3 ~/git-object-pool/git-pool-wrapper.py "$@"; }\nsource ~/git-object-pool/git-pool-completion.bash\n' >> ~/.bash_profile && source ~/.bash_profileZsh:
git clone git@github.com:mm304321141/git-object-pool.git ~/git-object-pool && printf '\n# git object pool wrapper\nfunction git() { python3 ~/git-object-pool/git-pool-wrapper.py "$@"; }\nsource ~/git-object-pool/git-pool-completion.zsh\n' >> ~/.zshrc && source ~/.zshrcWindows CMD:
mkdir "%USERPROFILE%\git-object-pool" 2>nul & curl -L -o "%USERPROFILE%\git-object-pool\git-pool-wrapper.py" "https://raw.githubusercontent.com/mm304321141/git-object-pool/main/git-pool-wrapper.py" & curl -L -o "%USERPROFILE%\git-object-pool\git-pool-completion.lua" "https://raw.githubusercontent.com/mm304321141/git-object-pool/main/git-pool-completion.lua" & (echo @python "%USERPROFILE%\git-object-pool\git-pool-wrapper.py" %%*) > "%USERPROFILE%\git-object-pool\git.cmd" & for /f "delims=" %%G in ('where /r "C:\Program Files\Git" git.exe 2^>nul') do (setx SYSTEM_GIT "%%G" & goto :sysgit_done) & echo Could not detect git.exe automatically. Please run: setx SYSTEM_GIT "C:\Program Files\Git\bin\git.exe" & :sysgit_done & if exist "%LocalAppData%\clink\" (copy /y "%USERPROFILE%\git-object-pool\git-pool-completion.lua" "%LocalAppData%\clink\" & echo Clink completion installed.) else (echo Clink not found. For tab completion in CMD, install Clink ^(https://github.com/chrisant996/clink^) then copy "%USERPROFILE%\git-object-pool\git-pool-completion.lua" to "%LocalAppData%\clink\") & echo Add "%USERPROFILE%\git-object-pool" to the front of PATH (before Git install dir).PowerShell:
$dir="$env:USERPROFILE\git-object-pool"; New-Item -ItemType Directory -Force $dir | Out-Null; iwr -UseBasicParsing "https://raw.githubusercontent.com/mm304321141/git-object-pool/main/git-pool-wrapper.py" -OutFile "$dir\git-pool-wrapper.py"; iwr -UseBasicParsing "https://raw.githubusercontent.com/mm304321141/git-object-pool/main/git-pool-completion.ps1" -OutFile "$dir\git-pool-completion.ps1"; Set-Content -Encoding ASCII "$dir\git.ps1" "& python `"$dir\git-pool-wrapper.py`" @args; exit `$LASTEXITCODE"; $realGit = (Get-Command git -All -ErrorAction SilentlyContinue | Where-Object { $_.Source -notlike "*git-object-pool*" } | Select-Object -First 1).Source; if ($realGit) { [Environment]::SetEnvironmentVariable('SYSTEM_GIT', $realGit, 'User'); Write-Host "SYSTEM_GIT set to $realGit" } else { Write-Host "Could not detect git.exe automatically. Please run: [Environment]::SetEnvironmentVariable('SYSTEM_GIT','C:\Program Files\Git\bin\git.exe','User')" }; if (-not (Test-Path $PROFILE)) { New-Item -Path $PROFILE -ItemType File -Force | Out-Null }; $completionLine = ". `"$dir\git-pool-completion.ps1`""; if (-not (Select-String -Path $PROFILE -Pattern ([regex]::Escape($completionLine)) -Quiet)) { Add-Content -Path $PROFILE -Value "`n$completionLine" }; Write-Host "Completion added to `$PROFILE. Run: . `$PROFILE to activate."; Write-Host "Add $dir to the front of PATH."将脚本放到任意固定路径,例如:
mkdir -p ~/bin
cp git-pool-wrapper.py ~/bin/git
chmod +x ~/bin/git确保 ~/bin 位于 PATH 中,并且优先级高于系统 Git:
export PATH="$HOME/bin:$PATH"验证当前命中的 Git:
which git脚本内部默认调用系统 Git:
/usr/bin/git
如你的系统 Git 不在该路径,需要修改脚本中的 SYSTEM_GIT。
Windows CMD:在 PATH 中某目录(如 %USERPROFILE%\bin)下放置 git.cmd:
@python "%USERPROFILE%\bin\git-pool-wrapper.py" %*将 git-pool-wrapper.py 复制到 %USERPROFILE%\bin\git-pool-wrapper.py,确保 %USERPROFILE%\bin 在 PATH 中且优先级高于 Git 安装目录(如 C:\Program Files\Git\cmd)。
Windows PowerShell:在 PATH 中某目录下放置 git.ps1:
& python "$env:USERPROFILE\bin\git-pool-wrapper.py" @args; exit $LASTEXITCODE同样确保该目录在 PATH 中优先级最高,并设置 SYSTEM_GIT:
[Environment]::SetEnvironmentVariable("SYSTEM_GIT", "C:\Program Files\Git\bin\git.exe", "User")不需要修改 PATH,直接在 shell profile 中定义一个名为 git 的函数覆盖系统命令:
Bash(在 ~/.bash_profile 或 ~/.bashrc 中添加):
# git object pool wrapper
function git() {
python3 ~/git-object-pool/git-pool-wrapper.py "$@"
}Zsh(在 ~/.zshrc 中添加):
# git object pool wrapper
function git() {
python3 ~/git-object-pool/git-pool-wrapper.py "$@"
}添加后执行 source ~/.bash_profile 或 source ~/.zshrc 生效。
Windows CMD:CMD 不支持 shell function,使用方式同方式一,通过 git.cmd shim 实现。
Windows PowerShell:在 PowerShell profile($PROFILE)中添加:
function git { & python "$env:USERPROFILE\git-object-pool\git-pool-wrapper.py" @args; $global:LASTEXITCODE = $LASTEXITCODE }添加后执行 . $PROFILE 生效。
仓库提供了 bash 和 zsh 两套补全脚本,安装后可以在 git migrate 时通过 Tab 键补全选项。
Bash(在 ~/.bash_profile 或 ~/.bashrc 中添加):
source ~/git-object-pool/git-pool-completion.bashZsh(在 ~/.zshrc 中添加):
source ~/git-object-pool/git-pool-completion.zsh添加后重新打开终端或执行 source ~/.bash_profile / source ~/.zshrc 即可生效。
注意(Shell Function 安装方式):如果使用方式二(shell function),bash 有时不会自动将补全函数绑定到同名 function,需要在补全脚本 source 之后手动绑定:
source ~/git-object-pool/git-pool-completion.bash complete -F _git gitZsh 通常不需要额外绑定。
Windows CMD(通过 Clink):
CMD 原生不支持可编程补全,建议安装 Clink。安装 Clink 后,将 git-pool-completion.lua(见仓库)放入 Clink scripts 目录:
copy git-pool-completion.lua "%LocalAppData%\clink\"Windows PowerShell:
在 PowerShell profile($PROFILE)中添加:
. "$env:USERPROFILE\git-object-pool\git-pool-completion.ps1"添加后执行 . $PROFILE 生效。
Bash:
sed -i '' '/# git object pool wrapper/d; /git-pool-wrapper\.py/d; /git-pool-completion\.bash/d' ~/.bash_profile && source ~/.bash_profileZsh:
sed -i '' '/# git object pool wrapper/d; /git-pool-wrapper\.py/d; /git-pool-completion\.zsh/d' ~/.zshrc && source ~/.zshrcWindows CMD:
del "%USERPROFILE%\git-object-pool\git.cmd" & del "%USERPROFILE%\git-object-pool\git-pool-wrapper.py" & echo Done. Remember to remove "%USERPROFILE%\git-object-pool" from PATH and unset SYSTEM_GIT if set.Windows PowerShell:
Remove-Item "$env:USERPROFILE\git-object-pool\git.ps1","$env:USERPROFILE\git-object-pool\git-pool-wrapper.py" -ErrorAction SilentlyContinue; Write-Host "Done. Remember to remove $env:USERPROFILE\git-object-pool from PATH and unset SYSTEM_GIT if set."以上仅移除 profile 中的配置行,不删除
~/git-object-pool目录。 若要连目录一起删除:rm -rf ~/git-object-pool。⚠️ 删除目录前请确认本机没有仓库通过alternates依赖对象池(~/.git-pool),否则相关仓库会因缺失对象损坏。
安装后继续按普通 Git 命令使用即可。
git clone git@github.com:owner/repo.git首次 clone 会先将 bare repository 放入对象池,再通过 --reference 创建工作区。
git fetch在普通仓库中执行 fetch 时,wrapper 会根据 remote.origin.url 更新对应对象池。
git submodule update --init --recursive --jobs 8wrapper 会解析 .gitmodules,为每个 submodule 预热对象池,并让 submodule gitdir 通过 alternates 引用池对象。
将一个已存在的普通仓库迁移为对象池化仓库,把历史对象从本地 .git 移到全局唯一池(默认 ~/.git-pool/pool.git):
# 在仓库目录内执行(无需传路径,也无需指定 remote)
git migrate
# 同时迁移所有已 init 的 submodule
git migrate -r
git migrate --recursive迁移流程概览:
- 池不存在时初始化全局唯一裸仓(
git init --bare ~/.git-pool/pool.git)。 - 将本仓库所有本地分支(
refs/heads/*)和 tag(refs/tags/*)指向的对象 fetch 进池。fetch 过程中会在池内使用临时refs/object-pool/命名空间中转引用,fetch 完成后自动清理,不会留下永久 ref。 - 通过 alternates 让本仓库引用池对象,先执行
git reflog expire --expire=now --all清理指向不存在对象的 reflog 条目,再执行git repack --local收缩本地存储。 --recursive时会对已 init 的 submodule(含嵌套 submodule)重复以上步骤;默认递归迁移还会扫描.git/modules/目录下历史上初始化过、但当前 worktree 已不存在的 orphan gitdir,一并迁移进池以回收磁盘空间。若 orphan gitdir 中的core.worktree指向不存在路径,脚本会临时屏蔽该配置后执行迁移,完成后再还原。
注意事项:
- 迁移后仓库依赖
~/.git-pool/pool.git(或GIT_POOL指向的路径),删除或损坏对象池会让仓库无法读取历史对象。 - 仅对已经存在的本地仓库生效;对未 clone 的仓库请使用
git clone,wrapper 会在 clone 阶段自动池化。 - 迁移操作会修改本仓库的
objects/info/alternates,建议在工作区干净时执行。
git gcwrapper 会先执行对象池维护,再透传执行原生 git gc。
池维护时会枚举所有依赖 worktree 的可达 commit 集合,并在池内为这些 commit 建立 refs/object-pool/<sha> 临时保护 ref;随后对对象池依次执行 prune --expire=now 与 repack -Ad,prune 完成后再清理这些临时 ref,避免 worktree 引用的本地独有对象被误删。
对象池维护设有 1 小时频率限制(记录于 ~/.git-pool/.gc_last_run):连续 git gc 只触发一次池维护。带显式参数的 git gc(如 git gc --prune=now)不触发池维护,直接透传。
| 变量 | 默认值 | 说明 |
|---|---|---|
GIT_POOL |
~/.git-pool |
全局 Git 对象池目录 |
对象池中主要包含:
| 路径 | 说明 |
|---|---|
.lock |
进程间文件锁,避免并发更新池仓库时冲突 |
.registered_shells |
已注册 worktree / gitdir 列表,用于 GC 依赖分析 |
pool.git |
全局唯一对象池(所有远端作为不同 remote 共存于此) |
以下场景不会进入对象池逻辑,会直接调用原生 Git:
- 本地路径 clone,例如
/path/to/repo、./repo、../repo。 file://URL。git clone已显式使用--reference或--reference-if-able。git clone --shared。git clone --bare。git clone --mirror。- 非
submodule update --init的其他 submodule 子命令。 - 未被 wrapper 识别的其他 Git 子命令。
- 文件锁:macOS / Linux 使用
fcntl排他锁;Windows 使用msvcrt文件锁。 - wrapper 使用 Git alternates 共享对象;不要手动删除对象池中仍被工作区引用的 bare repository。
- 如果对象池损坏,脚本会立即将损坏的池归档备份(重命名为
.bak目录),然后重新初始化。 git gc的对象池维护依赖.registered_shells,如果工作区被手动移动或删除,registry 会在清理时过滤失效路径。- 该脚本当前定位为个人本机 Git 加速与节省磁盘空间工具,不建议直接作为多人共享服务端组件使用。
临时绕过 wrapper,直接调用系统 Git:
/usr/bin/git clone git@github.com:owner/repo.git或调整 PATH,让系统 Git 优先于 wrapper。
export GIT_POOL=/data/git-pool建议在 shell profile 中固定该变量,避免不同终端使用不同对象池。
可以删除,但所有依赖该池 objects 的工作区可能需要重新 fetch 或重新 clone。删除前建议确认没有重要工作区仍依赖该对象池。
git-pool-wrapper.py:主脚本,作为gitwrapper 使用。git-pool-completion.bash:Bash 补全脚本,source 到~/.bash_profile后生效。git-pool-completion.zsh:Zsh 补全脚本,source 到~/.zshrc后生效。git-pool-completion.lua:Clink(Windows CMD)补全脚本,放入%LocalAppData%\clink\后生效。git-pool-completion.ps1:PowerShell 补全脚本,source 到$PROFILE后生效。