#git-repository #git #repository #incremental #offline #mirroring #git-directory

程序 git-ibundle

用于 Git 仓库增量离线镜像的工具

4 个版本

0.2.2 2024年1月30日
0.2.1 2023年6月24日
0.2.0 2023年2月28日
0.1.1 2023年2月7日
0.1.0 2023年2月6日

#1492命令行工具

每月 33 次下载

MIT 许可证

74KB
1.5K SLoC

git-ibundle

git-ibundle 是一个用于 Git 仓库增量离线镜像的工具。

通过一系列 "ibundle"(增量包)文件,将增量仓库数据从源网络传输到目标网络。两个网络之间不需要交互连接;只需要可靠的单向文件传输能力。

典型传输过程

假设将 repo.git 从源网络镜像到断开连接的目标网络。

首先进行一次设置

  • 在源网络上,对仓库执行镜像克隆

    git clone --mirror https://github.com/user/repo.git
    cd repo.git
    
  • 在目标网络上,设置一个空的裸仓库作为镜像

    mkdir repo.git
    cd repo.git
    git init --bare
    

接下来,根据需要重复以下步骤,以保持源和目标仓库同步

  • 在源网络上,获取任何更改并创建 repo.ibundle

    # On source network, within the `repo.git` directory:
    git fetch
    git-ibundle create .../path/to/repo.ibundle
    
  • repo.ibundle 文件传输到目标网络。

  • 在目标网络上,从 repo.ibundle 获取

    # On destination network, within the `repo.git` directory:
    git-ibundle fetch .../path/to/repo.ibundle
    

变更历史

有关变更列表,请参阅 CHANGES

许可证

git-ibundle 根据 MIT 许可证许可;请参阅 LICENSE

需求

  • git-ibundle 可执行文件
  • Git v2.31+(在 PATH 上有 git

注意:Git 版本 2.31 引入了 git-ibundle 所需的 git bundle create --stdin 标志。

开发和大多数测试是在 Linux 上进行的;这是最佳支持的平台。在 Windows 上进行了有限的测试。在 Macos 上没有进行测试。

安装

安装选项包括

使用git ibundle进行调用

git-ibundle使用git-前缀命名,以便它可以作为命令ibundle集成到Git中。如果找到git-ibundle可执行文件在PATH上,那么Git命令git ibundle将委托给git-ibundle。这些调用是等效的

git-ibundle <ibundle-arguments>
git ibundle <ibundle-arguments>

这允许git-ibundle继承一些通用的Git功能,其中最有用的是

git -C path/to/repository <command>

这会导致Git在运行<command>之前更改目录到path/to/repository。例如

# Create directory and initialize as a bare Git repo:
mkdir repo.git
git -C repo.git init --bare

这对于git-ibundle来说也是很有用的。考虑有一个仓库和一个ibundle文件在同一个目录下

./
    repo.git/
    repo.ibundle

要从这个ibundle获取到仓库中,您可以将目录更改为仓库目录并像这样获取

cd repo.git
git ibundle fetch ../repo.ibundle
cd ..

或者您可以使用-C repo.git在一步中完成这个操作

git -C repo.git ibundle fetch ../repo.ibundle

请注意,更改目录发生在git-ibundle检查其参数之前,因此如果您使用到repo.ibundle的相对路径,您必须使该路径相对于仓库的位置(这就是为什么上面的例子使用../repo.ibundle的原因)。

模型

git-ibundle在离散的时间同步点同步两个仓库。每次通过git-ibundle create创建ibundle时,就会定义一个新的同步点,并记录当前仓库状态。仓库状态包括HEAD以及所有分支、标签和相关提交ID。一个自动递增的序列号提供了一种识别同步点并标记相关ibundle文件和当前仓库状态的方法。

ibundle文件包含在先前(基础)状态和当前状态之间发生的源仓库更改。在目标位置,git-ibundle fetch将应用这些更改到目标位置,同步该仓库与源仓库。git-ibundle验证目标仓库已经应用了ibundle基础上的更改。

默认情况下,使用立即前一个序列号作为基础创建ibundle;您可以通过git-ibundle create --basis <seq_num>选择不同的基础。如果在将它们获取到目标仓库之前,任何先前的ibundle文件已经丢失,这将很有用。

对于repo.git仓库,git-ibundle使用目录repo.git/ibundle/来存储其元数据。该目录对Git来说是透明的,不会干扰或与正常的Git操作重叠。

镜像子集

git-ibundle自身总是创建源存储库的完整镜像。这包括存储库中的所有引用,包括在refs/remotes/<REMOTE>下找到的任何内容。应使用git clone --mirror将源存储库克隆到本地repo.git目录,以防止创建refs/remotes/<REMOTE>并确保准确的镜像。

可以通过设置负引用规范来镜像原始存储库的子集。例如,为了避免镜像Github拉取请求(这些引用规范的形式为refs/pull/*),可以使用以下负引用规范:

remote.origin.fetch=^refs/pull/*

这不能通过git clone --mirror --config来配置,因为负引用规范不会立即生效;相反,手动通过以下方式设置源repo.git

mkdir repo.git
cd repo.git
git init --bare
git remote add origin --mirror=push https://github.com/user/repo.git
git config remote.origin.fetch '+refs/*:refs/*'
git config --add remote.origin.fetch '^refs/pull/*'

然后可以抓取并验证引用是否符合预期。

git fetch
git show-ref

命令调用详情

创建ibundle

Usage: git-ibundle create [OPTIONS] <IBUNDLE_FILE>

Arguments:
  <IBUNDLE_FILE>  ibundle file to create

Options:
      --basis <BASIS>  Choose alternate basis sequence number
      --basis-current  Choose basis to be current repository state
      --standalone     Force ibundle to be standalone
      --allow-empty    Allow creation of an empty ibundle
  -h, --help           Print help information
  -V, --version        Print version information
  -v, --verbose...     More output per occurrence
  -q, --quiet...       Less output per occurrence

在第一次创建ibundle时,存储库将分配一个随机的repo_id。这有助于防止将ibundle文件错误地应用到错误的Git存储库目标。在git-ibundle fetch操作期间将检查repo_id。

基础序列号默认比ibundle的序列号小一;对于第一个ibundle(其序列号为1),基础序列号将为0

使用--basis 0,创建的ibundle将假设目标没有先决提交;它将包含通过git-ibundle fetch创建镜像存储库所需的一切。请注意,--basis 0意味着--standalone

如果不使用--standalone,则ibundle将假设目标已同步到--basis序列号,因此包含所有先决提交和引用;因此,创建的ibundle文件仅包含用于紧凑性的更改引用,以及包含更新Git对象的Git "PACK"。

使用--standalone,ibundle将包含完整的命名引用集合和先决提交ID的完整枚举。提交数据仍然是增量且基于由--basis隐含的提交。这可以用于目标存储库已知具有先决提交但缺少实际基础序列号的情况(例如,在目标网络上使用预存存储库镜像时)。

通常,git-ibundle create将拒绝在没有自上次创建ibundle以来有任何更改时创建ibundle。在这种情况下,将提供一个退出状态3(而大多数失败将导致退出状态为1)。要允许创建空ibundle,请使用--allow-empty

使用--basis-current(这隐含了--standalone--allow-empty),基础设置为当前仓库状态。ibundle将逻辑上为空且独立,适用于将现有目标仓库(已知与当前仓库状态匹配)中的内容提取出来。这为使用git-ibundle对现有镜像仓库对进行初始化提供了方法。例如

# On source network:
cd source.git
git-ibundle create --basis-current ../bootstrap.ibundle

# Transfer `bootstrap.ibundle` to destination network.

# On destination network:
cd destination.git
git-ibundle fetch ../bootstrap.ibundle --force

从ibundle获取

Usage: git-ibundle fetch [OPTIONS] <IBUNDLE_FILE>

Arguments:
  <IBUNDLE_FILE>  ibundle file to fetch

Options:
      --dry-run     Perform a trial fetch without making changes to the repository
      --force       Force fetch operation
  -h, --help        Print help information
  -V, --version     Print version information
  -v, --verbose...  More output per occurrence
  -q, --quiet...    Less output per occurrence

使用--dry-run,模拟获取操作,但不会更改仓库。这对于检查ibundle文件的有效性以及测试非常有用。

git-ibundle在从意外捆绑包获取时会非常小心。使用--force来覆盖此谨慎。以下情况下可以使用--force

  • 仓库非空,但尚未进行过获取操作,因此不存在git-ibundle repo_id。如果没有使用--force,git-ibundle不会冒险覆盖错误仓库的引用。

  • 一个带有非零基础序列号的独立ibundle正在应用于缺少该基础的仓库。因为ibundle是独立的,所以引用集和先决条件提交ID在ibundle本身内,因此fetch操作可以安全尝试;强制执行不会覆盖所有提交ID都必须存在的需求。

显示ibundle的详细信息

Usage: git-ibundle show [OPTIONS] <IBUNDLE_FILE>

Arguments:
  <IBUNDLE_FILE>  ibundle file to examine

Options:
  -h, --help        Print help information
  -V, --version     Print version information
  -v, --verbose...  More output per occurrence
  -q, --quiet...    Less output per occurrence

例如

$ git-ibundle show file.ibundle
standalone: no
repo_id: d64e7f05-9e75-458d-8c3d-9e7380b6d5b5
seq_num: 2
basis_seq_num: 1
head_ref: 'refs/heads/main'
head_detached: no
added_orefs: 1
removed_orefs: 1
moved_orefs: 2
prereqs: 1

使用--verbose,显示更多详细信息

$ git-ibundle show --verbose file.ibundle
standalone: no
repo_id: d64e7f05-9e75-458d-8c3d-9e7380b6d5b5
seq_num: 2
basis_seq_num: 1
head_ref: 'refs/heads/main'
head_detached: no
added_orefs: 1
4575ca5a540085b2569d714fd449ba7a21b3ebf6 'refs/tags/tag2'
.
removed_orefs: 1
29f7fecbb7c205a4185c59cf50c6ff0d5137979d 'refs/heads/branch1'
.
moved_orefs: 2
4575ca5a540085b2569d714fd449ba7a21b3ebf6 'HEAD'
4575ca5a540085b2569d714fd449ba7a21b3ebf6 'refs/heads/main'
.
prereqs: 1
29f7fecbb7c205a4185c59cf50c6ff0d5137979d 'Initial commit.'
.

报告状态

Report status

Usage: git-ibundle status [OPTIONS]

Options:
  -h, --help        Print help information
  -V, --version     Print version information
  -v, --verbose...  More output per occurrence
  -q, --quiet...    Less output per occurrence

这提供了给定仓库的git-ibundle状态。例如

$ git-ibundle status
repo_id: 18450f13-4003-474a-a69e-22782ef3848f
max_seq_num: 13
next_seq_num: 14

next_seq_num字段指示下一个git-ibundle create操作将使用的序列号。

max_seq_num字段指示由最近一次git-ibundle create操作使用的序列号。

使用--verbose,提供更多详细信息

$ git-ibundle status --verbose
repo_id: 18450f13-4003-474a-a69e-22782ef3848f
max_seq_num: 13
next_seq_num: 14
details:
  seq_num  num_refs HEAD
  1        0        refs/heads/main
  2        0        refs/heads/main
  3        5        refs/heads/main
  4        5        refs/heads/main
  5        6        refs/heads/main
  6        7        refs/heads/fix1
  7        7        refs/heads/main2
  8        7        refs/heads/main
  9        7        343f8d34eb565c0e97194604fa2c6c3ff8ba4931 (detached)
  10       7        refs/heads/main
  11       7        refs/heads/main
  12       7        refs/heads/main
  13       11       refs/heads/main

清理旧序列号

Usage: git-ibundle clean [OPTIONS]

Options:
      --keep <KEEP>  Number of sequence numbers to retain [default: 20]
  -h, --help         Print help information
  -V, --version      Print version information
  -v, --verbose...   More output per occurrence
  -q, --quiet...     Less output per occurrence

默认情况下,git-ibundle保留所有序列号的元数据。使用git-ibundle clean来清理旧序列号。

与Git捆绑包的比较

git-ibundle的大部分工作由Git自身的捆绑功能处理。对于非增量镜像,Git的捆绑包提供了一种完整的解决方案。例如,以下命令将源仓库的全部内容打包成一个捆绑文件

# Run from within the source Git repository:
git bundle create ../repo.bundle --all

同样,在空的目标仓库中,以下命令从捆绑包中获取内容,并复制几乎整个仓库状态

# Run from within the destination Git repository:
git fetch --prune --force repo.bundle "*:*"

唯一缺失的是将HEAD设置为适当的符号分支名称,因为捆绑文件没有通信该分支名称的方法。但是,一对额外的命令可以处理这个问题。在源仓库中,通过以下方式查询HEAD

$ git symbolic-ref HEAD
refs/heads/main

然后,在目标仓库中,手动设置相应的HEAD,例如

git symbolic-ref HEAD refs/heads/main

Git还提供了一种方法通过在提交前添加一个前导撇号(^)来排除捆绑包中的提交。在源中main的单一附加提交后,可以创建一个新的捆绑文件,仅包含附加的提交(假设main是仓库中唯一的引用)

git bundle create ../repo.bundle --all ^HEAD~

捆绑包可能包含如下头部信息

# v2 git bundle
-9a3bbf283e30565d9ac378cb73c36ca8a417c5e0 Some commit log message
22a3d70042ecc8bce2772bfa85eadf64adb77441 refs/heads/main
22a3d70042ecc8bce2772bfa85eadf64adb77441 HEAD

提交9a3bbf2已在第一个捆绑包中发送,它已成为此增量捆绑文件的先决条件。Git出色地将所需引用和排除的范围精简到最小的一组先决条件和更改后的引用。

遗憾的是,Git 的先决条件始终是提交。注解标签指向标签对象,然后这些对象再指向提交。包文件没有方法表达标签对象作为先决条件。

此外,Git 将删除任何指向由任何 ^ 排除操作排除的对象的引用。假设一个包含大量 main 提交的仓库通过以下方式完全打包:

git bundle create ../repo.bundle --all

现在假设唯一的变化是在 HEAD 几个提交之前添加一个新分支

git branch branch1 HEAD~2

尝试请求将此新分支添加到新的增量包将失败

git bundle create ../repo.bundle branch1 ^main

这是因为 branch1 指向 main 的一个祖先,而 main 已被排除,导致 branch1 也被排除。

为了执行增量镜像,git-ibundle 使用源仓库中的 git bundle create 在每个同步点创建临时包文件。在包中包含了一组先决提交、一组新对象和一组指向新创建对象的引用列表。git-ibundle 然后从包文件中提取此信息,并将其与其他元数据结合起来创建一个 ibundle 文件;在目标仓库中,将 ibundle 与存储的仓库元数据结合,重建写入临时包文件的全套引用;此包使用 git fetch --prune --force temp.bundle "*:*" 应用到目标仓库;最后,根据 ibundle 文件中传达的值适当设置 HEAD

依赖关系

~15MB
~350K SLoC