Support remote helpers implementing smart transports
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Ilari Liusvaara committed
Dec 9, 2009 at 17:26 UTC
fa8c097cc9b416283ae4c1c1507909ff207b7f60
2 files changed
+142
-7
Documentation/git-remote-helpers.txt
+24
-1
@@ -93,6 +93,20 @@ Supported if the helper has the "push" capability.
93
+
94
Supported if the helper has the "import" capability.
95
96
+'connect' <service>::
97
+ Connects to given service. Standard input and standard output
98
+ of helper are connected to specified service (git prefix is
99
+ included in service name so e.g. fetching uses 'git-upload-pack'
100
+ as service) on remote side. Valid replies to this command are
101
+ empty line (connection established), 'fallback' (no smart
102
+ transport support, fall back to dumb transports) and just
103
+ exiting with error message printed (can't connect, don't
104
+ bother trying to fall back). After line feed terminating the
105
+ positive (empty) response, the output of service starts. After
106
+ the connection ends, the remote helper exits.
107
++
108
+Supported if the helper has the "connect" capability.
109
+
110
If a fatal error occurs, the program writes the error message to
111
stderr and exits. The caller should expect that a suitable error
112
message has been printed if the child closes the connection without
@@ -126,6 +140,9 @@ CAPABILITIES
140
all, it must cover all refs reported by the list command; if
141
it is not used, it is effectively "*:*"
142
143
+'connect'::
144
+ This helper supports the 'connect' command.
145
+
146
REF LIST ATTRIBUTES
147
-------------------
148
@@ -168,9 +185,15 @@ OPTIONS
185
but don't actually change any repository data. For most
186
helpers this only applies to the 'push', if supported.
187
188
+'option servpath <c-style-quoted-path>'::
189
+ Set service path (--upload-pack, --receive-pack etc.) for
190
+ next connect. Remote helper MAY support this option. Remote
191
+ helper MUST NOT rely on this option being set before
192
+ connect request occurs.
193
+
194
Documentation
195
-------------
173
-Documentation by Daniel Barkalow.
196
+Documentation by Daniel Barkalow and Ilari Liusvaara
197
198
GIT
199
---
transport-helper.c
+118
-6
@@ -18,7 +18,9 @@ struct helper_data
18
unsigned fetch : 1,
19
import : 1,
20
option : 1,
21
- push : 1;
21
+ push : 1,
22
+ connect : 1,
23
+ no_disconnect_req : 1;
24
/* These go from remote name (as in "list") to private name */
25
struct refspec *refspecs;
26
int refspec_nr;
@@ -37,12 +39,12 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer)
39
die_errno("Full write to remote helper failed");
40
}
41
40
-static int recvline(struct helper_data *helper, struct strbuf *buffer)
42
+static int recvline_fh(FILE *helper, struct strbuf *buffer)
43
{
44
strbuf_reset(buffer);
45
if (debug)
46
fprintf(stderr, "Debug: Remote helper: Waiting...\n");
45
- if (strbuf_getline(buffer, helper->out, '\n') == EOF) {
47
+ if (strbuf_getline(buffer, helper, '\n') == EOF) {
48
if (debug)
49
fprintf(stderr, "Debug: Remote helper quit.\n");
50
exit(128);
@@ -53,6 +55,11 @@ static int recvline(struct helper_data *helper, struct strbuf *buffer)
55
return 0;
56
}
57
58
+static int recvline(struct helper_data *helper, struct strbuf *buffer)
59
+{
60
+ return recvline_fh(helper->out, buffer);
61
+}
62
+
63
static void xchgline(struct helper_data *helper, struct strbuf *buffer)
64
{
65
sendline(helper, buffer);
@@ -77,6 +84,15 @@ const char *remove_ext_force(const char *url)
84
return url;
85
}
86
87
+static void do_take_over(struct transport *transport)
88
+{
89
+ struct helper_data *data;
90
+ data = (struct helper_data *)transport->data;
91
+ transport_take_over(transport, data->helper);
92
+ fclose(data->out);
93
+ free(data);
94
+}
95
+
96
static struct child_process *get_helper(struct transport *transport)
97
{
98
struct helper_data *data = transport->data;
@@ -103,12 +119,12 @@ static struct child_process *get_helper(struct transport *transport)
119
if (start_command(helper))
120
die("Unable to run helper: git %s", helper->argv[0]);
121
data->helper = helper;
122
+ data->no_disconnect_req = 0;
123
124
/*
125
* Open the output as FILE* so strbuf_getline() can be used.
126
* Do this with duped fd because fclose() will close the fd,
127
* and stuff like taking over will require the fd to remain.
111
- *
128
*/
129
duped = dup(helper->out);
130
if (duped < 0)
@@ -146,6 +162,8 @@ static struct child_process *get_helper(struct transport *transport)
162
refspec_nr + 1,
163
refspec_alloc);
164
refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
165
+ } else if (!strcmp(capname, "connect")) {
166
+ data->connect = 1;
167
} else if (mandatory) {
168
die("Unknown madatory capability %s. This remote "
169
"helper probably needs newer version of Git.\n",
@@ -175,8 +193,10 @@ static int disconnect_helper(struct transport *transport)
193
if (data->helper) {
194
if (debug)
195
fprintf(stderr, "Debug: Disconnecting.\n");
178
- strbuf_addf(&buf, "\n");
179
- sendline(data, &buf);
196
+ if (!data->no_disconnect_req) {
197
+ strbuf_addf(&buf, "\n");
198
+ sendline(data, &buf);
199
+ }
200
close(data->helper->in);
201
close(data->helper->out);
202
fclose(data->out);
@@ -370,12 +390,94 @@ static int fetch_with_import(struct transport *transport,
390
return 0;
391
}
392
393
+static int process_connect_service(struct transport *transport,
394
+ const char *name, const char *exec)
395
+{
396
+ struct helper_data *data = transport->data;
397
+ struct strbuf cmdbuf = STRBUF_INIT;
398
+ struct child_process *helper;
399
+ int r, duped, ret = 0;
400
+ FILE *input;
401
+
402
+ helper = get_helper(transport);
403
+
404
+ /*
405
+ * Yes, dup the pipe another time, as we need unbuffered version
406
+ * of input pipe as FILE*. fclose() closes the underlying fd and
407
+ * stream buffering only can be changed before first I/O operation
408
+ * on it.
409
+ */
410
+ duped = dup(helper->out);
411
+ if (duped < 0)
412
+ die_errno("Can't dup helper output fd");
413
+ input = xfdopen(duped, "r");
414
+ setvbuf(input, NULL, _IONBF, 0);
415
+
416
+ /*
417
+ * Handle --upload-pack and friends. This is fire and forget...
418
+ * just warn if it fails.
419
+ */
420
+ if (strcmp(name, exec)) {
421
+ r = set_helper_option(transport, "servpath", exec);
422
+ if (r > 0)
423
+ warning("Setting remote service path not supported by protocol.");
424
+ else if (r < 0)
425
+ warning("Invalid remote service path.");
426
+ }
427
+
428
+ if (data->connect)
429
+ strbuf_addf(&cmdbuf, "connect %s\n", name);
430
+ else
431
+ goto exit;
432
+
433
+ sendline(data, &cmdbuf);
434
+ recvline_fh(input, &cmdbuf);
435
+ if (!strcmp(cmdbuf.buf, "")) {
436
+ data->no_disconnect_req = 1;
437
+ if (debug)
438
+ fprintf(stderr, "Debug: Smart transport connection "
439
+ "ready.\n");
440
+ ret = 1;
441
+ } else if (!strcmp(cmdbuf.buf, "fallback")) {
442
+ if (debug)
443
+ fprintf(stderr, "Debug: Falling back to dumb "
444
+ "transport.\n");
445
+ } else
446
+ die("Unknown response to connect: %s",
447
+ cmdbuf.buf);
448
+
449
+exit:
450
+ fclose(input);
451
+ return ret;
452
+}
453
+
454
+static int process_connect(struct transport *transport,
455
+ int for_push)
456
+{
457
+ struct helper_data *data = transport->data;
458
+ const char *name;
459
+ const char *exec;
460
+
461
+ name = for_push ? "git-receive-pack" : "git-upload-pack";
462
+ if (for_push)
463
+ exec = data->transport_options.receivepack;
464
+ else
465
+ exec = data->transport_options.uploadpack;
466
+
467
+ return process_connect_service(transport, name, exec);
468
+}
469
+
470
static int fetch(struct transport *transport,
471
int nr_heads, struct ref **to_fetch)
472
{
473
struct helper_data *data = transport->data;
474
int i, count;
475
476
+ if (process_connect(transport, 0)) {
477
+ do_take_over(transport);
478
+ return transport->fetch(transport, nr_heads, to_fetch);
479
+ }
480
+
481
count = 0;
482
for (i = 0; i < nr_heads; i++)
483
if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
@@ -403,6 +505,11 @@ static int push_refs(struct transport *transport,
505
struct child_process *helper;
506
struct ref *ref;
507
508
+ if (process_connect(transport, 1)) {
509
+ do_take_over(transport);
510
+ return transport->push_refs(transport, remote_refs, flags);
511
+ }
512
+
513
if (!remote_refs)
514
return 0;
515
@@ -543,6 +650,11 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
650
651
helper = get_helper(transport);
652
653
+ if (process_connect(transport, for_push)) {
654
+ do_take_over(transport);
655
+ return transport->get_refs_list(transport, for_push);
656
+ }
657
+
658
if (data->push && for_push)
659
write_str_in_full(helper->in, "list for-push\n");
660
else