回答我自己的问题。我找到了一个可接受的(虽然几乎没有记录)答案。我不得不通过大量的 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 ]
所以协议的第一部分是这样的:
- 与远程建立传输(ssh 然后运行
git-upload-archive
或使用匿名 git 协议)
- 发送
git-upload-archive /cornballer.git\0host=ssh.mycompany.com\0
(作为数据包线路)
至此,连接建立。如果该命令不受支持或存在任何问题,Git 可能会返回错误。我还没有弄清楚如何检查这个。
接下来是未记录的部分。我们基本上通过网络发送命令行参数git-archive
。它们与命令完全相同,但git-archive
有一个例外:它们都以 . 为前缀argument[SPACE]
。每个参数都写成(至少在参考实现中)作为单独的数据包行。所以对于上面的例子:
- 发送
argument --format=tar
(作为数据包线路)
- 发送
argument master
(作为数据包线路)
- 发送
argument plans/documents/cornballer-blueprint.pdf
(作为数据包线路)
- 发送一个刷新数据包 (
0000
)
至此,我们已经为远程 git-archive 进程提供了整个命令。现在我们阅读响应。我们从服务器读回一个数据包行,这将是以下响应之一:
ACK
(意思是成功——准备发送存档)
NACK [message]
-- 某种错误,只找到了一个使用实例 -- “无法生成子进程”
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 获取单个文件”功能。