@matheusbsbs / matheus-bagliano / commits / 7f3ecebfcd
Fork 0

git-remote-ext

This remote helper invokes external command and passes raw smart transport stream through it. This is useful for instance for invoking ssh with one-off odd options, connecting to git services in unix domain sockets, in abstract namespace, using TLS or other secure protocols, etc... Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ilari Liusvaara committed Oct 12, 2010 at 19:39 UTC 7f3ecebfcda9b32252a4791dc716c1e3d88c9f70
6 files changed +377
.gitignore
+1
@@ -113,6 +113,7 @@
113 /git-remote-ftp
114 /git-remote-ftps
115 /git-remote-fd
116 +/git-remote-ext
117 /git-remote-testgit
118 /git-repack
119 /git-replace
Documentation/git-remote-ext.txt new
+125
@@ -0,0 +1,125 @@
1 +git-remote-ext(1)
2 +=================
3 +
4 +NAME
5 +----
6 +git-remote-ext - Bridge smart transport to external command.
7 +
8 +SYNOPSIS
9 +--------
10 +git remote add nick "ext::<command>[ <arguments>...]"
11 +
12 +DESCRIPTION
13 +-----------
14 +This remote helper uses the specified 'program' to connect
15 +to a remote git server.
16 +
17 +Data written to stdin of this specified 'program' is assumed
18 +to be sent to git:// server, git-upload-pack, git-receive-pack
19 +or git-upload-archive (depending on situation), and data read
20 +from stdout of this program is assumed to be received from
21 +the same service.
22 +
23 +Command and arguments are separated by unescaped space.
24 +
25 +The following sequences have a special meaning:
26 +
27 +'% '::
28 + Literal space in command or argument.
29 +
30 +'%%'::
31 + Literal percent sign.
32 +
33 +'%s'::
34 + Replaced with name (receive-pack, upload-pack, or
35 + upload-archive) of the service git wants to invoke.
36 +
37 +'%S'::
38 + Replaced with long name (git-receive-pack,
39 + git-upload-pack, or git-upload-archive) of the service
40 + git wants to invoke.
41 +
42 +'%G<repository>' (as argument)::
43 + This argument will not be passed to 'program'. Instead, it
44 + will cause helper to start by sending git:// service request to
45 + remote side with service field set to approiate value and
46 + repository field set to <repository>. Default is not to send
47 + such request.
48 ++
49 +This is useful if remote side is git:// server accessed over
50 +some tunnel.
51 +
52 +'%V<host>' (as argument)::
53 + This argument will not be passed to 'program'. Instead it sets
54 + the vhost field in git:// service request. Default is not to
55 + send vhost in such request (if sent).
56 +
57 +ENVIRONMENT VARIABLES:
58 +----------------------
59 +
60 +GIT_TRANSLOOP_DEBUG::
61 + If set, prints debugging information about various reads/writes.
62 +
63 +ENVIRONMENT VARIABLES PASSED TO COMMAND:
64 +----------------------------------------
65 +
66 +GIT_EXT_SERVICE::
67 + Set to long name (git-upload-pack, etc...) of service helper needs
68 + to invoke.
69 +
70 +GIT_EXT_SERVICE_NOPREFIX::
71 + Set to long name (upload-pack, etc...) of service helper needs
72 + to invoke.
73 +
74 +
75 +EXAMPLES:
76 +---------
77 +This remote helper is transparently used by git when
78 +you use commands such as "git fetch <URL>", "git clone <URL>",
79 +, "git push <URL>" or "git remote add nick <URL>", where <URL>
80 +begins with `ext::`. Examples:
81 +
82 +"ext::ssh -i /home/foo/.ssh/somekey user&#64;host.example %S 'foo/repo'"::
83 + Like host.example:foo/repo, but use /home/foo/.ssh/somekey as
84 + keypair and user as user on remote side. This avoids needing to
85 + edit .ssh/config.
86 +
87 +"ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo"::
88 + Represents repository with path /somerepo accessable over
89 + git protocol at abstract namespace address /git-server.
90 +
91 +"ext::git-server-alias foo %G/repo"::
92 + Represents a repository with path /repo accessed using the
93 + helper program "git-server-alias foo". The path to the
94 + repository and type of request are not passed on the command
95 + line but as part of the protocol stream, as usual with git://
96 + protocol.
97 +
98 +"ext::git-server-alias foo %G/repo %Vfoo"::
99 + Represents a repository with path /repo accessed using the
100 + helper program "git-server-alias foo". The hostname for the
101 + remote server passed in the protocol stream will be "foo"
102 + (this allows multiple virtual git servers to share a
103 + link-level address).
104 +
105 +"ext::git-server-alias foo %G/repo% with% spaces %Vfoo"::
106 + Represents a repository with path '/repo with spaces' accessed
107 + using the helper program "git-server-alias foo". The hostname for
108 + the remote server passed in the protocol stream will be "foo"
109 + (this allows multiple virtual git servers to share a
110 + link-level address).
111 +
112 +"ext::git-ssl foo.example /bar"::
113 + Represents a repository accessed using the helper program
114 + "git-ssl foo.example /bar". The type of request can be
115 + determined by the helper using environment variables (see
116 + above).
117 +
118 +Documentation
119 +--------------
120 +Documentation by Ilari Liusvaara, Jonathan Nieder and the git list
121 +<git@vger.kernel.org>
122 +
123 +GIT
124 +---
125 +Part of the linkgit:git[1] suite
Makefile
+1
@@ -723,6 +723,7 @@ BUILTIN_OBJS += builtin/read-tree.o
723 BUILTIN_OBJS += builtin/receive-pack.o
724 BUILTIN_OBJS += builtin/reflog.o
725 BUILTIN_OBJS += builtin/remote.o
726 +BUILTIN_OBJS += builtin/remote-ext.o
727 BUILTIN_OBJS += builtin/remote-fd.o
728 BUILTIN_OBJS += builtin/replace.o
729 BUILTIN_OBJS += builtin/rerere.o
builtin.h
+1
@@ -107,6 +107,7 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
107 extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
108 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
109 extern int cmd_remote(int argc, const char **argv, const char *prefix);
110 +extern int cmd_remote_ext(int argc, const char **argv, const char *prefix);
111 extern int cmd_remote_fd(int argc, const char **argv, const char *prefix);
112 extern int cmd_config(int argc, const char **argv, const char *prefix);
113 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
builtin/remote-ext.c new
+248
@@ -0,0 +1,248 @@
1 +#include "git-compat-util.h"
2 +#include "transport.h"
3 +#include "run-command.h"
4 +
5 +/*
6 + * URL syntax:
7 + * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
8 + * Special characters:
9 + * '% ': Literal space in argument.
10 + * '%%': Literal percent sign.
11 + * '%S': Name of service (git-upload-pack/git-upload-archive/
12 + * git-receive-pack.
13 + * '%s': Same as \s, but with possible git- prefix stripped.
14 + * '%G': Only allowed as first 'character' of argument. Do not pass this
15 + * Argument to command, instead send this as name of repository
16 + * in in-line git://-style request (also activates sending this
17 + * style of request).
18 + * '%V': Only allowed as first 'character' of argument. Used in
19 + * conjunction with '%G': Do not pass this argument to command,
20 + * instead send this as vhost in git://-style request (note: does
21 + * not activate sending git:// style request).
22 + */
23 +
24 +static char *git_req;
25 +static char *git_req_vhost;
26 +
27 +static char *strip_escapes(const char *str, const char *service,
28 + const char **next)
29 +{
30 + size_t rpos = 0;
31 + int escape = 0;
32 + char special = 0;
33 + size_t pslen = 0;
34 + size_t pSlen = 0;
35 + size_t psoff = 0;
36 + struct strbuf ret = STRBUF_INIT;
37 +
38 + /* Calculate prefix length for \s and lengths for \s and \S */
39 + if (!strncmp(service, "git-", 4))
40 + psoff = 4;
41 + pSlen = strlen(service);
42 + pslen = pSlen - psoff;
43 +
44 + /* Pass the service to command. */
45 + setenv("GIT_EXT_SERVICE", service, 1);
46 + setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
47 +
48 + /* Scan the length of argument. */
49 + while (str[rpos] && (escape || str[rpos] != ' ')) {
50 + if (escape) {
51 + switch (str[rpos]) {
52 + case ' ':
53 + case '%':
54 + case 's':
55 + case 'S':
56 + break;
57 + case 'G':
58 + case 'V':
59 + special = str[rpos];
60 + if (rpos == 1)
61 + break;
62 + /* Fall-through to error. */
63 + default:
64 + die("Bad remote-ext placeholder '%%%c'.",
65 + str[rpos]);
66 + }
67 + escape = 0;
68 + } else
69 + escape = (str[rpos] == '%');
70 + rpos++;
71 + }
72 + if (escape && !str[rpos])
73 + die("remote-ext command has incomplete placeholder");
74 + *next = str + rpos;
75 + if (**next == ' ')
76 + ++*next; /* Skip over space */
77 +
78 + /*
79 + * Do the actual placeholder substitution. The string will be short
80 + * enough not to overflow integers.
81 + */
82 + rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
83 + escape = 0;
84 + while (str[rpos] && (escape || str[rpos] != ' ')) {
85 + if (escape) {
86 + switch (str[rpos]) {
87 + case ' ':
88 + case '%':
89 + strbuf_addch(&ret, str[rpos]);
90 + break;
91 + case 's':
92 + strbuf_addstr(&ret, service + psoff);
93 + break;
94 + case 'S':
95 + strbuf_addstr(&ret, service);
96 + break;
97 + }
98 + escape = 0;
99 + } else
100 + switch (str[rpos]) {
101 + case '%':
102 + escape = 1;
103 + break;
104 + default:
105 + strbuf_addch(&ret, str[rpos]);
106 + break;
107 + }
108 + rpos++;
109 + }
110 + switch (special) {
111 + case 'G':
112 + git_req = strbuf_detach(&ret, NULL);
113 + return NULL;
114 + case 'V':
115 + git_req_vhost = strbuf_detach(&ret, NULL);
116 + return NULL;
117 + default:
118 + return strbuf_detach(&ret, NULL);
119 + }
120 +}
121 +
122 +/* Should be enough... */
123 +#define MAXARGUMENTS 256
124 +
125 +static const char **parse_argv(const char *arg, const char *service)
126 +{
127 + int arguments = 0;
128 + int i;
129 + const char **ret;
130 + char *temparray[MAXARGUMENTS + 1];
131 +
132 + while (*arg) {
133 + char *expanded;
134 + if (arguments == MAXARGUMENTS)
135 + die("remote-ext command has too many arguments");
136 + expanded = strip_escapes(arg, service, &arg);
137 + if (expanded)
138 + temparray[arguments++] = expanded;
139 + }
140 +
141 + ret = xmalloc((arguments + 1) * sizeof(char *));
142 + for (i = 0; i < arguments; i++)
143 + ret[i] = temparray[i];
144 + ret[arguments] = NULL;
145 + return (const char **)ret;
146 +}
147 +
148 +static void send_git_request(int stdin_fd, const char *serv, const char *repo,
149 + const char *vhost)
150 +{
151 + size_t bufferspace;
152 + size_t wpos = 0;
153 + char *buffer;
154 +
155 + /*
156 + * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
157 + * 6 bytes extra (xxxx \0) if there is no vhost.
158 + */
159 + if (vhost)
160 + bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
161 + else
162 + bufferspace = strlen(serv) + strlen(repo) + 6;
163 +
164 + if (bufferspace > 0xFFFF)
165 + die("Request too large to send");
166 + buffer = xmalloc(bufferspace);
167 +
168 + /* Make the packet. */
169 + wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
170 + serv, repo, 0);
171 +
172 + /* Add vhost if any. */
173 + if (vhost)
174 + sprintf(buffer + wpos, "host=%s%c", vhost, 0);
175 +
176 + /* Send the request */
177 + if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
178 + die_errno("Failed to send request");
179 +
180 + free(buffer);
181 +}
182 +
183 +static int run_child(const char *arg, const char *service)
184 +{
185 + int r;
186 + struct child_process child;
187 +
188 + memset(&child, 0, sizeof(child));
189 + child.in = -1;
190 + child.out = -1;
191 + child.err = 0;
192 + child.argv = parse_argv(arg, service);
193 +
194 + if (start_command(&child) < 0)
195 + die("Can't run specified command");
196 +
197 + if (git_req)
198 + send_git_request(child.in, service, git_req, git_req_vhost);
199 +
200 + r = bidirectional_transfer_loop(child.out, child.in);
201 + if (!r)
202 + r = finish_command(&child);
203 + else
204 + finish_command(&child);
205 + return r;
206 +}
207 +
208 +#define MAXCOMMAND 4096
209 +
210 +static int command_loop(const char *child)
211 +{
212 + char buffer[MAXCOMMAND];
213 +
214 + while (1) {
215 + size_t length;
216 + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
217 + if (ferror(stdin))
218 + die("Comammand input error");
219 + exit(0);
220 + }
221 + /* Strip end of line characters. */
222 + length = strlen(buffer);
223 + while (isspace((unsigned char)buffer[length - 1]))
224 + buffer[--length] = 0;
225 +
226 + if (!strcmp(buffer, "capabilities")) {
227 + printf("*connect\n\n");
228 + fflush(stdout);
229 + } else if (!strncmp(buffer, "connect ", 8)) {
230 + printf("\n");
231 + fflush(stdout);
232 + return run_child(child, buffer + 8);
233 + } else {
234 + fprintf(stderr, "Bad command");
235 + return 1;
236 + }
237 + }
238 +}
239 +
240 +int cmd_remote_ext(int argc, const char **argv, const char *prefix)
241 +{
242 + if (argc < 3) {
243 + fprintf(stderr, "Error: URL missing");
244 + exit(1);
245 + }
246 +
247 + return command_loop(argv[2]);
248 +}
git.c
+1
@@ -374,6 +374,7 @@ static void handle_internal_command(int argc, const char **argv)
374 { "receive-pack", cmd_receive_pack },
375 { "reflog", cmd_reflog, RUN_SETUP },
376 { "remote", cmd_remote, RUN_SETUP },
377 + { "remote-ext", cmd_remote_ext },
378 { "remote-fd", cmd_remote_fd },
379 { "replace", cmd_replace, RUN_SETUP },
380 { "repo-config", cmd_config, RUN_SETUP_GENTLY },