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

时间:2013-01-18 19:01:53

标签: git protocols

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

我正在编写一个使用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做......”。我只是想知道协议本身是否能够做到这一点。我觉得有一种非常聪明的方式滥用协议来实现我想要的东西。有什么想法吗?

1 个答案:

答案 0 :(得分: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

除了软件,希望使用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(作为数据包行)
  3. 此时建立连接。如果不支持该命令或者存在任何类型的问题,则可能会返回错误。我还没弄清楚如何检查这个。

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

    1. 发送argument --format=tar(作为数据包行)
    2. 发送argument master(作为数据包行)
    3. 发送argument plans/documents/cornballer-blueprint.pdf(作为数据包行)
    4. 发送一个刷新数据包(0000
    5. 此时我们已经为远程git-archive过程提供了整个命令。现在我们阅读回复。我们从服务器读回一个数据包行,这将是以下响应之一:

      1. ACK(意味着成功 - 准备好发送档案)
      2. NACK [message] - 某种错误,只发现了一个使用它的实例 - “无法生成子进程”
      3. ERR [message] - 发生错误
      4. 如果发送ACK,则会跟随一个刷新数据包(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
        

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