Skip to content

Commit 77cab8a

Browse files
committed
Merge branch 'it/fetch-pack-many-refs'
When "git fetch" encounters repositories with too many references, the command line of "fetch-pack" that is run by a helper e.g. remote-curl, may fail to hold all of them. Now such an internal invocation can feed the references through the standard input of "fetch-pack". By Ivan Todoroski * it/fetch-pack-many-refs: remote-curl: main test case for the OS command line overflow fetch-pack: test cases for the new --stdin option remote-curl: send the refs to fetch-pack on stdin fetch-pack: new --stdin option to read refs from stdin
2 parents 4de561c + 7103d25 commit 77cab8a

File tree

6 files changed

+162
-6
lines changed

6 files changed

+162
-6
lines changed

Documentation/git-fetch-pack.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ OPTIONS
3232
--all::
3333
Fetch all remote refs.
3434

35+
--stdin::
36+
Take the list of refs from stdin, one per line. If there
37+
are refs specified on the command line in addition to this
38+
option, then the refs from stdin are processed after those
39+
on the command line.
40+
+
41+
If '--stateless-rpc' is specified together with this option then
42+
the list of refs must be in packet format (pkt-line). Each ref must
43+
be in a separate packet, and the list must end with a flush packet.
44+
3545
-q::
3646
--quiet::
3747
Pass '-q' flag to 'git unpack-objects'; this makes the

builtin/fetch-pack.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ static struct fetch_pack_args args = {
2323
};
2424

2525
static const char fetch_pack_usage[] =
26-
"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
26+
"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
27+
"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
28+
"[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
2729

2830
#define COMPLETE (1U << 0)
2931
#define COMMON (1U << 1)
@@ -942,6 +944,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
942944
args.fetch_all = 1;
943945
continue;
944946
}
947+
if (!strcmp("--stdin", arg)) {
948+
args.stdin_refs = 1;
949+
continue;
950+
}
945951
if (!strcmp("-v", arg)) {
946952
args.verbose = 1;
947953
continue;
@@ -973,6 +979,40 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
973979
if (!dest)
974980
usage(fetch_pack_usage);
975981

982+
if (args.stdin_refs) {
983+
/*
984+
* Copy refs from cmdline to new growable list, then
985+
* append the refs from the standard input.
986+
*/
987+
int alloc_heads = nr_heads;
988+
int size = nr_heads * sizeof(*heads);
989+
heads = memcpy(xmalloc(size), heads, size);
990+
if (args.stateless_rpc) {
991+
/* in stateless RPC mode we use pkt-line to read
992+
* from stdin, until we get a flush packet
993+
*/
994+
static char line[1000];
995+
for (;;) {
996+
int n = packet_read_line(0, line, sizeof(line));
997+
if (!n)
998+
break;
999+
if (line[n-1] == '\n')
1000+
n--;
1001+
ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
1002+
heads[nr_heads++] = xmemdupz(line, n);
1003+
}
1004+
}
1005+
else {
1006+
/* read from stdin one ref per line, until EOF */
1007+
struct strbuf line = STRBUF_INIT;
1008+
while (strbuf_getline(&line, stdin, '\n') != EOF) {
1009+
ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
1010+
heads[nr_heads++] = strbuf_detach(&line, NULL);
1011+
}
1012+
strbuf_release(&line);
1013+
}
1014+
}
1015+
9761016
if (args.stateless_rpc) {
9771017
conn = NULL;
9781018
fd[0] = 0;

fetch-pack.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct fetch_pack_args {
1010
lock_pack:1,
1111
use_thin_pack:1,
1212
fetch_all:1,
13+
stdin_refs:1,
1314
verbose:1,
1415
no_progress:1,
1516
include_tag:1,

remote-curl.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ static void output_refs(struct ref *refs)
290290
struct rpc_state {
291291
const char *service_name;
292292
const char **argv;
293+
struct strbuf *stdin_preamble;
293294
char *service_url;
294295
char *hdr_content_type;
295296
char *hdr_accept;
@@ -535,6 +536,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
535536
{
536537
const char *svc = rpc->service_name;
537538
struct strbuf buf = STRBUF_INIT;
539+
struct strbuf *preamble = rpc->stdin_preamble;
538540
struct child_process client;
539541
int err = 0;
540542

@@ -545,6 +547,8 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
545547
client.argv = rpc->argv;
546548
if (start_command(&client))
547549
exit(1);
550+
if (preamble)
551+
write_or_die(client.in, preamble->buf, preamble->len);
548552
if (heads)
549553
write_or_die(client.in, heads->buf, heads->len);
550554

@@ -626,13 +630,14 @@ static int fetch_git(struct discovery *heads,
626630
int nr_heads, struct ref **to_fetch)
627631
{
628632
struct rpc_state rpc;
633+
struct strbuf preamble = STRBUF_INIT;
629634
char *depth_arg = NULL;
630-
const char **argv;
631635
int argc = 0, i, err;
636+
const char *argv[15];
632637

633-
argv = xmalloc((15 + nr_heads) * sizeof(char*));
634638
argv[argc++] = "fetch-pack";
635639
argv[argc++] = "--stateless-rpc";
640+
argv[argc++] = "--stdin";
636641
argv[argc++] = "--lock-pack";
637642
if (options.followtags)
638643
argv[argc++] = "--include-tag";
@@ -651,24 +656,27 @@ static int fetch_git(struct discovery *heads,
651656
argv[argc++] = depth_arg;
652657
}
653658
argv[argc++] = url;
659+
argv[argc++] = NULL;
660+
654661
for (i = 0; i < nr_heads; i++) {
655662
struct ref *ref = to_fetch[i];
656663
if (!ref->name || !*ref->name)
657664
die("cannot fetch by sha1 over smart http");
658-
argv[argc++] = ref->name;
665+
packet_buf_write(&preamble, "%s\n", ref->name);
659666
}
660-
argv[argc++] = NULL;
667+
packet_buf_flush(&preamble);
661668

662669
memset(&rpc, 0, sizeof(rpc));
663670
rpc.service_name = "git-upload-pack",
664671
rpc.argv = argv;
672+
rpc.stdin_preamble = &preamble;
665673
rpc.gzip_request = 1;
666674

667675
err = rpc_service(&rpc, heads);
668676
if (rpc.result.len)
669677
safe_write(1, rpc.result.buf, rpc.result.len);
670678
strbuf_release(&rpc.result);
671-
free(argv);
679+
strbuf_release(&preamble);
672680
free(depth_arg);
673681
return err;
674682
}

t/t5500-fetch-pack.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,70 @@ EOF
326326
test_cmp count7.expected count7.actual
327327
'
328328

329+
test_expect_success 'setup tests for the --stdin parameter' '
330+
for head in C D E F
331+
do
332+
add $head
333+
done &&
334+
for head in A B C D E F
335+
do
336+
git tag $head $head
337+
done &&
338+
cat >input <<-\EOF
339+
refs/heads/C
340+
refs/heads/A
341+
refs/heads/D
342+
refs/tags/C
343+
refs/heads/B
344+
refs/tags/A
345+
refs/heads/E
346+
refs/tags/B
347+
refs/tags/E
348+
refs/tags/D
349+
EOF
350+
sort <input >expect &&
351+
(
352+
echo refs/heads/E &&
353+
echo refs/tags/E &&
354+
cat input
355+
) >input.dup
356+
'
357+
358+
test_expect_success 'fetch refs from cmdline' '
359+
(
360+
cd client &&
361+
git fetch-pack --no-progress .. $(cat ../input)
362+
) >output &&
363+
cut -d " " -f 2 <output | sort >actual &&
364+
test_cmp expect actual
365+
'
366+
367+
test_expect_success 'fetch refs from stdin' '
368+
(
369+
cd client &&
370+
git fetch-pack --stdin --no-progress .. <../input
371+
) >output &&
372+
cut -d " " -f 2 <output | sort >actual &&
373+
test_cmp expect actual
374+
'
375+
376+
test_expect_success 'fetch mixed refs from cmdline and stdin' '
377+
(
378+
cd client &&
379+
tail -n +5 ../input |
380+
git fetch-pack --stdin --no-progress .. $(head -n 4 ../input)
381+
) >output &&
382+
cut -d " " -f 2 <output | sort >actual &&
383+
test_cmp expect actual
384+
'
385+
386+
test_expect_success 'test duplicate refs from stdin' '
387+
(
388+
cd client &&
389+
test_must_fail git fetch-pack --stdin --no-progress .. <../input.dup
390+
) >output &&
391+
cut -d " " -f 2 <output | sort >actual &&
392+
test_cmp expect actual
393+
'
394+
329395
test_done

t/t5551-http-fetch.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,36 @@ test_expect_success 'follow redirects (302)' '
109109
git clone $HTTPD_URL/smart-redir-temp/repo.git --quiet repo-t
110110
'
111111

112+
test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE
113+
114+
test_expect_success EXPENSIVE 'create 50,000 tags in the repo' '
115+
(
116+
cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
117+
for i in `seq 50000`
118+
do
119+
echo "commit refs/heads/too-many-refs"
120+
echo "mark :$i"
121+
echo "committer git <[email protected]> $i +0000"
122+
echo "data 0"
123+
echo "M 644 inline bla.txt"
124+
echo "data 4"
125+
echo "bla"
126+
# make every commit dangling by always
127+
# rewinding the branch after each commit
128+
echo "reset refs/heads/too-many-refs"
129+
echo "from :1"
130+
done | git fast-import --export-marks=marks &&
131+
132+
# now assign tags to all the dangling commits we created above
133+
tag=$(perl -e "print \"bla\" x 30") &&
134+
sed -e "s/^:\(.\+\) \(.\+\)$/\2 refs\/tags\/$tag-\1/" <marks >>packed-refs
135+
)
136+
'
137+
138+
test_expect_success EXPENSIVE 'clone the 50,000 tag repo to check OS command line overflow' '
139+
git clone $HTTPD_URL/smart/repo.git too-many-refs 2>err &&
140+
test_line_count = 0 err
141+
'
142+
112143
stop_httpd
113144
test_done

0 commit comments

Comments
 (0)