Feature image

Git私有传输协议实现-接口篇(1)

Git默认支持http, https, ssh协议,同时也提供了扩展私有协议的方法,文档git-remote-helpers给出了详细的spec。

比如要实现一种协议,把git repository存储(可加密)到私人的email邮箱中,以存储一些不便于host到GitHub的私人repo,同时免去购买主机/服务器的成本和维护带来的麻烦,Repository的clone url格式定义为mail://your@email.com:repo_name

调用

使用git命令clone, 现在什么都没实现,所以理所当然的报错:

1
$ git clone mail://akfish@gmail.com:foo
fatal: Unable to find remote helper for 'mail'

根据文档的描述:

When git encounters a URL of the form <transport>://<address>, where <transport> is a protocol that it cannot handle natively, it automatically invokes git remote-<transport> with the full URL as the second argument. If such a URL is encountered directly on the command line, the first argument is the same as the second, and if it is encountered in a configured remote, the first argument is the name of that remote.

即git会把url中mail://映射到调用命令git-remote-mail,所以只需要用任何开发语言实现一个标准输入输出的命令行程序,满足文档中定义的命令格式,放在git能搜寻到的位置,就能让git支持私有协议(注意python在windows下存在stdout无法被重定向的问题,无法和git正确通信)。本例中用C#实现,创建控制台程序git-remote-mail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace git_remote_mail
{
class Program
{
static void Main(string[] args)
{

Logger logger = new Logger("git-remote-mail.txt");

logger.Log("args: " + String.Join(", ", args));

logger.Dispose();
}
}
}

再次执行clone,错误提示消失,由于这个脚本什么事情也没做,所以当然也就什么都不会发生。stdin和stdout被用于与git通信,不会显示出来。如果需要输出消息,write到stderr,或者产生一个日志文件来记录,Logger类实现了相应的功能,具体代码见Logger.cs。执行clone命令后,输出为:

1
[2013/10/20 22:07:02]Open log file git-remote-mail.txt
[2013/10/20 22:07:02]args: origin, mail://akfish@gmail.com:foo
[2013/10/20 22:07:02]Close log file

可以看到,在调用的时候,还传入了两个参数:origin和mail://akfish@gmail.com:foo,根据文档:

Remote helper programs are invoked with one or (optionally) two arguments. The first argument specifies a remote repository as in git; it is either the name of a configured remote or a URL. The second argument specifies a URL; it is usually of the form <transport>://<address>, but any arbitrary string is possible.

参数的数量为1~2个,第一个参数为repo的名字或者url,第二个参数如果存在,为repo的url。

命令流

Git通过stdin向remote helper发送命令,一行一个,第一个命令总是capabilities。Remote helper需要通过stdout返回支持的capabilities,每行一个,以空行结束。Capabilities代表helper支持哪些命令子集,如fetch需要支持connect, fetch, import,详细的列表在文档里有列出。

命令流通常以空行结束,但在某些情况下空行后会跟着其它协议的payload(如pack),具体参见command的具体说明。要注意的是命令流用的是linux-style line ending,即以\n结尾,如果使用Console.WriteLine产生的是DOS line ending(\r\n),则不能正确工作。

增加代码响应capabilities命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//.... 
static int Main(string[] args)
{

Logger logger = new Logger("git-remote-mail.txt");

logger.Log("args: " + String.Join(", ", args));

string line;
int exitCode = 0;
while (true)
{
line = Console.ReadLine();

logger.Log("&gt;&gt;" + line);

if (string.IsNullOrEmpty(line))
{
logger.Log("Command stream terminated");
exitCode = 0;
break;
}

if (line.StartsWith("capabilities"))
{
Console.Write("connect\n");
Console.Write("fetch\n");
Console.Write("import\n");
Console.Write("\n");
logger.Log("&lt;&lt;connect, fetch, import");
}
else
{
logger.Log("Unhandled command. Exit");
exitCode = 1;
break;
}
}

logger.Dispose();
return exitCode;
}

//...

输出为:

1
[2013/10/20 22:28:53]Open log file git-remote-mail.txt
[2013/10/20 22:28:53]args: origin, mail://akfish@gmail.com:foo
[2013/10/20 22:28:53]&gt;&gt;capabilities
[2013/10/20 22:28:53]&lt;&lt;connect, fetch, import
[2013/10/20 22:28:53]&gt;&gt;connect git-upload-pack
[2013/10/20 22:28:53]Unhandled command. Exit
[2013/10/20 22:28:53]Close log file

表明命令流已经成功初始化,git继续发出connect命令开始clone的工作。

接口部分就这么简单,接下来的工作就是根据文档的描述,响应具体的命令,完成协议的具体设计。

更多参考资料

Git的repo中包含了大量文档,都是很好的参考资料

1
$ git clone https://github.com/git/git
$ cd git/Documentation
$ grep -nRHI "receive-pack" *

会给出这些文档:

以下文档是相关的后端命令,作为补充:

查看源代码中与传输协议相关的commit:

1
$ git clone https://github.com/git/git
$ cd git
$ git log -Stransfer

可以参见以下commits: