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@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 },