From b7b021701cea6c9e54c826e1f6bc5faa9d3fef53 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 14:53:33 -0500 Subject: [PATCH 01/20] upload-pack: use get_sha1_hex to parse "shallow" lines When we receive a line like "shallow " from the client, we feed the part to get_sha1. This is a mistake, as the argument on a shallow line is defined by Documentation/technical/pack-protocol.txt to contain an "obj-id". This is never defined in the BNF, but it is clear from the text and from the other uses that it is meant to be a hex sha1, not an arbitrary identifier (and that is what fetch-pack has always sent). We should be using get_sha1_hex instead, which doesn't allow the client to request arbitrary junk like "HEAD@{yesterday}". Because this is just marking shallow objects, the client couldn't actually do anything interesting (like fetching objects from unreachable reflog entries), but we should keep our parsing tight to be on the safe side. Because get_sha1 is for the most part a superset of get_sha1_hex, in theory the only behavior change should be disallowing non-hex object references. However, there is one interesting exception: get_sha1 will only parse a 40-character hex sha1 if the string has exactly 40 characters, whereas get_sha1_hex will just eat the first 40 characters, leaving the rest. That means that current versions of git-upload-pack will not accept a "shallow" packet that has a trailing newline, even though the protocol documentation is clear that newlines are allowed (even encouraged) in non-binary parts of the protocol. This never mattered in practice, though, because fetch-pack, contrary to the protocol documentation, does not include a newline in its shallow lines. JGit follows its lead (though it correctly is strict on the parsing end about wanting a hex object id). We do not adjust fetch-pack to send newlines here, as it would break communication with older versions of git (and there is no actual benefit to doing so, except for consistency with other parts of the protocol). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- upload-pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upload-pack.c b/upload-pack.c index 30146a04f..b058e8de5 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -596,7 +596,7 @@ static void receive_needs(void) if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; - if (get_sha1(line + 8, sha1)) + if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); object = parse_object(sha1); if (!object) From e58e57e49eb990e38df19628a744c71b44397ef1 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 14:54:57 -0500 Subject: [PATCH 02/20] upload-pack: do not add duplicate objects to shallow list When the client tells us it has a shallow object via "shallow ", we make sure we have the object, mark it with a flag, then add it to a dynamic array of shallow objects. This means that a client can get us to allocate arbitrary amounts of memory just by flooding us with shallow lines (whether they have the objects or not). You can demonstrate it easily with: yes '0035shallow e83c5163316f89bfbde7d9ab23ca2e25604af290' | git-upload-pack git.git We already protect against duplicates in want lines by checking if our flag is already set; let's do the same thing here. Note that a client can still get us to allocate some amount of memory by marking every object in the repo as "shallow" (or "want"). But this at least bounds it with the number of objects in the repository, which is not under the control of an upload-pack client. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- upload-pack.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/upload-pack.c b/upload-pack.c index b058e8de5..bd6f25519 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -603,8 +603,10 @@ static void receive_needs(void) die("did not find object for %s", line); if (object->type != OBJ_COMMIT) die("invalid shallow object %s", sha1_to_hex(sha1)); - object->flags |= CLIENT_SHALLOW; - add_object_array(object, NULL, &shallows); + if (!(object->flags & CLIENT_SHALLOW)) { + object->flags |= CLIENT_SHALLOW; + add_object_array(object, NULL, &shallows); + } continue; } if (!prefixcmp(line, "deepen ")) { From 97a83fa839d818130bfeca060279c4c355036785 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 14:55:28 -0500 Subject: [PATCH 03/20] upload-pack: remove packet debugging harness If you set the GIT_DEBUG_SEND_PACK environment variable, upload-pack will dump lines it receives in the receive_needs phase to a descriptor. This debugging harness is a strict subset of what GIT_TRACE_PACKET can do. Let's just drop it in favor of that. A few tests used GIT_DEBUG_SEND_PACK to confirm which objects get sent; we have to adapt them to the new output format. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t5503-tagfollow.sh | 38 +++++++++++++++++--------------------- t/t5700-clone-reference.sh | 10 +++++----- upload-pack.c | 9 --------- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 60de2d6ed..d181c9681 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -5,7 +5,7 @@ test_description='test automatic tag following' . ./test-lib.sh if ! test_have_prereq NOT_MINGW; then - say "GIT_DEBUG_SEND_PACK not supported - skipping tests" + say "GIT_TRACE_PACKET not supported - skipping tests" fi # End state of the repository: @@ -42,21 +42,26 @@ U=UPLOAD_LOG test_expect_success NOT_MINGW 'setup expect' ' cat - <expect -#S want $A -#E EOF ' +get_needs () { + perl -alne ' + next unless $F[1] eq "upload-pack<"; + last if $F[2] eq "0000"; + print $F[2], " ", $F[3]; + ' "$@" +} + test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=3 git fetch 3>../$U && test $A = $(git rev-parse --verify origin/master) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' @@ -74,10 +79,8 @@ test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' test_expect_success NOT_MINGW 'setup expect' ' cat - <expect -#S want $C want $T -#E EOF ' @@ -85,13 +88,12 @@ test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=3 git fetch 3>../$U && test $C = $(git rev-parse --verify origin/cat) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' @@ -113,10 +115,8 @@ test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' test_expect_success NOT_MINGW 'setup expect' ' cat - <expect -#S want $B want $S -#E EOF ' @@ -124,22 +124,19 @@ test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=3 git fetch 3>../$U && test $B = $(git rev-parse --verify origin/master) && test $B = $(git rev-parse --verify tag2^0) && test $S = $(git rev-parse --verify tag2) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' test_expect_success NOT_MINGW 'setup expect' ' cat - <expect -#S want $B want $S -#E EOF ' @@ -151,15 +148,14 @@ test_expect_success NOT_MINGW 'new clone fetch master and tags' ' cd clone2 && git init && git remote add origin .. && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=3 git fetch 3>../$U && test $B = $(git rev-parse --verify origin/master) && test $S = $(git rev-parse --verify tag2) && test $B = $(git rev-parse --verify tag2^0) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index c47d450cc..9cd3b4d10 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -55,10 +55,10 @@ cd "$base_dir" rm -f "$U.D" test_expect_success 'cloning with reference (no -l -s)' \ -'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"' +'GIT_TRACE_PACKET=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"' test_expect_success 'fetched no objects' \ -'! grep "^want" "$U.D"' +'! grep " want" "$U.D"' cd "$base_dir" @@ -173,12 +173,12 @@ test_expect_success 'fetch with incomplete alternates' ' ( cd K && git remote add J "file://$base_dir/J" && - GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K" + GIT_TRACE_PACKET=3 git fetch J 3>"$U.K" ) && master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) && - ! grep "^want $master_object" "$U.K" && + ! grep " want $master_object" "$U.K" && tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && - ! grep "^want $tag_object" "$U.K" + ! grep " want $tag_object" "$U.K" ' test_done diff --git a/upload-pack.c b/upload-pack.c index bd6f25519..0edc79e4a 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -42,7 +42,6 @@ static unsigned int timeout; * otherwise maximum packet size (up to 65520 bytes). */ static int use_sideband; -static int debug_fd; static int advertise_refs; static int stateless_rpc; @@ -580,8 +579,6 @@ static void receive_needs(void) int has_non_tip = 0; shallow_nr = 0; - if (debug_fd) - write_str_in_full(debug_fd, "#S\n"); for (;;) { struct object *o; const char *features; @@ -590,8 +587,6 @@ static void receive_needs(void) reset_timeout(); if (!len) break; - if (debug_fd) - write_in_full(debug_fd, line, len); if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; @@ -653,8 +648,6 @@ static void receive_needs(void) add_object_array(o, NULL, &want_obj); } } - if (debug_fd) - write_str_in_full(debug_fd, "#E\n"); /* * We have sent all our refs already, and the other end @@ -845,8 +838,6 @@ int main(int argc, char **argv) if (is_repository_shallow()) die("attempt to fetch/clone from a shallow repository"); git_config(upload_pack_config, NULL); - if (getenv("GIT_DEBUG_SEND_PACK")) - debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK")); upload_pack(); return 0; } From 030e9dd64f931c0ccfb6ad4be88775f8272f02eb Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:00:28 -0500 Subject: [PATCH 04/20] fetch-pack: fix out-of-bounds buffer offset in get_ack When we read acks from the remote, we expect either: ACK or ACK We parse the "ACK " bit from the line, and then start looking for the flag strings at "line+45"; if we don't have them, we assume it's of the first type. But if we do have the first type, then line+45 is not necessarily inside our string at all! It turns out that this works most of the time due to the way we parse the packets. They should come in with a newline, and packet_read puts an extra NUL into the buffer, so we end up with: ACK \n\0 with the newline at offset 44 and the NUL at offset 45. We then strip the newline, putting a NUL at offset 44. So when we look at "line+45", we are looking past the end of our string; but it's OK, because we hit the terminator from the original string. This breaks down, however, if the other side does not terminate their packets with a newline. In that case, our packet is one character shorter, and we start looking through uninitialized memory for the flag. No known implementation sends such a packet, so it has never come up in practice. This patch tightens the check by looking for a short, flagless ACK before trying to parse the flag. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- fetch-pack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fetch-pack.c b/fetch-pack.c index 6d8926a55..27a3e8036 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -226,6 +226,8 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1) return NAK; if (!prefixcmp(line, "ACK ")) { if (!get_sha1_hex(line+4, result_sha1)) { + if (len < 45) + return ACK; if (strstr(line+45, "continue")) return ACK_continue; if (strstr(line+45, "common")) From 8f9e3e498c113e987c2b8662485db5461cad1c36 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:00:43 -0500 Subject: [PATCH 05/20] send-pack: prefer prefixcmp over memcmp in receive_status This code predates prefixcmp, so it used memcmp along with static sizes. Replacing these memcmps with prefixcmp makes the code much more readable, and the lack of static sizes will make refactoring it in future patches simpler. Note that we used to be unnecessarily liberal in parsing the "unpack" status line, and would accept "unpack ok\njunk". No version of git has ever produced that, and it violates the BNF in Documentation/technical/pack-protocol.txt. Let's take this opportunity to tighten the check by converting the prefix comparison into a strcmp. While we're in the area, let's also fix a vague error message that does not follow our usual conventions (it writes directly to stderr and does not use the "error:" prefix). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- send-pack.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/send-pack.c b/send-pack.c index 97ab33609..e91cbe2c9 100644 --- a/send-pack.c +++ b/send-pack.c @@ -109,9 +109,9 @@ static int receive_status(int in, struct ref *refs) char line[1000]; int ret = 0; int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) + if (prefixcmp(line, "unpack ")) return error("did not receive remote status"); - if (memcmp(line, "unpack ok\n", 10)) { + if (strcmp(line, "unpack ok\n")) { char *p = line + strlen(line) - 1; if (*p == '\n') *p = '\0'; @@ -125,9 +125,8 @@ static int receive_status(int in, struct ref *refs) len = packet_read_line(in, line, sizeof(line)); if (!len) break; - if (len < 3 || - (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { - fprintf(stderr, "protocol error: %s\n", line); + if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) { + error("invalid ref status from remote: %s", line); ret = -1; break; } From 6379dd052243aa6b1cc5115b72b78aedbeeed008 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:00:59 -0500 Subject: [PATCH 06/20] upload-archive: do not copy repo name According to the comment, enter_repo will modify its input. However, this has not been the case since 1c64b48 (enter_repo: do not modify input, 2011-10-04). Drop the now-useless copy. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/upload-archive.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index b928beb8e..c3d134eac 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -27,13 +27,8 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) if (argc != 2) usage(upload_archive_usage); - if (strlen(argv[1]) + 1 > sizeof(buf)) - die("insanely long repository name"); - - strcpy(buf, argv[1]); /* enter-repo smudges its argument */ - - if (!enter_repo(buf, 0)) - die("'%s' does not appear to be a git repository", buf); + if (!enter_repo(argv[1], 0)) + die("'%s' does not appear to be a git repository", argv[1]); /* put received options in sent_argv[] */ sent_argc = 1; From 090fd4fe24b9da9d912aa5856f4cd32d157924c6 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:01:26 -0500 Subject: [PATCH 07/20] upload-archive: use argv_array to store client arguments The current parsing scheme for upload-archive is to pack arguments into a fixed-size buffer, separated by NULs, and put a pointer to each argument in the buffer into a fixed-size argv array. This works fine, and the limits are high enough that nobody reasonable is going to hit them, but it makes the code hard to follow. Instead, let's just stuff the arguments into an argv_array, which is much simpler. That lifts the "all arguments must fit inside 4K together" limit. We could also trivially lift the MAX_ARGS limitation (in fact, we have to keep extra code to enforce it). But that would mean a client could force us to allocate an arbitrary amount of memory simply by sending us "argument" lines. By limiting the MAX_ARGS, we limit an attacker to about 4 megabytes (64 times a maximum 64K packet buffer). That may sound like a lot compared to the 4K limit, but it's not a big deal compared to what git-archive will actually allocate while working (e.g., to load blobs into memory). The important thing is that it is bounded. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/upload-archive.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index c3d134eac..1517dec40 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -7,6 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" +#include "argv-array.h" static const char upload_archive_usage[] = "git upload-archive "; @@ -18,10 +19,9 @@ static const char deadchild[] = int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { - const char *sent_argv[MAX_ARGS]; + struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - char *p, buf[4096]; - int sent_argc; + char buf[4096]; int len; if (argc != 2) @@ -31,33 +31,26 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) die("'%s' does not appear to be a git repository", argv[1]); /* put received options in sent_argv[] */ - sent_argc = 1; - sent_argv[0] = "git-upload-archive"; - for (p = buf;;) { + argv_array_push(&sent_argv, "git-upload-archive"); + for (;;) { /* This will die if not enough free space in buf */ - len = packet_read_line(0, p, (buf + sizeof buf) - p); + len = packet_read_line(0, buf, sizeof(buf)); if (len == 0) break; /* got a flush */ - if (sent_argc > MAX_ARGS - 2) - die("Too many options (>%d)", MAX_ARGS - 2); + if (sent_argv.argc > MAX_ARGS) + die("Too many options (>%d)", MAX_ARGS - 1); - if (p[len-1] == '\n') { - p[--len] = 0; + if (buf[len-1] == '\n') { + buf[--len] = 0; } - if (len < strlen(arg_cmd) || - strncmp(arg_cmd, p, strlen(arg_cmd))) - die("'argument' token or flush expected"); - len -= strlen(arg_cmd); - memmove(p, p + strlen(arg_cmd), len); - sent_argv[sent_argc++] = p; - p += len; - *p++ = 0; + if (prefixcmp(buf, arg_cmd)) + die("'argument' token or flush expected"); + argv_array_push(&sent_argv, buf + strlen(arg_cmd)); } - sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); + return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) From 756e676ca011083dd15264ec29e281c47297ae40 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:01:36 -0500 Subject: [PATCH 08/20] write_or_die: raise SIGPIPE when we get EPIPE The write_or_die function will always die on an error, including EPIPE. However, it currently treats EPIPE specially by suppressing any error message, and by exiting with exit code 0. Suppressing the error message makes some sense; a pipe death may just be a sign that the other side is not interested in what we have to say. However, exiting with a successful error code is not a good idea, as write_or_die is frequently used in cases where we want to be careful about having written all of the output, and we may need to signal to our caller that we have done so (e.g., you would not want a push whose other end has hung up to report success). This distinction doesn't typically matter in git, because we do not ignore SIGPIPE in the first place. Which means that we will not get EPIPE, but instead will just die when we get a SIGPIPE. But it's possible for a default handler to be set by a parent process, or for us to add a callsite inside one of our few SIGPIPE-ignoring blocks of code. This patch converts write_or_die to actually raise SIGPIPE when we see EPIPE, rather than exiting with zero. This brings the behavior in line with the "normal" case that we die from SIGPIPE (and any callers who want to check why we died will see the same thing). We also give the same treatment to other related functions, including write_or_whine_pipe and maybe_flush_or_die. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- write_or_die.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/write_or_die.c b/write_or_die.c index 960f448cf..b50f99a93 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -1,5 +1,15 @@ #include "cache.h" +static void check_pipe(int err) +{ + if (err == EPIPE) { + signal(SIGPIPE, SIG_DFL); + raise(SIGPIPE); + /* Should never happen, but just in case... */ + exit(141); + } +} + /* * Some cases use stdio, but want to flush after the write * to get error handling (and to get better interactive @@ -34,8 +44,7 @@ void maybe_flush_or_die(FILE *f, const char *desc) return; } if (fflush(f)) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); die_errno("write failure on '%s'", desc); } } @@ -50,8 +59,7 @@ void fsync_or_die(int fd, const char *msg) void write_or_die(int fd, const void *buf, size_t count) { if (write_in_full(fd, buf, count) < 0) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); die_errno("write error"); } } @@ -59,8 +67,7 @@ void write_or_die(int fd, const void *buf, size_t count) int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg) { if (write_in_full(fd, buf, count) < 0) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); fprintf(stderr, "%s: write error (%s)\n", msg, strerror(errno)); return 0; From e148542870013e40d02490e692818a62691c1a10 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:01:46 -0500 Subject: [PATCH 09/20] pkt-line: move a misplaced comment The comment describing the packet writing interface was originally written above packet_write, but migrated to be above safe_write in f3a3214, probably because it is meant to generally describe the packet writing interface and not a single function. Let's move it into the header file, where users of the interface are more likely to see it. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- pkt-line.c | 15 --------------- pkt-line.h | 14 +++++++++++++- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/pkt-line.c b/pkt-line.c index eaba15f12..5138f47b6 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -46,21 +46,6 @@ static void packet_trace(const char *buf, unsigned int len, int write) strbuf_release(&out); } -/* - * Write a packetized stream, where each line is preceded by - * its length (including the header) as a 4-byte hex number. - * A length of 'zero' means end of stream (and a length of 1-3 - * would be an error). - * - * This is all pretty stupid, but we use this packetized line - * format to make a streaming format possible without ever - * over-running the read buffers. That way we'll never read - * into what might be the pack data (which should go to another - * process entirely). - * - * The writing side could use stdio, but since the reading - * side can't, we stay with pure read/write interfaces. - */ ssize_t safe_write(int fd, const void *buf, ssize_t n) { ssize_t nn = n; diff --git a/pkt-line.h b/pkt-line.h index 8cfeb0c31..7a67e9c65 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -5,7 +5,19 @@ #include "strbuf.h" /* - * Silly packetized line writing interface + * Write a packetized stream, where each line is preceded by + * its length (including the header) as a 4-byte hex number. + * A length of 'zero' means end of stream (and a length of 1-3 + * would be an error). + * + * This is all pretty stupid, but we use this packetized line + * format to make a streaming format possible without ever + * over-running the read buffers. That way we'll never read + * into what might be the pack data (which should go to another + * process entirely). + * + * The writing side could use stdio, but since the reading + * side can't, we stay with pure read/write interfaces. */ void packet_flush(int fd); void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); From cdf4fb8e332f9641ac1ca95e999fe98251d31392 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:01:56 -0500 Subject: [PATCH 10/20] pkt-line: drop safe_write function This is just write_or_die by another name. The one distinction is that write_or_die will treat EPIPE specially by suppressing error messages. That's fine, as we die by SIGPIPE anyway (and in the off chance that it is disabled, write_or_die will simulate it). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/receive-pack.c | 2 +- builtin/send-pack.c | 2 +- fetch-pack.c | 2 +- http-backend.c | 8 ++++---- pkt-line.c | 21 ++------------------- pkt-line.h | 1 - remote-curl.c | 4 ++-- send-pack.c | 2 +- sideband.c | 9 +++++---- upload-pack.c | 3 ++- 10 files changed, 19 insertions(+), 35 deletions(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 62ba6e7a3..912956378 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -932,7 +932,7 @@ static void report(struct command *commands, const char *unpack_status) if (use_sideband) send_sideband(1, 1, buf.buf, buf.len, use_sideband); else - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 57a46b265..87785197c 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -79,7 +79,7 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); } diff --git a/fetch-pack.c b/fetch-pack.c index 27a3e8036..b53a18f92 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -247,7 +247,7 @@ static void send_request(struct fetch_pack_args *args, send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); packet_flush(fd); } else - safe_write(fd, buf->buf, buf->len); + write_or_die(fd, buf->buf, buf->len); } static void insert_one_alternate_ref(const struct ref *ref, void *unused) diff --git a/http-backend.c b/http-backend.c index f50e77fb2..8144f3ad5 100644 --- a/http-backend.c +++ b/http-backend.c @@ -70,7 +70,7 @@ static void format_write(int fd, const char *fmt, ...) if (n >= sizeof(buffer)) die("protocol error: impossibly long line"); - safe_write(fd, buffer, n); + write_or_die(fd, buffer, n); } static void http_status(unsigned code, const char *msg) @@ -111,7 +111,7 @@ static void hdr_cache_forever(void) static void end_headers(void) { - safe_write(1, "\r\n", 2); + write_or_die(1, "\r\n", 2); } __attribute__((format (printf, 1, 2))) @@ -157,7 +157,7 @@ static void send_strbuf(const char *type, struct strbuf *buf) hdr_int(content_length, buf->len); hdr_str(content_type, type); end_headers(); - safe_write(1, buf->buf, buf->len); + write_or_die(1, buf->buf, buf->len); } static void send_local_file(const char *the_type, const char *name) @@ -185,7 +185,7 @@ static void send_local_file(const char *the_type, const char *name) die_errno("Cannot read '%s'", p); if (!n) break; - safe_write(1, buf, n); + write_or_die(1, buf, n); } close(fd); free(buf); diff --git a/pkt-line.c b/pkt-line.c index 5138f47b6..699c2dd3b 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -46,23 +46,6 @@ static void packet_trace(const char *buf, unsigned int len, int write) strbuf_release(&out); } -ssize_t safe_write(int fd, const void *buf, ssize_t n) -{ - ssize_t nn = n; - while (n) { - int ret = xwrite(fd, buf, n); - if (ret > 0) { - buf = (char *) buf + ret; - n -= ret; - continue; - } - if (!ret) - die("write error (disk full?)"); - die_errno("write error"); - } - return nn; -} - /* * If we buffered things up above (we don't, but we should), * we'd flush it here @@ -70,7 +53,7 @@ ssize_t safe_write(int fd, const void *buf, ssize_t n) void packet_flush(int fd) { packet_trace("0000", 4, 1); - safe_write(fd, "0000", 4); + write_or_die(fd, "0000", 4); } void packet_buf_flush(struct strbuf *buf) @@ -106,7 +89,7 @@ void packet_write(int fd, const char *fmt, ...) va_start(args, fmt); n = format_packet(fmt, args); va_end(args); - safe_write(fd, buffer, n); + write_or_die(fd, buffer, n); } void packet_buf_write(struct strbuf *buf, const char *fmt, ...) diff --git a/pkt-line.h b/pkt-line.h index 7a67e9c65..3b6c19c4e 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -27,6 +27,5 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f int packet_read_line(int fd, char *buffer, unsigned size); int packet_read(int fd, char *buffer, unsigned size); int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); -ssize_t safe_write(int, const void *, ssize_t); #endif diff --git a/remote-curl.c b/remote-curl.c index 933c69ac2..7be4b5349 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -685,7 +685,7 @@ static int fetch_git(struct discovery *heads, err = rpc_service(&rpc, heads); if (rpc.result.len) - safe_write(1, rpc.result.buf, rpc.result.len); + write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); strbuf_release(&preamble); free(depth_arg); @@ -805,7 +805,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) err = rpc_service(&rpc, heads); if (rpc.result.len) - safe_write(1, rpc.result.buf, rpc.result.len); + write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); free(argv); return err; diff --git a/send-pack.c b/send-pack.c index e91cbe2c9..bde796b1b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -280,7 +280,7 @@ int send_pack(struct send_pack_args *args, send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); } } else { - safe_write(out, req_buf.buf, req_buf.len); + write_or_die(out, req_buf.buf, req_buf.len); packet_flush(out); } strbuf_release(&req_buf); diff --git a/sideband.c b/sideband.c index d5ffa1c89..8f7b25bf7 100644 --- a/sideband.c +++ b/sideband.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "pkt-line.h" #include "sideband.h" @@ -108,7 +109,7 @@ int recv_sideband(const char *me, int in_stream, int out) } while (len); continue; case 1: - safe_write(out, buf + pf+1, len); + write_or_die(out, buf + pf+1, len); continue; default: fprintf(stderr, "%s: protocol error: bad band #%d\n", @@ -138,12 +139,12 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet if (0 <= band) { sprintf(hdr, "%04x", n + 5); hdr[4] = band; - safe_write(fd, hdr, 5); + write_or_die(fd, hdr, 5); } else { sprintf(hdr, "%04x", n + 4); - safe_write(fd, hdr, 4); + write_or_die(fd, hdr, 4); } - safe_write(fd, p, n); + write_or_die(fd, p, n); p += n; sz -= n; } diff --git a/upload-pack.c b/upload-pack.c index 0edc79e4a..afc2d9279 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -69,7 +69,8 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz) xwrite(fd, data, sz); return sz; } - return safe_write(fd, data, sz); + write_or_die(fd, data, sz); + return sz; } static FILE *pack_pipe = NULL; From 0380942902b23f02f7f595bc394e09bcd74d4ded Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:02:10 -0500 Subject: [PATCH 11/20] pkt-line: provide a generic reading function with options Originally we had a single function for reading packetized data: packet_read_line. Commit 46284dd grew a more "gentle" form, packet_read, that returns an error instead of dying upon reading a truncated input stream. However, it is not clear from the names which should be called, or what the difference is. Let's instead make packet_read be a generic public interface that can take option flags, and update the single callsite that uses it. This is less code, more clear, and paves the way for introducing more options into the generic interface later. The function signature is changed, so there should be no hidden conflicts with topics in flight. While we're at it, we'll document how error conditions are handled based on the options, and rename the confusing "return_line_fail" option to "gentle_on_eof". While we are cleaning up the names, we can drop the "return_line_fail" checks in packet_read_internal entirely. They look like this: ret = safe_read(..., return_line_fail); if (return_line_fail && ret < 0) ... The check for return_line_fail is a no-op; safe_read will only ever return an error value if return_line_fail was true in the first place. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- connect.c | 3 ++- pkt-line.c | 21 ++++++++------------- pkt-line.h | 27 ++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/connect.c b/connect.c index 49e56ba35..0aa202f88 100644 --- a/connect.c +++ b/connect.c @@ -76,7 +76,8 @@ struct ref **get_remote_heads(int in, struct ref **list, char *name; int len, name_len; - len = packet_read(in, buffer, sizeof(buffer)); + len = packet_read(in, buffer, sizeof(buffer), + PACKET_READ_GENTLE_ON_EOF); if (len < 0) die_initial_contact(got_at_least_one_head); diff --git a/pkt-line.c b/pkt-line.c index 699c2dd3b..8700cf8ad 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -103,13 +103,13 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) strbuf_add(buf, buffer, n); } -static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail) +static int safe_read(int fd, void *buffer, unsigned size, int options) { ssize_t ret = read_in_full(fd, buffer, size); if (ret < 0) die_errno("read error"); else if (ret < size) { - if (return_line_fail) + if (options & PACKET_READ_GENTLE_ON_EOF) return -1; die("The remote end hung up unexpectedly"); @@ -143,13 +143,13 @@ static int packet_length(const char *linelen) return len; } -static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail) +int packet_read(int fd, char *buffer, unsigned size, int options) { int len, ret; char linelen[4]; - ret = safe_read(fd, linelen, 4, return_line_fail); - if (return_line_fail && ret < 0) + ret = safe_read(fd, linelen, 4, options); + if (ret < 0) return ret; len = packet_length(linelen); if (len < 0) @@ -161,22 +161,17 @@ static int packet_read_internal(int fd, char *buffer, unsigned size, int return_ len -= 4; if (len >= size) die("protocol error: bad line length %d", len); - ret = safe_read(fd, buffer, len, return_line_fail); - if (return_line_fail && ret < 0) + ret = safe_read(fd, buffer, len, options); + if (ret < 0) return ret; buffer[len] = 0; packet_trace(buffer, len, 0); return len; } -int packet_read(int fd, char *buffer, unsigned size) -{ - return packet_read_internal(fd, buffer, size, 1); -} - int packet_read_line(int fd, char *buffer, unsigned size) { - return packet_read_internal(fd, buffer, size, 0); + return packet_read(fd, buffer, size, 0); } int packet_get_line(struct strbuf *out, diff --git a/pkt-line.h b/pkt-line.h index 3b6c19c4e..8cd326c92 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -24,8 +24,33 @@ void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2 void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +/* + * Read a packetized line from the descriptor into the buffer, which must be at + * least size bytes long. The return value specifies the number of bytes read + * into the buffer. + * + * If options does not contain PACKET_READ_GENTLE_ON_EOF, we will die under any + * of the following conditions: + * + * 1. Read error from descriptor. + * + * 2. Protocol error from the remote (e.g., bogus length characters). + * + * 3. Receiving a packet larger than "size" bytes. + * + * 4. Truncated output from the remote (e.g., we expected a packet but got + * EOF, or we got a partial packet followed by EOF). + * + * If options does contain PACKET_READ_GENTLE_ON_EOF, we will not die on + * condition 4 (truncated input), but instead return -1. However, we will still + * die for the other 3 conditions. + */ +#define PACKET_READ_GENTLE_ON_EOF (1u<<0) +int packet_read(int fd, char *buffer, unsigned size, int options); + +/* Historical convenience wrapper for packet_read that sets no options */ int packet_read_line(int fd, char *buffer, unsigned size); -int packet_read(int fd, char *buffer, unsigned size); + int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); #endif From 819b929d3389f6007e1c469d9060e7876caeb97f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:02:28 -0500 Subject: [PATCH 12/20] pkt-line: teach packet_read_line to chomp newlines The packets sent during ref negotiation are all terminated by newline; even though the code to chomp these newlines is short, we end up doing it in a lot of places. This patch teaches packet_read_line to auto-chomp the trailing newline; this lets us get rid of a lot of inline chomping code. As a result, some call-sites which are not reading line-oriented data (e.g., when reading chunks of packfiles alongside sideband) transition away from packet_read_line to the generic packet_read interface. This patch converts all of the existing callsites. Since the function signature of packet_read_line does not change (but its behavior does), there is a possibility of new callsites being introduced in later commits, silently introducing an incompatibility. However, since a later patch in this series will change the signature, such a commit would have to be merged directly into this commit, not to the tip of the series; we can therefore ignore the issue. This is an internal cleanup and should produce no change of behavior in the normal case. However, there is one corner case to note. Callers of packet_read_line have never been able to tell the difference between a flush packet ("0000") and an empty packet ("0004"), as both cause packet_read_line to return a length of 0. Readers treat them identically, even though Documentation/technical/protocol-common.txt says we must not; it also says that implementations should not send an empty pkt-line. By stripping out the newline before the result gets to the caller, we will now treat the newline-only packet ("0005\n") the same as an empty packet, which in turn gets treated like a flush packet. In practice this doesn't matter, as neither empty nor newline-only packets are part of git's protocols (at least not for the line-oriented bits, and readers who are not expecting line-oriented packets will be calling packet_read directly, anyway). But even if we do decide to care about the distinction later, it is orthogonal to this patch. The right place to tighten would be to stop treating empty packets as flush packets, and this change does not make doing so any harder. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/archive.c | 2 -- builtin/fetch-pack.c | 2 -- builtin/receive-pack.c | 2 -- builtin/upload-archive.c | 4 ---- connect.c | 5 ++--- daemon.c | 2 +- fetch-pack.c | 2 -- pkt-line.c | 7 ++++++- pkt-line.h | 9 ++++++++- remote-curl.c | 6 +++--- send-pack.c | 6 +----- sideband.c | 2 +- upload-pack.c | 8 -------- 13 files changed, 22 insertions(+), 35 deletions(-) diff --git a/builtin/archive.c b/builtin/archive.c index 9a1cfd3da..d381ac414 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -56,8 +56,6 @@ static int run_remote_archiver(int argc, const char **argv, len = packet_read_line(fd[0], buf, sizeof(buf)); if (!len) die(_("git archive: expected ACK/NAK, got EOF")); - if (buf[len-1] == '\n') - buf[--len] = 0; if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 940ae35dc..f73664f43 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -105,8 +105,6 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) int n = packet_read_line(0, line, sizeof(line)); if (!n) break; - if (line[n-1] == '\n') - n--; string_list_append(&sought, xmemdupz(line, n)); } } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 912956378..6679e636c 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -763,8 +763,6 @@ static struct command *read_head_info(void) len = packet_read_line(0, line, sizeof(line)); if (!len) break; - if (line[len-1] == '\n') - line[--len] = 0; if (len < 83 || line[40] != ' ' || line[81] != ' ' || diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 1517dec40..d90f0aba4 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -40,10 +40,6 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) if (sent_argv.argc > MAX_ARGS) die("Too many options (>%d)", MAX_ARGS - 1); - if (buf[len-1] == '\n') { - buf[--len] = 0; - } - if (prefixcmp(buf, arg_cmd)) die("'argument' token or flush expected"); argv_array_push(&sent_argv, buf + strlen(arg_cmd)); diff --git a/connect.c b/connect.c index 0aa202f88..fe8eb01ae 100644 --- a/connect.c +++ b/connect.c @@ -77,14 +77,13 @@ struct ref **get_remote_heads(int in, struct ref **list, int len, name_len; len = packet_read(in, buffer, sizeof(buffer), - PACKET_READ_GENTLE_ON_EOF); + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); if (len < 0) die_initial_contact(got_at_least_one_head); if (!len) break; - if (buffer[len-1] == '\n') - buffer[--len] = 0; if (len > 4 && !prefixcmp(buffer, "ERR ")) die("remote error: %s", buffer + 4); diff --git a/daemon.c b/daemon.c index 4602b46a5..4f5cd6155 100644 --- a/daemon.c +++ b/daemon.c @@ -612,7 +612,7 @@ static int execute(void) loginfo("Connection from %s:%s", addr, port); alarm(init_timeout ? init_timeout : timeout); - pktlen = packet_read_line(0, line, sizeof(line)); + pktlen = packet_read(0, line, sizeof(line), 0); alarm(0); len = strlen(line); diff --git a/fetch-pack.c b/fetch-pack.c index b53a18f92..f830db224 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -220,8 +220,6 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1) if (!len) die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; if (!strcmp(line, "NAK")) return NAK; if (!prefixcmp(line, "ACK ")) { diff --git a/pkt-line.c b/pkt-line.c index 8700cf8ad..dc11c407c 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -164,6 +164,11 @@ int packet_read(int fd, char *buffer, unsigned size, int options) ret = safe_read(fd, buffer, len, options); if (ret < 0) return ret; + + if ((options & PACKET_READ_CHOMP_NEWLINE) && + len && buffer[len-1] == '\n') + len--; + buffer[len] = 0; packet_trace(buffer, len, 0); return len; @@ -171,7 +176,7 @@ int packet_read(int fd, char *buffer, unsigned size, int options) int packet_read_line(int fd, char *buffer, unsigned size) { - return packet_read(fd, buffer, size, 0); + return packet_read(fd, buffer, size, PACKET_READ_CHOMP_NEWLINE); } int packet_get_line(struct strbuf *out, diff --git a/pkt-line.h b/pkt-line.h index 8cd326c92..5d2fb423d 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -44,11 +44,18 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f * If options does contain PACKET_READ_GENTLE_ON_EOF, we will not die on * condition 4 (truncated input), but instead return -1. However, we will still * die for the other 3 conditions. + * + * If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if + * present) is removed from the buffer before returning. */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0) +#define PACKET_READ_CHOMP_NEWLINE (1u<<1) int packet_read(int fd, char *buffer, unsigned size, int options); -/* Historical convenience wrapper for packet_read that sets no options */ +/* + * Convenience wrapper for packet_read that is not gentle, and sets the + * CHOMP_NEWLINE option. + */ int packet_read_line(int fd, char *buffer, unsigned size); int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); diff --git a/remote-curl.c b/remote-curl.c index 7be4b5349..b28f96504 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -308,7 +308,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, if (!avail) { rpc->initial_buffer = 0; - avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + avail = packet_read(rpc->out, rpc->buf, rpc->alloc, 0); if (!avail) return 0; rpc->pos = 0; @@ -425,7 +425,7 @@ static int post_rpc(struct rpc_state *rpc) break; } - n = packet_read_line(rpc->out, buf, left); + n = packet_read(rpc->out, buf, left, 0); if (!n) break; rpc->len += n; @@ -579,7 +579,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->hdr_accept = strbuf_detach(&buf, NULL); while (!err) { - int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + int n = packet_read(rpc->out, rpc->buf, rpc->alloc, 0); if (!n) break; rpc->pos = 0; diff --git a/send-pack.c b/send-pack.c index bde796b1b..8c230bf6c 100644 --- a/send-pack.c +++ b/send-pack.c @@ -111,10 +111,7 @@ static int receive_status(int in, struct ref *refs) int len = packet_read_line(in, line, sizeof(line)); if (prefixcmp(line, "unpack ")) return error("did not receive remote status"); - if (strcmp(line, "unpack ok\n")) { - char *p = line + strlen(line) - 1; - if (*p == '\n') - *p = '\0'; + if (strcmp(line, "unpack ok")) { error("unpack failed: %s", line + 7); ret = -1; } @@ -131,7 +128,6 @@ static int receive_status(int in, struct ref *refs) break; } - line[strlen(line)-1] = '\0'; refname = line + 3; msg = strchr(refname, ' '); if (msg) diff --git a/sideband.c b/sideband.c index 8f7b25bf7..15cc1aec2 100644 --- a/sideband.c +++ b/sideband.c @@ -38,7 +38,7 @@ int recv_sideband(const char *me, int in_stream, int out) while (1) { int band, len; - len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX); + len = packet_read(in_stream, buf + pf, LARGE_PACKET_MAX, 0); if (len == 0) break; if (len < 1) { diff --git a/upload-pack.c b/upload-pack.c index afc2d9279..6e6d16687 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -50,13 +50,6 @@ static void reset_timeout(void) alarm(timeout); } -static int strip(char *line, int len) -{ - if (len && line[len-1] == '\n') - line[--len] = 0; - return len; -} - static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { if (use_sideband) @@ -447,7 +440,6 @@ static int get_common_commits(void) got_other = 0; continue; } - strip(line, len); if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ From 047ec60205b5ae8b180386579749d143db4c9be3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:02:45 -0500 Subject: [PATCH 13/20] pkt-line: move LARGE_PACKET_MAX definition from sideband Having the packet sizes defined near the packet read/write functions makes more sense. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http.c | 1 + pkt-line.h | 3 +++ sideband.h | 3 --- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/http.c b/http.c index d9d1aad3b..8803c7082 100644 --- a/http.c +++ b/http.c @@ -5,6 +5,7 @@ #include "url.h" #include "credential.h" #include "version.h" +#include "pkt-line.h" int active_requests; int http_is_verbose; diff --git a/pkt-line.h b/pkt-line.h index 5d2fb423d..6927ea521 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -58,6 +58,9 @@ int packet_read(int fd, char *buffer, unsigned size, int options); */ int packet_read_line(int fd, char *buffer, unsigned size); +#define DEFAULT_PACKET_MAX 1000 +#define LARGE_PACKET_MAX 65520 + int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); #endif diff --git a/sideband.h b/sideband.h index d72db35d1..e46bed0b0 100644 --- a/sideband.h +++ b/sideband.h @@ -4,9 +4,6 @@ #define SIDEBAND_PROTOCOL_ERROR -2 #define SIDEBAND_REMOTE_ERROR -1 -#define DEFAULT_PACKET_MAX 1000 -#define LARGE_PACKET_MAX 65520 - int recv_sideband(const char *me, int in_stream, int out); ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); From 74543a0423c96130b3b07946c20b10735c3b5b15 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:02:57 -0500 Subject: [PATCH 14/20] pkt-line: provide a LARGE_PACKET_MAX static buffer Most of the callers of packet_read_line just read into a static 1000-byte buffer (callers which handle arbitrary binary data already use LARGE_PACKET_MAX). This works fine in practice, because: 1. The only variable-sized data in these lines is a ref name, and refs tend to be a lot shorter than 1000 characters. 2. When sending ref lines, git-core always limits itself to 1000 byte packets. However, the only limit given in the protocol specification in Documentation/technical/protocol-common.txt is LARGE_PACKET_MAX; the 1000 byte limit is mentioned only in pack-protocol.txt, and then only describing what we write, not as a specific limit for readers. This patch lets us bump the 1000-byte limit to LARGE_PACKET_MAX. Even though git-core will never write a packet where this makes a difference, there are two good reasons to do this: 1. Other git implementations may have followed protocol-common.txt and used a larger maximum size. We don't bump into it in practice because it would involve very long ref names. 2. We may want to increase the 1000-byte limit one day. Since packets are transferred before any capabilities, it's difficult to do this in a backwards-compatible way. But if we bump the size of buffer the readers can handle, eventually older versions of git will be obsolete enough that we can justify bumping the writers, as well. We don't have plans to do this anytime soon, but there is no reason not to start the clock ticking now. Just bumping all of the reading bufs to LARGE_PACKET_MAX would waste memory. Instead, since most readers just read into a temporary buffer anyway, let's provide a single static buffer that all callers can use. We can further wrap this detail away by having the packet_read_line wrapper just use the buffer transparently and return a pointer to the static storage. That covers most of the cases, and the remaining ones already read into their own LARGE_PACKET_MAX buffers. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/archive.c | 15 +++++++-------- builtin/fetch-pack.c | 7 +++---- builtin/receive-pack.c | 6 +++--- builtin/upload-archive.c | 7 ++----- connect.c | 4 ++-- daemon.c | 4 ++-- fetch-pack.c | 12 ++++++------ pkt-line.c | 9 +++++++-- pkt-line.h | 9 +++++++-- send-pack.c | 7 +++---- upload-pack.c | 12 +++++------- 11 files changed, 47 insertions(+), 45 deletions(-) diff --git a/builtin/archive.c b/builtin/archive.c index d381ac414..49178f159 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -27,8 +27,8 @@ static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec, const char *name_hint) { - char buf[LARGE_PACKET_MAX]; - int fd[2], i, len, rv; + char *buf; + int fd[2], i, rv; struct transport *transport; struct remote *_remote; @@ -53,19 +53,18 @@ static int run_remote_archiver(int argc, const char **argv, packet_write(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) + buf = packet_read_line(fd[0], NULL); + if (!buf) die(_("git archive: expected ACK/NAK, got EOF")); if (strcmp(buf, "ACK")) { - if (len > 5 && !prefixcmp(buf, "NACK ")) + if (!prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); - if (len > 4 && !prefixcmp(buf, "ERR ")) + if (!prefixcmp(buf, "ERR ")) die(_("remote error: %s"), buf + 4); die(_("git archive: protocol error")); } - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) + if (packet_read_line(fd[0], NULL)) die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index f73664f43..c21cc2c77 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -100,12 +100,11 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) /* in stateless RPC mode we use pkt-line to read * from stdin, until we get a flush packet */ - static char line[1000]; for (;;) { - int n = packet_read_line(0, line, sizeof(line)); - if (!n) + char *line = packet_read_line(0, NULL); + if (!line) break; - string_list_append(&sought, xmemdupz(line, n)); + string_list_append(&sought, xstrdup(line)); } } else { diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 6679e636c..ccebd74f1 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -754,14 +754,14 @@ static struct command *read_head_info(void) struct command *commands = NULL; struct command **p = &commands; for (;;) { - static char line[1000]; + char *line; unsigned char old_sha1[20], new_sha1[20]; struct command *cmd; char *refname; int len, reflen; - len = packet_read_line(0, line, sizeof(line)); - if (!len) + line = packet_read_line(0, &len); + if (!line) break; if (len < 83 || line[40] != ' ' || diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index d90f0aba4..af2da35e7 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -21,8 +21,6 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - char buf[4096]; - int len; if (argc != 2) usage(upload_archive_usage); @@ -33,9 +31,8 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) /* put received options in sent_argv[] */ argv_array_push(&sent_argv, "git-upload-archive"); for (;;) { - /* This will die if not enough free space in buf */ - len = packet_read_line(0, buf, sizeof(buf)); - if (len == 0) + char *buf = packet_read_line(0, NULL); + if (!buf) break; /* got a flush */ if (sent_argv.argc > MAX_ARGS) die("Too many options (>%d)", MAX_ARGS - 1); diff --git a/connect.c b/connect.c index fe8eb01ae..611ffb441 100644 --- a/connect.c +++ b/connect.c @@ -72,11 +72,11 @@ struct ref **get_remote_heads(int in, struct ref **list, for (;;) { struct ref *ref; unsigned char old_sha1[20]; - static char buffer[1000]; char *name; int len, name_len; + char *buffer = packet_buffer; - len = packet_read(in, buffer, sizeof(buffer), + len = packet_read(in, packet_buffer, sizeof(packet_buffer), PACKET_READ_GENTLE_ON_EOF | PACKET_READ_CHOMP_NEWLINE); if (len < 0) diff --git a/daemon.c b/daemon.c index 4f5cd6155..3f70e79b8 100644 --- a/daemon.c +++ b/daemon.c @@ -604,7 +604,7 @@ static void parse_host_arg(char *extra_args, int buflen) static int execute(void) { - static char line[1000]; + char *line = packet_buffer; int pktlen, len, i; char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); @@ -612,7 +612,7 @@ static int execute(void) loginfo("Connection from %s:%s", addr, port); alarm(init_timeout ? init_timeout : timeout); - pktlen = packet_read(0, line, sizeof(line), 0); + pktlen = packet_read(0, packet_buffer, sizeof(packet_buffer), 0); alarm(0); len = strlen(line); diff --git a/fetch-pack.c b/fetch-pack.c index f830db224..66ff9add8 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -172,8 +172,8 @@ static void consume_shallow_list(struct fetch_pack_args *args, int fd) * shallow and unshallow commands every time there * is a block of have lines exchanged. */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { + char *line; + while ((line = packet_read_line(fd, NULL))) { if (!prefixcmp(line, "shallow ")) continue; if (!prefixcmp(line, "unshallow ")) @@ -215,8 +215,8 @@ static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) static enum ack_type get_ack(int fd, unsigned char *result_sha1) { - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); + int len; + char *line = packet_read_line(fd, &len); if (!len) die("git fetch-pack: expected ACK/NAK, got EOF"); @@ -346,11 +346,11 @@ static int find_common(struct fetch_pack_args *args, state_len = req_buf.len; if (args->depth > 0) { - char line[1024]; + char *line; unsigned char sha1[20]; send_request(args, fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { + while ((line = packet_read_line(fd[0], NULL))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); diff --git a/pkt-line.c b/pkt-line.c index dc11c407c..55fb68889 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -1,6 +1,7 @@ #include "cache.h" #include "pkt-line.h" +char packet_buffer[LARGE_PACKET_MAX]; static const char *packet_trace_prefix = "git"; static const char trace_key[] = "GIT_TRACE_PACKET"; @@ -174,9 +175,13 @@ int packet_read(int fd, char *buffer, unsigned size, int options) return len; } -int packet_read_line(int fd, char *buffer, unsigned size) +char *packet_read_line(int fd, int *len_p) { - return packet_read(fd, buffer, size, PACKET_READ_CHOMP_NEWLINE); + int len = packet_read(fd, packet_buffer, sizeof(packet_buffer), + PACKET_READ_CHOMP_NEWLINE); + if (len_p) + *len_p = len; + return len ? packet_buffer : NULL; } int packet_get_line(struct strbuf *out, diff --git a/pkt-line.h b/pkt-line.h index 6927ea521..fa93e3207 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -54,12 +54,17 @@ int packet_read(int fd, char *buffer, unsigned size, int options); /* * Convenience wrapper for packet_read that is not gentle, and sets the - * CHOMP_NEWLINE option. + * CHOMP_NEWLINE option. The return value is NULL for a flush packet, + * and otherwise points to a static buffer (that may be overwritten by + * subsequent calls). If the size parameter is not NULL, the length of the + * packet is written to it. */ -int packet_read_line(int fd, char *buffer, unsigned size); +char *packet_read_line(int fd, int *size); + #define DEFAULT_PACKET_MAX 1000 #define LARGE_PACKET_MAX 65520 +extern char packet_buffer[LARGE_PACKET_MAX]; int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); diff --git a/send-pack.c b/send-pack.c index 8c230bf6c..7d172ef37 100644 --- a/send-pack.c +++ b/send-pack.c @@ -106,9 +106,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext static int receive_status(int in, struct ref *refs) { struct ref *hint; - char line[1000]; int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); + char *line = packet_read_line(in, NULL); if (prefixcmp(line, "unpack ")) return error("did not receive remote status"); if (strcmp(line, "unpack ok")) { @@ -119,8 +118,8 @@ static int receive_status(int in, struct ref *refs) while (1) { char *refname; char *msg; - len = packet_read_line(in, line, sizeof(line)); - if (!len) + line = packet_read_line(in, NULL); + if (!line) break; if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) { error("invalid ref status from remote: %s", line); diff --git a/upload-pack.c b/upload-pack.c index 6e6d16687..98ddb6958 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -408,7 +408,6 @@ static int ok_to_give_up(void) static int get_common_commits(void) { - static char line[1000]; unsigned char sha1[20]; char last_hex[41]; int got_common = 0; @@ -418,10 +417,10 @@ static int get_common_commits(void) save_commit_buffer = 0; for (;;) { - int len = packet_read_line(0, line, sizeof(line)); + char *line = packet_read_line(0, NULL); reset_timeout(); - if (!len) { + if (!line) { if (multi_ack == 2 && got_common && !got_other && ok_to_give_up()) { sent_ready = 1; @@ -567,8 +566,7 @@ static void check_non_tip(void) static void receive_needs(void) { struct object_array shallows = OBJECT_ARRAY_INIT; - static char line[1000]; - int len, depth = 0; + int depth = 0; int has_non_tip = 0; shallow_nr = 0; @@ -576,9 +574,9 @@ static void receive_needs(void) struct object *o; const char *features; unsigned char sha1_buf[20]; - len = packet_read_line(0, line, sizeof(line)); + char *line = packet_read_line(0, NULL); reset_timeout(); - if (!len) + if (!line) break; if (!prefixcmp(line, "shallow ")) { From 4981fe750b1fc58bfdf5b9ca9843f4f505b9bb4d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 23 Feb 2013 17:31:34 -0500 Subject: [PATCH 15/20] pkt-line: share buffer/descriptor reading implementation The packet_read function reads from a descriptor. The packet_get_line function is similar, but reads from an in-memory buffer, and uses a completely separate implementation. This patch teaches the generic packet_read function to accept either source, and we can do away with packet_get_line's implementation. There are two other differences to account for between the old and new functions. The first is that we used to read into a strbuf, but now read into a fixed size buffer. The only two callers are fine with that, and in fact it simplifies their code, since they can use the same static-buffer interface as the rest of the packet_read_line callers (and we provide a similar convenience wrapper for reading from a buffer rather than a descriptor). This is technically an externally-visible behavior change in that we used to accept arbitrary sized packets up to 65532 bytes, and now cap out at LARGE_PACKET_MAX, 65520. In practice this doesn't matter, as we use it only for parsing smart-http headers (of which there is exactly one defined, and it is small and fixed-size). And any extension headers would be breaking the protocol to go over LARGE_PACKET_MAX anyway. The other difference is that packet_get_line would return on error rather than dying. However, both callers of packet_get_line are actually improved by dying. The first caller does its own error checking, but we can drop that; as a result, we'll actually get more specific reporting about protocol breakage when packet_read dies internally. The only downside is that packet_read will not print the smart-http URL that failed, but that's not a big deal; anybody not debugging can already see the remote's URL already, and anybody debugging would want to run with GIT_CURL_VERBOSE anyway to see way more information. The second caller, which is just trying to skip past any extra smart-http headers (of which there are none defined, but which we allow to keep room for future expansion), did not error check at all. As a result, it would treat an error just like a flush packet. The resulting mess would generally cause an error later in get_remote_heads, but now we get error reporting much closer to the source of the problem. Brown-paper-bag-fixes-by: Ramsay Jones Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- connect.c | 3 +- daemon.c | 2 +- pkt-line.c | 76 +++++++++++++++++++++++++-------------------------- pkt-line.h | 23 ++++++++++++---- remote-curl.c | 22 +++++++-------- sideband.c | 2 +- 6 files changed, 69 insertions(+), 59 deletions(-) diff --git a/connect.c b/connect.c index 611ffb441..3d999999e 100644 --- a/connect.c +++ b/connect.c @@ -76,7 +76,8 @@ struct ref **get_remote_heads(int in, struct ref **list, int len, name_len; char *buffer = packet_buffer; - len = packet_read(in, packet_buffer, sizeof(packet_buffer), + len = packet_read(in, NULL, NULL, + packet_buffer, sizeof(packet_buffer), PACKET_READ_GENTLE_ON_EOF | PACKET_READ_CHOMP_NEWLINE); if (len < 0) diff --git a/daemon.c b/daemon.c index 3f70e79b8..82d5bf50e 100644 --- a/daemon.c +++ b/daemon.c @@ -612,7 +612,7 @@ static int execute(void) loginfo("Connection from %s:%s", addr, port); alarm(init_timeout ? init_timeout : timeout); - pktlen = packet_read(0, packet_buffer, sizeof(packet_buffer), 0); + pktlen = packet_read(0, NULL, NULL, packet_buffer, sizeof(packet_buffer), 0); alarm(0); len = strlen(line); diff --git a/pkt-line.c b/pkt-line.c index 55fb68889..70f19501d 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -104,12 +104,28 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) strbuf_add(buf, buffer, n); } -static int safe_read(int fd, void *buffer, unsigned size, int options) +static int get_packet_data(int fd, char **src_buf, size_t *src_size, + void *dst, unsigned size, int options) { - ssize_t ret = read_in_full(fd, buffer, size); - if (ret < 0) - die_errno("read error"); - else if (ret < size) { + ssize_t ret; + + if (fd >= 0 && src_buf && *src_buf) + die("BUG: multiple sources given to packet_read"); + + /* Read up to "size" bytes from our source, whatever it is. */ + if (src_buf && *src_buf) { + ret = size < *src_size ? size : *src_size; + memcpy(dst, *src_buf, ret); + *src_buf += ret; + *src_size -= ret; + } else { + ret = read_in_full(fd, dst, size); + if (ret < 0) + die_errno("read error"); + } + + /* And complain if we didn't get enough bytes to satisfy the read. */ + if (ret < size) { if (options & PACKET_READ_GENTLE_ON_EOF) return -1; @@ -144,12 +160,13 @@ static int packet_length(const char *linelen) return len; } -int packet_read(int fd, char *buffer, unsigned size, int options) +int packet_read(int fd, char **src_buf, size_t *src_len, + char *buffer, unsigned size, int options) { int len, ret; char linelen[4]; - ret = safe_read(fd, linelen, 4, options); + ret = get_packet_data(fd, src_buf, src_len, linelen, 4, options); if (ret < 0) return ret; len = packet_length(linelen); @@ -162,7 +179,7 @@ int packet_read(int fd, char *buffer, unsigned size, int options) len -= 4; if (len >= size) die("protocol error: bad line length %d", len); - ret = safe_read(fd, buffer, len, options); + ret = get_packet_data(fd, src_buf, src_len, buffer, len, options); if (ret < 0) return ret; @@ -175,41 +192,24 @@ int packet_read(int fd, char *buffer, unsigned size, int options) return len; } -char *packet_read_line(int fd, int *len_p) +static char *packet_read_line_generic(int fd, + char **src, size_t *src_len, + int *dst_len) { - int len = packet_read(fd, packet_buffer, sizeof(packet_buffer), + int len = packet_read(fd, src, src_len, + packet_buffer, sizeof(packet_buffer), PACKET_READ_CHOMP_NEWLINE); - if (len_p) - *len_p = len; + if (dst_len) + *dst_len = len; return len ? packet_buffer : NULL; } -int packet_get_line(struct strbuf *out, - char **src_buf, size_t *src_len) +char *packet_read_line(int fd, int *len_p) { - int len; - - if (*src_len < 4) - return -1; - len = packet_length(*src_buf); - if (len < 0) - return -1; - if (!len) { - *src_buf += 4; - *src_len -= 4; - packet_trace("0000", 4, 0); - return 0; - } - if (*src_len < len) - return -2; - - *src_buf += 4; - *src_len -= 4; - len -= 4; + return packet_read_line_generic(fd, NULL, NULL, len_p); +} - strbuf_add(out, *src_buf, len); - *src_buf += len; - *src_len -= len; - packet_trace(out->buf, out->len, 0); - return len; +char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len) +{ + return packet_read_line_generic(-1, src, src_len, dst_len); } diff --git a/pkt-line.h b/pkt-line.h index fa93e3207..0a838d165 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -25,9 +25,16 @@ void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); /* - * Read a packetized line from the descriptor into the buffer, which must be at - * least size bytes long. The return value specifies the number of bytes read - * into the buffer. + * Read a packetized line into the buffer, which must be at least size bytes + * long. The return value specifies the number of bytes read into the buffer. + * + * If src_buffer is not NULL (and nor is *src_buffer), it should point to a + * buffer containing the packet data to parse, of at least *src_len bytes. + * After the function returns, src_buf will be incremented and src_len + * decremented by the number of bytes consumed. + * + * If src_buffer (or *src_buffer) is NULL, then data is read from the + * descriptor "fd". * * If options does not contain PACKET_READ_GENTLE_ON_EOF, we will die under any * of the following conditions: @@ -50,7 +57,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0) #define PACKET_READ_CHOMP_NEWLINE (1u<<1) -int packet_read(int fd, char *buffer, unsigned size, int options); +int packet_read(int fd, char **src_buffer, size_t *src_len, char + *buffer, unsigned size, int options); /* * Convenience wrapper for packet_read that is not gentle, and sets the @@ -61,11 +69,14 @@ int packet_read(int fd, char *buffer, unsigned size, int options); */ char *packet_read_line(int fd, int *size); +/* + * Same as packet_read_line, but read from a buf rather than a descriptor; + * see packet_read for details on how src_* is used. + */ +char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size); #define DEFAULT_PACKET_MAX 1000 #define LARGE_PACKET_MAX 65520 extern char packet_buffer[LARGE_PACKET_MAX]; -int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); - #endif diff --git a/remote-curl.c b/remote-curl.c index b28f96504..c8379a53f 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -138,28 +138,26 @@ static struct discovery* discover_refs(const char *service) if (maybe_smart && (5 <= last->len && last->buf[4] == '#') && !strbuf_cmp(&exp, &type)) { + char *line; + /* * smart HTTP response; validate that the service * pkt-line matches our request. */ - if (packet_get_line(&buffer, &last->buf, &last->len) <= 0) - die("%s has invalid packet header", refs_url); - if (buffer.len && buffer.buf[buffer.len - 1] == '\n') - strbuf_setlen(&buffer, buffer.len - 1); + line = packet_read_line_buf(&last->buf, &last->len, NULL); strbuf_reset(&exp); strbuf_addf(&exp, "# service=%s", service); - if (strbuf_cmp(&exp, &buffer)) - die("invalid server response; got '%s'", buffer.buf); + if (strcmp(line, exp.buf)) + die("invalid server response; got '%s'", line); strbuf_release(&exp); /* The header can include additional metadata lines, up * until a packet flush marker. Ignore these now, but * in the future we might start to scan them. */ - strbuf_reset(&buffer); - while (packet_get_line(&buffer, &last->buf, &last->len) > 0) - strbuf_reset(&buffer); + while (packet_read_line_buf(&last->buf, &last->len, NULL)) + ; last->proto_git = 1; } @@ -308,7 +306,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, if (!avail) { rpc->initial_buffer = 0; - avail = packet_read(rpc->out, rpc->buf, rpc->alloc, 0); + avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!avail) return 0; rpc->pos = 0; @@ -425,7 +423,7 @@ static int post_rpc(struct rpc_state *rpc) break; } - n = packet_read(rpc->out, buf, left, 0); + n = packet_read(rpc->out, NULL, NULL, buf, left, 0); if (!n) break; rpc->len += n; @@ -579,7 +577,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->hdr_accept = strbuf_detach(&buf, NULL); while (!err) { - int n = packet_read(rpc->out, rpc->buf, rpc->alloc, 0); + int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!n) break; rpc->pos = 0; diff --git a/sideband.c b/sideband.c index 15cc1aec2..d1125f5c5 100644 --- a/sideband.c +++ b/sideband.c @@ -38,7 +38,7 @@ int recv_sideband(const char *me, int in_stream, int out) while (1) { int band, len; - len = packet_read(in_stream, buf + pf, LARGE_PACKET_MAX, 0); + len = packet_read(in_stream, NULL, NULL, buf + pf, LARGE_PACKET_MAX, 0); if (len == 0) break; if (len < 1) { From 85edf4f58b5368e2f2acc4bce0d10e1ca9d6c879 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:06:45 -0500 Subject: [PATCH 16/20] teach get_remote_heads to read from a memory buffer Now that we can read packet data from memory as easily as a descriptor, get_remote_heads can take either one as a source. This will allow further refactoring in remote-curl. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/fetch-pack.c | 2 +- builtin/send-pack.c | 2 +- cache.h | 4 +++- connect.c | 6 +++--- remote-curl.c | 2 +- transport.c | 6 +++--- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index c21cc2c77..03ed2caae 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -125,7 +125,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], &ref, 0, NULL); + get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL); ref = fetch_pack(&args, fd, conn, ref, dest, &sought, pack_lockfile_ptr); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 87785197c..152c4ea09 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -207,7 +207,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) memset(&extra_have, 0, sizeof(extra_have)); - get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have); + get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have); transport_verify_remote_names(nr_refspecs, refspecs); diff --git a/cache.h b/cache.h index e493563f4..db646a2ff 100644 --- a/cache.h +++ b/cache.h @@ -1049,7 +1049,9 @@ struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; }; -extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *); +extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, + struct extra_have_objects *); extern int server_supports(const char *feature); extern int parse_feature_request(const char *features, const char *feature); extern const char *server_feature_value(const char *feature, int *len_ret); diff --git a/connect.c b/connect.c index 3d999999e..f57efd06c 100644 --- a/connect.c +++ b/connect.c @@ -62,8 +62,8 @@ static void die_initial_contact(int got_at_least_one_head) /* * Read all the refs from the other end */ -struct ref **get_remote_heads(int in, struct ref **list, - unsigned int flags, +struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, struct extra_have_objects *extra_have) { int got_at_least_one_head = 0; @@ -76,7 +76,7 @@ struct ref **get_remote_heads(int in, struct ref **list, int len, name_len; char *buffer = packet_buffer; - len = packet_read(in, NULL, NULL, + len = packet_read(in, &src_buf, &src_len, packet_buffer, sizeof(packet_buffer), PACKET_READ_GENTLE_ON_EOF | PACKET_READ_CHOMP_NEWLINE); diff --git a/remote-curl.c b/remote-curl.c index c8379a53f..24c86264d 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -192,7 +192,7 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push) if (start_async(&async)) die("cannot start thread to parse advertised refs"); - get_remote_heads(async.out, &list, + get_remote_heads(async.out, NULL, 0, &list, for_push ? REF_NORMAL : 0, NULL); close(async.out); if (finish_async(&async)) diff --git a/transport.c b/transport.c index 886ffd8b1..62df466c1 100644 --- a/transport.c +++ b/transport.c @@ -507,7 +507,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus struct ref *refs; connect_setup(transport, for_push, 0); - get_remote_heads(data->fd[0], &refs, + get_remote_heads(data->fd[0], NULL, 0, &refs, for_push ? REF_NORMAL : 0, &data->extra_have); data->got_remote_heads = 1; @@ -541,7 +541,7 @@ static int fetch_refs_via_pack(struct transport *transport, if (!data->got_remote_heads) { connect_setup(transport, 0, 0); - get_remote_heads(data->fd[0], &refs_tmp, 0, NULL); + get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, NULL); data->got_remote_heads = 1; } @@ -799,7 +799,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re struct ref *tmp_refs; connect_setup(transport, 1, 0); - get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL); + get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL); data->got_remote_heads = 1; } From 5dbf43602de31cac518607b4d9a04a40d7b154c8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:07:02 -0500 Subject: [PATCH 17/20] remote-curl: pass buffer straight to get_remote_heads Until recently, get_remote_heads only knew how to read refs from a file descriptor. To hack around this, we spawned a thread (or forked a process) to write the buffer back to us. Now that we can just pass it our buffer directly, we don't have to use this hack anymore. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- remote-curl.c | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 24c86264d..832f441b1 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -170,33 +170,11 @@ static struct discovery* discover_refs(const char *service) return last; } -static int write_discovery(int in, int out, void *data) -{ - struct discovery *heads = data; - int err = 0; - if (write_in_full(out, heads->buf, heads->len) != heads->len) - err = 1; - close(out); - return err; -} - static struct ref *parse_git_refs(struct discovery *heads, int for_push) { struct ref *list = NULL; - struct async async; - - memset(&async, 0, sizeof(async)); - async.proc = write_discovery; - async.data = heads; - async.out = -1; - - if (start_async(&async)) - die("cannot start thread to parse advertised refs"); - get_remote_heads(async.out, NULL, 0, &list, - for_push ? REF_NORMAL : 0, NULL); - close(async.out); - if (finish_async(&async)) - die("ref parsing thread failed"); + get_remote_heads(-1, heads->buf, heads->len, &list, + for_push ? REF_NORMAL : 0, NULL); return list; } From b8054bbee70620b57e11ed78716bf187071bc368 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:07:11 -0500 Subject: [PATCH 18/20] remote-curl: move ref-parsing code up in file The ref-parsing functions are static. Let's move them up in the file to be available to more functions, which will help us with later refactoring. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- remote-curl.c | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 832f441b1..a36c16673 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -80,6 +80,65 @@ struct discovery { }; static struct discovery *last_discovery; +static struct ref *parse_git_refs(struct discovery *heads, int for_push) +{ + struct ref *list = NULL; + get_remote_heads(-1, heads->buf, heads->len, &list, + for_push ? REF_NORMAL : 0, NULL); + return list; +} + +static struct ref *parse_info_refs(struct discovery *heads) +{ + char *data, *start, *mid; + char *ref_name; + int i = 0; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = heads->buf; + start = NULL; + mid = data; + while (i < heads->len) { + if (!start) { + start = &data[i]; + } + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + if (mid - start != 40) + die("%sinfo/refs not valid: is this a git repository?", url); + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + ref = alloc_ref("HEAD"); + if (!http_fetch_ref(url, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + + return refs; +} + static void free_discovery(struct discovery *d) { if (d) { @@ -170,65 +229,6 @@ static struct discovery* discover_refs(const char *service) return last; } -static struct ref *parse_git_refs(struct discovery *heads, int for_push) -{ - struct ref *list = NULL; - get_remote_heads(-1, heads->buf, heads->len, &list, - for_push ? REF_NORMAL : 0, NULL); - return list; -} - -static struct ref *parse_info_refs(struct discovery *heads) -{ - char *data, *start, *mid; - char *ref_name; - int i = 0; - - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; - - data = heads->buf; - start = NULL; - mid = data; - while (i < heads->len) { - if (!start) { - start = &data[i]; - } - if (data[i] == '\t') - mid = &data[i]; - if (data[i] == '\n') { - if (mid - start != 40) - die("%sinfo/refs not valid: is this a git repository?", url); - data[i] = 0; - ref_name = mid + 1; - ref = xmalloc(sizeof(struct ref) + - strlen(ref_name) + 1); - memset(ref, 0, sizeof(struct ref)); - strcpy(ref->name, ref_name); - get_sha1_hex(start, ref->old_sha1); - if (!refs) - refs = ref; - if (last_ref) - last_ref->next = ref; - last_ref = ref; - start = NULL; - } - i++; - } - - ref = alloc_ref("HEAD"); - if (!http_fetch_ref(url, ref) && - !resolve_remote_symref(ref, refs)) { - ref->next = refs; - refs = ref; - } else { - free(ref); - } - - return refs; -} - static struct ref *get_refs(int for_push) { struct discovery *heads; From 2a4552021a92be17c7c4d2d2313df9913e8eb4bf Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Feb 2013 15:07:19 -0500 Subject: [PATCH 19/20] remote-curl: always parse incoming refs When remote-curl receives a list of refs from a server, it keeps the whole buffer intact. When we get a "list" command, we feed the result to get_remote_heads, and when we get a "fetch" or "push" command, we feed it to fetch-pack or send-pack, respectively. If the HTTP response from the server is truncated for any reason, we will get an incomplete ref advertisement. If we then feed this incomplete list to fetch-pack, one of a few things may happen: 1. If the truncation is in a packet header, fetch-pack will notice the bogus line and complain. 2. If the truncation is inside a packet, fetch-pack will keep waiting for us to send the rest of the packet, which we never will. 3. If the truncation is at a packet boundary, fetch-pack will keep waiting for us to send the next packet, which we never will. As a result, fetch-pack hangs, waiting for input. However, remote-curl believes it has sent all of the advertisement, and therefore waits for fetch-pack to speak. The two processes end up in a deadlock. We do notice the broken ref list if we feed it to get_remote_heads. So if git asks the helper to do a "list" followed by a "fetch", we are safe; we'll abort during the list operation, which parses the refs. This patch teaches remote-curl to always parse and save the incoming ref list when we read the ref advertisement from a server. That means that we will always verify and abort before even running fetch-pack (or send-pack) when reading a corrupted list, even if we do not run the "list" command explicitly. Since we save the result, in the common case of running "list" then "fetch", we do not do any extra parsing at all. In the case of just a "fetch", we do an extra round of parsing, but only once. Note also that the "fetch" case will now also initialize server_capabilities from the remote (in remote-curl; we already would do so inside fetch-pack). Doing "list+fetch" already does this. It doesn't actually matter now, but the new behavior is arguably more correct, should remote-curl ever start caring about the server's capability list. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- remote-curl.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index a36c16673..93a09a64c 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -76,6 +76,7 @@ struct discovery { char *buf_alloc; char *buf; size_t len; + struct ref *refs; unsigned proto_git : 1; }; static struct discovery *last_discovery; @@ -145,11 +146,12 @@ static void free_discovery(struct discovery *d) if (d == last_discovery) last_discovery = NULL; free(d->buf_alloc); + free_refs(d->refs); free(d); } } -static struct discovery* discover_refs(const char *service) +static struct discovery* discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; struct strbuf type = STRBUF_INIT; @@ -221,6 +223,11 @@ static struct discovery* discover_refs(const char *service) last->proto_git = 1; } + if (last->proto_git) + last->refs = parse_git_refs(last, for_push); + else + last->refs = parse_info_refs(last); + free(refs_url); strbuf_release(&exp); strbuf_release(&type); @@ -234,13 +241,11 @@ static struct ref *get_refs(int for_push) struct discovery *heads; if (for_push) - heads = discover_refs("git-receive-pack"); + heads = discover_refs("git-receive-pack", for_push); else - heads = discover_refs("git-upload-pack"); + heads = discover_refs("git-upload-pack", for_push); - if (heads->proto_git) - return parse_git_refs(heads, for_push); - return parse_info_refs(heads); + return heads->refs; } static void output_refs(struct ref *refs) @@ -254,7 +259,6 @@ static void output_refs(struct ref *refs) } printf("\n"); fflush(stdout); - free_refs(refs); } struct rpc_state { @@ -670,7 +674,7 @@ static int fetch_git(struct discovery *heads, static int fetch(int nr_heads, struct ref **to_fetch) { - struct discovery *d = discover_refs("git-upload-pack"); + struct discovery *d = discover_refs("git-upload-pack", 0); if (d->proto_git) return fetch_git(d, nr_heads, to_fetch); else @@ -789,7 +793,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) static int push(int nr_spec, char **specs) { - struct discovery *heads = discover_refs("git-receive-pack"); + struct discovery *heads = discover_refs("git-receive-pack", 1); int ret; if (heads->proto_git) From 2ad23273e71ff708936bd924502186b8789a7289 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Mar 2013 13:43:47 -0400 Subject: [PATCH 20/20] do not use GIT_TRACE_PACKET=3 in tests Some test scripts use the GIT_TRACE mechanism to dump debugging information to descriptor 3 (and point it to a file using the shell). On Windows, however, bash is unable to set up descriptor 3. We do not write our trace to the file, and worse, we may interfere with other operations happening on descriptor 3, causing tests to fail or even behave inconsistently. Prior to commit 97a83fa (upload-pack: remove packet debugging harness), these tests used GIT_DEBUG_SEND_PACK, which only supported output to a descriptor. The tests in t5503 were always broken on Windows, and were marked to be skipped via the NOT_MINGW prerequisite. In t5700, the tests used to pass prior to 97a83fa, but only because they were not careful enough; because we only grepped the trace file, an empty file looked successful to us. But post-97a83fa, the writing to descriptor 3 causes "git fetch" to hang (presumably because we are throwing random bytes into the middle of the protocol). Now that we are using the GIT_TRACE mechanism, we can improve both scripts by asking git to write directly to a file rather than a descriptor. That fixes the hang in t5700, and should allow t5503 to successfully run on Windows. In both cases we now also use "test -s" to double-check that our trace file actually contains output, which should reduce the possibility of an erroneously passing test. Signed-off-by: Jeff King Tested-by: Johannes Sixt Signed-off-by: Junio C Hamano --- t/t5503-tagfollow.sh | 38 ++++++++++++++++++-------------------- t/t5700-clone-reference.sh | 14 +++++++++----- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index d181c9681..f30c03885 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -4,10 +4,6 @@ test_description='test automatic tag following' . ./test-lib.sh -if ! test_have_prereq NOT_MINGW; then - say "GIT_TRACE_PACKET not supported - skipping tests" -fi - # End state of the repository: # # T - tag1 S - tag2 @@ -17,7 +13,7 @@ fi # \ C - origin/cat \ # origin/master master -test_expect_success NOT_MINGW setup ' +test_expect_success setup ' test_tick && echo ichi >file && git add file && @@ -39,33 +35,35 @@ test_expect_success NOT_MINGW setup ' ' U=UPLOAD_LOG +UPATH="$(pwd)/$U" -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect want $A EOF ' get_needs () { + test -s "$1" && perl -alne ' next unless $F[1] eq "upload-pack<"; last if $F[2] eq "0000"; print $F[2], " ", $F[3]; - ' "$@" + ' "$1" } -test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' ' +test_expect_success 'fetch A (new commit : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_TRACE_PACKET=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $A = $(git rev-parse --verify origin/master) ) && get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' +test_expect_success "create tag T on A, create C on branch cat" ' git tag -a -m tag1 tag1 $A && T=$(git rev-parse --verify tag1) && @@ -77,18 +75,18 @@ test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' git checkout master ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect want $C want $T EOF ' -test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' ' +test_expect_success 'fetch C, T (new branch, tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_TRACE_PACKET=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $C = $(git rev-parse --verify origin/cat) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) @@ -97,7 +95,7 @@ test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' ' test_cmp expect actual ' -test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' +test_expect_success "create commits O, B, tag S on B" ' test_tick && echo O >file && git add file && @@ -113,18 +111,18 @@ test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' S=$(git rev-parse --verify tag2) ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect want $B want $S EOF ' -test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' ' +test_expect_success 'fetch B, S (commit and tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_TRACE_PACKET=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $B = $(git rev-parse --verify origin/master) && test $B = $(git rev-parse --verify tag2^0) && test $S = $(git rev-parse --verify tag2) @@ -133,14 +131,14 @@ test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' ' test_cmp expect actual ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect want $B want $S EOF ' -test_expect_success NOT_MINGW 'new clone fetch master and tags' ' +test_expect_success 'new clone fetch master and tags' ' git branch -D cat rm -f $U ( @@ -148,7 +146,7 @@ test_expect_success NOT_MINGW 'new clone fetch master and tags' ' cd clone2 && git init && git remote add origin .. && - GIT_TRACE_PACKET=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $B = $(git rev-parse --verify origin/master) && test $S = $(git rev-parse --verify tag2) && test $B = $(git rev-parse --verify tag2^0) && diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 9cd3b4d10..60f1552ad 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -54,11 +54,14 @@ cd "$base_dir" rm -f "$U.D" -test_expect_success 'cloning with reference (no -l -s)' \ -'GIT_TRACE_PACKET=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"' +test_expect_success 'cloning with reference (no -l -s)' ' + GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D +' -test_expect_success 'fetched no objects' \ -'! grep " want" "$U.D"' +test_expect_success 'fetched no objects' ' + test -s "$U.D" && + ! grep " want" "$U.D" +' cd "$base_dir" @@ -173,9 +176,10 @@ test_expect_success 'fetch with incomplete alternates' ' ( cd K && git remote add J "file://$base_dir/J" && - GIT_TRACE_PACKET=3 git fetch J 3>"$U.K" + GIT_TRACE_PACKET=$U.K git fetch J ) && master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) && + test -s "$U.K" && ! grep " want $master_object" "$U.K" && tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && ! grep " want $tag_object" "$U.K"