11

我会预先说这个问题本质上与this相似。有一个关键的区别使它与众不同:我想使用原始的 git 协议(如果您不熟悉基本的包网络协议,请参阅此处此处)。

我正在使用 Scala 和 JGit 编写一个将连接到匿名 git 存储库的应用程序。我想请求一个 blob(想想“/path/to/file.txt”@“refs/heads/branch1”)。最终,我的目标是以编程方式从远程存储库中检索单个文件。似乎是一件非常有用的事情。

不管怎样,我一直在研究这个协议的内部结构。看起来这个的基本版本是“我想要这些对象,我有这些对象”——而且 bam,有一个包含所有你没有的东西的包文件。我的问题的核心是:如何以非递归方式向 git-upload-packfile 询问单个对象?我可以下载单个提交对象,然后请求树,然后是子树,然后是另一个子树,最后是 blob 本身。速度在这里并不太重要,主要是我想节省带宽。但似乎根本没有办法告诉 git-upload-packfile,“请只给我我要求的一个对象”。

是的,有“拥有”列表,它基本上将对象排除在外,但是这需要对存储库内容的先验知识(记住,我没有本地存储库)。我可以生成所有可能的 sha1 列表并发送所有可能的 sha1,但我想要的除外,但这太荒谬了(耗时、消耗带宽,并且对各地程序员的犯罪行为)

我一直在研究的另一个可能的解决方案是在远程端使用 git-upload-archive,尽管我承认我还没有花太多时间研究它。

如果涉及到这一点,我非常愿意重写 JGit,所以请不要把它读作“我如何让 JGit 做......”。我只想知道协议本身是否能够做到这一点。我觉得有一些非常聪明的方法可以滥用协议来实现我想要的。有什么想法吗?

4

1 回答 1

13

回答我自己的问题。我找到了一个可接受的(虽然几乎没有记录)答案。我不得不通过大量的 C 代码来解决这个问题。

首先,使用无法实现上述要求,git-upload-packfile因为这根本不是程序设计的目的。我怀疑的正确答案是git-upload-archive。可悲的是,该协议几乎没有记录。所以这是我的笔记,以防其他人有类似的要求。

基本上我在这里(在scala中)试图模拟的是以下命令:

git archive --format=tar --remote=ssh://dave@ssh.mycompany.com/cornballer.git \
  > master plans/documents/cornballer-blueprint.pdf | tar -x

除了在软件中,希望使用 JGit。遗憾的是,JGit (还)不支持 git archive 命令。所以这里有一个关于如何添加支持的非常高级的概述(我可能会分叉 JGit 并在以后添加它)。

让我们看一下协议(来自 Documentation/technical/pack-protocol.txt):

git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
request-command   = "git-upload-pack" / "git-receive-pack" /
                    "git-upload-archive"   ; case sensitive
pathname          = *( %x01-ff ) ; exclude NUL
host-parameter    = "host=" hostname [ ":" port ]

所以协议的第一部分是这样的:

  1. 与远程建立传输(ssh 然后运行git-upload-archive或使用匿名 git 协议)
  2. 发送git-upload-archive /cornballer.git\0host=ssh.mycompany.com\0(作为数据包线路)

至此,连接建立。如果该命令不受支持或存在任何问题,Git 可能会返回错误。我还没有弄清楚如何检查这个。

接下来是未记录的部分。我们基本上通过网络发送命令行参数git-archive。它们与命令完全相同,但git-archive有一个例外:它们都以 . 为前缀argument[SPACE]。每个参数都写成(至少在参考实现中)作为单独的数据包行。所以对于上面的例子:

  1. 发送argument --format=tar(作为数据包线路)
  2. 发送argument master(作为数据包线路)
  3. 发送argument plans/documents/cornballer-blueprint.pdf(作为数据包线路)
  4. 发送一个刷新数据包 ( 0000)

至此,我们已经为远程 git-archive 进程提供了整个命令。现在我们阅读响应。我们从服务器读回一个数据包行,这将是以下响应之一:

  1. ACK(意思是成功——准备发送存档)
  2. NACK [message]-- 某种错误,只找到了一个使用实例 -- “无法生成子进程”
  3. ERR [message]-- 发生错误

如果ACK发送了 an ,后面会跟着一个刷新数据包 ( 0000),然后是原始 tar 数据。此时,您重复读取来自边带#1(主数据通道)的数据包行。当你到达一个刷新数据包时,你停止阅读。很简单。

所以现在你有了远程文件,但是如果你想做一些巧妙的缓存呢?我如此热衷于使用的一个原因git-upload-packfile是它可以让我记录提交 ID,从而将其缓存在本地,并且只在需要时刷新。tar 文件不会告诉我们这些信息,对吧?错误的!

从 git-archive 的手册页:

此外,如果使用 tar 格式,则提交 ID 存储在全局扩展 pax 标头中;它可以使用 git get-tar-commit-id 提取。在 ZIP 文件中,它被存储为文件注释。

嗯,这是个好消息!这就是我想要的一切。如果您想知道标头是什么样的,这里有一个示例(不,我不会剖析 pax 标头):

pax_global_header00006660000000000000000000000064121002672560014513gustar00rootroot0000000000000052 comment=326756f834865880c9832b64238e7665632e9b67

所以从我的角度来看,我只需要设置一个管道来自动运行上述步骤,通过一个 untar 步骤(以编程方式)运行它来执行所需的“从 git 获取单个文件”功能。

于 2013-01-30T17:47:57.677 回答