Git以编程方式从远程存储库中获取单个文件

Chr*_*rle 9 git protocols

我会事先说这个问题在本质上与类似.有一个关键区别使得这个独特:我想使用原始git协议(如果您不熟悉基本包网络协议,请参阅此处此处).

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

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

是的,有"有"列表,它基本上会排除对象的下降,但是这需要对存储库内容的先验知识(我没有本地存储库,请记住).我可以生成所有可能sha1的列表,并发送除我想要的所有sha1之外的所有sha1,但这超出了荒谬(耗时,带宽消耗,并且对各地的程序员犯罪)

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

我非常愿意重写JGit,所以请不要将其视为"我如何让JGit做......".我只是想知道协议本身是否能够做到这一点.我觉得有一种非常聪明的方式滥用协议来实现我想要的东西.有什么想法吗?

Chr*_*rle 10

回答我自己的问题.我找到了一个可接受的(虽然几乎没有记录)答案.我不得不通过大量的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
Run Code Online (Sandbox Code Playgroud)

除了在软件中,希望使用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 ]
Run Code Online (Sandbox Code Playgroud)

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

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

此时建立连接.如果不支持该命令或者存在任何类型的问题,则可能会返回错误.我还没弄清楚如何检查这个.

接下来是未记录的部分.我们基本上通过线路发送命令行参数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发送了一个,则会跟随一个flush packet(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
Run Code Online (Sandbox Code Playgroud)

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