From ff5effdf455bd6e694ff0bb3e0793515a2214adb Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 3 Aug 2012 12:19:16 -0400 Subject: include agent identifier in capability string Instead of having the client advertise a particular version number in the git protocol, we have managed extensions and backwards compatibility by having clients and servers advertise capabilities that they support. This is far more robust than having each side consult a table of known versions, and provides sufficient information for the protocol interaction to complete. However, it does not allow servers to keep statistics on which client versions are being used. This information is not necessary to complete the network request (the capabilities provide enough information for that), but it may be helpful to conduct a general survey of client versions in use. We already send the client version in the user-agent header for http requests; adding it here allows us to gather similar statistics for non-http requests. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 149db88..fe56596 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -10,6 +10,7 @@ #include "remote.h" #include "run-command.h" #include "transport.h" +#include "version.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -327,6 +328,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.no_progress) strbuf_addstr(&c, " no-progress"); if (args.include_tag) strbuf_addstr(&c, " include-tag"); if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); + strbuf_addf(&c, " agent=%s", git_user_agent_sanitized()); packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); strbuf_release(&c); } else diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 0afb8b2..fbfa128 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -12,6 +12,7 @@ #include "string-list.h" #include "sha1-array.h" #include "connected.h" +#include "version.h" static const char receive_pack_usage[] = "git receive-pack "; @@ -121,10 +122,11 @@ static void show_ref(const char *path, const unsigned char *sha1) if (sent_capabilities) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else - packet_write(1, "%s %s%c%s%s\n", + packet_write(1, "%s %s%c%s%s agent=%s\n", sha1_to_hex(sha1), path, 0, " report-status delete-refs side-band-64k quiet", - prefer_ofs_delta ? " ofs-delta" : ""); + prefer_ofs_delta ? " ofs-delta" : "", + git_user_agent_sanitized()); sent_capabilities = 1; } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index d5d7105..c4d4211 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -8,6 +8,7 @@ #include "send-pack.h" #include "quote.h" #include "transport.h" +#include "version.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=] [--verbose] [--thin] [:] [...]\n" @@ -306,11 +307,13 @@ int send_pack(struct send_pack_args *args, int quiet = quiet_supported && (args->quiet || !args->progress); if (!cmds_sent && (status_report || use_sideband || args->quiet)) { - packet_buf_write(&req_buf, "%s %s %s%c%s%s%s", + packet_buf_write(&req_buf, + "%s %s %s%c%s%s%s agent=%s", old_hex, new_hex, ref->name, 0, status_report ? " report-status" : "", use_sideband ? " side-band-64k" : "", - quiet ? " quiet" : ""); + quiet ? " quiet" : "", + git_user_agent_sanitized()); } else packet_buf_write(&req_buf, "%s %s %s", diff --git a/upload-pack.c b/upload-pack.c index bb08e2e..2e90ccb 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -11,6 +11,7 @@ #include "list-objects.h" #include "run-command.h" #include "sigchain.h" +#include "version.h" static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=] "; @@ -734,9 +735,11 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo } if (capabilities) - packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname_nons, + packet_write(1, "%s %s%c%s%s agent=%s\n", + sha1_to_hex(sha1), refname_nons, 0, capabilities, - stateless_rpc ? " no-done" : ""); + stateless_rpc ? " no-done" : "", + git_user_agent_sanitized()); else packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons); capabilities = NULL; diff --git a/version.c b/version.c index f98d5a6..6106a80 100644 --- a/version.c +++ b/version.c @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "version.h" +#include "strbuf.h" const char git_version_string[] = GIT_VERSION; @@ -15,3 +16,23 @@ const char *git_user_agent(void) return agent; } + +const char *git_user_agent_sanitized(void) +{ + static const char *agent = NULL; + + if (!agent) { + struct strbuf buf = STRBUF_INIT; + int i; + + strbuf_addstr(&buf, git_user_agent()); + strbuf_trim(&buf); + for (i = 0; i < buf.len; i++) { + if (buf.buf[i] <= 32 || buf.buf[i] >= 127) + buf.buf[i] = '.'; + } + agent = buf.buf; + } + + return agent; +} diff --git a/version.h b/version.h index fd9cdd6..6911a4f 100644 --- a/version.h +++ b/version.h @@ -4,5 +4,6 @@ extern const char git_version_string[]; const char *git_user_agent(void); +const char *git_user_agent_sanitized(void); #endif /* VERSION_H */ -- cgit v0.10.2-6-g49f6 From ca8e127c9bd65b07a565ca00aff8e0a48628bfc3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 10 Aug 2012 03:57:31 -0400 Subject: send-pack: fix capability-sending logic If we have capabilities to send to the server, we send the regular "want" line followed by a NUL, then the capabilities; otherwise, we do not even send the NUL. However, when checking whether we want to send the "quiet" capability, we check args->quiet, which is wrong. That flag only tells us whether the client side wanted to be quiet, not whether the server supports it (originally, in c207e34f, it meant both; however, that was later split into two flags by 01fdc21f). We still check the right flag when actually printing "quiet", so this could only have two effects: 1. We might send the trailing NUL when we do not otherwise need to. In theory, an antique pre-capability implementation of git might choke on this (since the client is instructed never to respond with capabilities that the server has not first advertised). 2. We might also want to send the quiet flag if the args->progress flag is false, but this code path would not trigger in that instance. In practice, it almost certainly never matters. The report-status capability dates back to 2005. Any real-world server is going to advertise that, and we will always respond with at least that capability. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin/send-pack.c b/builtin/send-pack.c index c4d4211..5c69995 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -306,7 +306,7 @@ int send_pack(struct send_pack_args *args, char *new_hex = sha1_to_hex(ref->new_sha1); int quiet = quiet_supported && (args->quiet || !args->progress); - if (!cmds_sent && (status_report || use_sideband || args->quiet)) { + if (!cmds_sent && (status_report || use_sideband || quiet)) { packet_buf_write(&req_buf, "%s %s %s%c%s%s%s agent=%s", old_hex, new_hex, ref->name, 0, -- cgit v0.10.2-6-g49f6 From d50c3871632451b0cac066eea178198ac8a9dff1 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 10 Aug 2012 03:57:43 -0400 Subject: do not send client agent unless server does first Commit ff5effdf taught both clients and servers of the git protocol to send an "agent" capability that just advertises their version for statistics and debugging purposes. The protocol-capabilities.txt document however indicates that the client's advertisement is actually a response, and should never include capabilities not mentioned in the server's advertisement. Adding the unconditional advertisement in the server programs was OK, then, but the clients broke the protocol. The server implementation of git-core itself does not care, but at least one does: the Google Code git server (or any server using Dulwich), will hang up with an internal error upon seeing an unknown capability. Instead, each client must record whether we saw an agent string from the server, and respond with its agent only if the server mentioned it first. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index fe56596..bc7a0f9 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -19,6 +19,7 @@ static int prefer_ofs_delta = 1; static int no_done; static int fetch_fsck_objects = -1; static int transfer_fsck_objects = -1; +static int agent_supported; static struct fetch_pack_args args = { /* .uploadpack = */ "git-upload-pack", }; @@ -328,7 +329,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.no_progress) strbuf_addstr(&c, " no-progress"); if (args.include_tag) strbuf_addstr(&c, " include-tag"); if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); - strbuf_addf(&c, " agent=%s", git_user_agent_sanitized()); + if (agent_supported) strbuf_addf(&c, " agent=%s", + git_user_agent_sanitized()); packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); strbuf_release(&c); } else @@ -821,6 +823,9 @@ static struct ref *do_fetch_pack(int fd[2], fprintf(stderr, "Server supports ofs-delta\n"); } else prefer_ofs_delta = 0; + if (server_supports("agent")) + agent_supported = 1; + if (everything_local(&ref, nr_match, match)) { packet_flush(fd[1]); goto all_done; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 5c69995..7d05064 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -252,6 +252,7 @@ int send_pack(struct send_pack_args *args, int status_report = 0; int use_sideband = 0; int quiet_supported = 0; + int agent_supported = 0; unsigned cmds_sent = 0; int ret; struct async demux; @@ -267,6 +268,8 @@ int send_pack(struct send_pack_args *args, use_sideband = 1; if (server_supports("quiet")) quiet_supported = 1; + if (server_supports("agent")) + agent_supported = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@ -306,14 +309,17 @@ int send_pack(struct send_pack_args *args, char *new_hex = sha1_to_hex(ref->new_sha1); int quiet = quiet_supported && (args->quiet || !args->progress); - if (!cmds_sent && (status_report || use_sideband || quiet)) { + if (!cmds_sent && (status_report || use_sideband || + quiet || agent_supported)) { packet_buf_write(&req_buf, - "%s %s %s%c%s%s%s agent=%s", + "%s %s %s%c%s%s%s%s%s", old_hex, new_hex, ref->name, 0, status_report ? " report-status" : "", use_sideband ? " side-band-64k" : "", quiet ? " quiet" : "", - git_user_agent_sanitized()); + agent_supported ? " agent=" : "", + agent_supported ? git_user_agent_sanitized() : "" + ); } else packet_buf_write(&req_buf, "%s %s %s", -- cgit v0.10.2-6-g49f6 From 74991a98df79ff3702dcb3b5c22c7b9ec20cfead Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 10 Aug 2012 14:27:52 -0700 Subject: fetch-pack: do not ask for unadvertised capabilities In the same spirit as the previous fix, stop asking for thin-pack, no-progress and include-tag capabilities when the other end does not claim to support them. Signed-off-by: Junio C Hamano diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index bc7a0f9..fdec7f6 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -818,6 +818,12 @@ static struct ref *do_fetch_pack(int fd[2], fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; } + if (!server_supports("thin-pack")) + args.use_thin_pack = 0; + if (!server_supports("no-progress")) + args.no_progress = 0; + if (!server_supports("include-tag")) + args.include_tag = 0; if (server_supports("ofs-delta")) { if (args.verbose) fprintf(stderr, "Server supports ofs-delta\n"); -- cgit v0.10.2-6-g49f6 From 9442710801b0b7b9aeefe60408d1835af138cfcc Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 13 Aug 2012 21:59:27 -0400 Subject: parse_feature_request: make it easier to see feature values We already take care to parse key/value capabilities like "foo=bar", but the code does not provide a good way of actually finding out what is on the right-hand side of the "=". A server using "parse_feature_request" could accomplish this with some extra parsing. You must skip past the "key" portion manually, check for "=" versus NUL or space, and then find the length by searching for the next space (or NUL). But clients can't even do that, since the "server_supports" interface does not even return the pointer. Instead, let's have our parser share more information by providing a pointer to the value and its length. The "parse_feature_value" function returns a pointer to the feature's value portion, along with the length of the value. If the feature is missing, NULL is returned. If it does not have an "=", then a zero-length value is returned. Similarly, "server_feature_value" behaves in the same way, but always checks the static server_feature_list variable. We can then implement "server_supports" in terms of "server_feature_value". We cannot implement the original "parse_feature_request" in terms of our new function, because it returned a pointer to the beginning of the feature. However, no callers actually cared about the value of the returned pointer, so we can simplify it to a boolean just as we do for "server_supports". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 06413e1..b71b065 100644 --- a/cache.h +++ b/cache.h @@ -1030,7 +1030,9 @@ struct extra_have_objects { }; extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *); extern int server_supports(const char *feature); -extern const char *parse_feature_request(const char *features, 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); +extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret); extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); diff --git a/connect.c b/connect.c index 912cdde..9aec10c 100644 --- a/connect.c +++ b/connect.c @@ -99,12 +99,7 @@ struct ref **get_remote_heads(int in, struct ref **list, return list; } -int server_supports(const char *feature) -{ - return !!parse_feature_request(server_capabilities, feature); -} - -const char *parse_feature_request(const char *feature_list, const char *feature) +const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp) { int len; @@ -116,14 +111,46 @@ const char *parse_feature_request(const char *feature_list, const char *feature) const char *found = strstr(feature_list, feature); if (!found) return NULL; - if ((feature_list == found || isspace(found[-1])) && - (!found[len] || isspace(found[len]) || found[len] == '=')) - return found; + if (feature_list == found || isspace(found[-1])) { + const char *value = found + len; + /* feature with no value (e.g., "thin-pack") */ + if (!*value || isspace(*value)) { + if (lenp) + *lenp = 0; + return value; + } + /* feature with a value (e.g., "agent=git/1.2.3") */ + else if (*value == '=') { + value++; + if (lenp) + *lenp = strcspn(value, " \t\n"); + return value; + } + /* + * otherwise we matched a substring of another feature; + * keep looking + */ + } feature_list = found + 1; } return NULL; } +int parse_feature_request(const char *feature_list, const char *feature) +{ + return !!parse_feature_value(feature_list, feature, NULL); +} + +const char *server_feature_value(const char *feature, int *len) +{ + return parse_feature_value(server_capabilities, feature, len); +} + +int server_supports(const char *feature) +{ + return !!server_feature_value(feature, NULL); +} + enum protocol { PROTO_LOCAL = 1, PROTO_SSH, -- cgit v0.10.2-6-g49f6 From 36c60f7a08b28e2cee649d697291ac6b708b213f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 13 Aug 2012 22:02:10 -0400 Subject: fetch-pack: mention server version with verbose output Fetch-pack's verbose mode is more of a debugging mode (and in fact takes two "-v" arguments to trigger via the porcelain layer). Let's mention the server version as another possible item of interest. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index fdec7f6..fdda36f 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -787,6 +787,8 @@ static struct ref *do_fetch_pack(int fd[2], { struct ref *ref = copy_ref_list(orig_ref); unsigned char sha1[20]; + const char *agent_feature; + int agent_len; sort_ref_list(&ref, ref_compare_name); @@ -829,8 +831,13 @@ static struct ref *do_fetch_pack(int fd[2], fprintf(stderr, "Server supports ofs-delta\n"); } else prefer_ofs_delta = 0; - if (server_supports("agent")) + + if ((agent_feature = server_feature_value("agent", &agent_len))) { agent_supported = 1; + if (args.verbose && agent_len) + fprintf(stderr, "Server version is %.*s\n", + agent_len, agent_feature); + } if (everything_local(&ref, nr_match, match)) { packet_flush(fd[1]); -- cgit v0.10.2-6-g49f6