Currently the client advertises that it supports the wire protocol
version set in the protocol.version config. However, not all services
support the same set of protocol versions. For example, git-receive-pack
supports v1 and v0, but not v2. If a client connects to git-receive-pack
and requests v2, it will instead be downgraded to v0. Other services,
such as git-upload-archive, do not do any version negotiation checks.

This patch creates a protocol version registry. Individual client and
server programs register all the protocol versions they support prior to
communicating with a remote instance. Versions should be listed in
preference order; the version specified in protocol.version will
automatically be moved to the front of the registry.

The protocol version registry is passed to remote helpers via the
GIT_PROTOCOL environment variable.

Clients now advertise the full list of registered versions. Servers
select the first allowed version from this advertisement.

Additionally, remove special cases around advertising version=0.
Previously we avoided adding version advertisements to the client's
initial connection request if the client wanted version=0. However,
including these advertisements does not change the version negotiation
behavior, so it's better to have simpler code. As a side effect, this
means that client operations over SSH will always include a
"SendEnv=GIT_PROTOCOL" option on the SSH command line.

While we're at it, remove unnecessary externs from function declarations
in protocol.h.

Signed-off-by: Josh Steadmon <stead...@google.com>
---
 builtin/archive.c           |   3 +
 builtin/clone.c             |   4 ++
 builtin/fetch-pack.c        |   4 ++
 builtin/fetch.c             |   5 ++
 builtin/ls-remote.c         |   5 ++
 builtin/pull.c              |   5 ++
 builtin/push.c              |   4 ++
 builtin/receive-pack.c      |   3 +
 builtin/send-pack.c         |   3 +
 builtin/upload-archive.c    |   3 +
 builtin/upload-pack.c       |   4 ++
 connect.c                   |  52 +++++++--------
 protocol.c                  | 122 +++++++++++++++++++++++++++++++++---
 protocol.h                  |  23 ++++++-
 remote-curl.c               |  27 +++++---
 t/t5551-http-fetch-smart.sh |   1 +
 t/t5570-git-daemon.sh       |   2 +-
 t/t5601-clone.sh            |  38 +++++------
 t/t5700-protocol-v1.sh      |   8 +--
 t/t5702-protocol-v2.sh      |  16 +++--
 transport-helper.c          |   6 ++
 21 files changed, 256 insertions(+), 82 deletions(-)

diff --git a/builtin/archive.c b/builtin/archive.c
index e74f675390..8adb9f381b 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -8,6 +8,7 @@
 #include "transport.h"
 #include "parse-options.h"
 #include "pkt-line.h"
+#include "protocol.h"
 #include "sideband.h"
 
 static void create_output_file(const char *output_file)
@@ -94,6 +95,8 @@ int cmd_archive(int argc, const char **argv, const char 
*prefix)
                OPT_END()
        };
 
+       register_allowed_protocol_version(protocol_v0);
+
        argc = parse_options(argc, argv, prefix, local_opts, NULL,
                             PARSE_OPT_KEEP_ALL);
 
diff --git a/builtin/clone.c b/builtin/clone.c
index fd2c3ef090..1651a950b6 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -900,6 +900,10 @@ int cmd_clone(int argc, const char **argv, const char 
*prefix)
        struct refspec rs = REFSPEC_INIT_FETCH;
        struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
 
+       register_allowed_protocol_version(protocol_v2);
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        fetch_if_missing = 0;
 
        packet_trace_identity("clone");
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index 1a1bc63566..cba935e4d3 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -57,6 +57,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char 
*prefix)
 
        fetch_if_missing = 0;
 
+       register_allowed_protocol_version(protocol_v2);
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        packet_trace_identity("fetch-pack");
 
        memset(&args, 0, sizeof(args));
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 61bec5d213..2a20cf8bfd 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -21,6 +21,7 @@
 #include "argv-array.h"
 #include "utf8.h"
 #include "packfile.h"
+#include "protocol.h"
 #include "list-objects-filter-options.h"
 
 static const char * const builtin_fetch_usage[] = {
@@ -1476,6 +1477,10 @@ int cmd_fetch(int argc, const char **argv, const char 
*prefix)
        int prune_tags_ok = 1;
        struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
 
+       register_allowed_protocol_version(protocol_v2);
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        packet_trace_identity("fetch");
 
        fetch_if_missing = 0;
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 1a25df7ee1..ea685e8bb9 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "protocol.h"
 #include "transport.h"
 #include "ref-filter.h"
 #include "remote.h"
@@ -80,6 +81,10 @@ int cmd_ls_remote(int argc, const char **argv, const char 
*prefix)
 
        memset(&ref_array, 0, sizeof(ref_array));
 
+       register_allowed_protocol_version(protocol_v2);
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        argc = parse_options(argc, argv, prefix, options, ls_remote_usage,
                             PARSE_OPT_STOP_AT_NON_OPTION);
        dest = argv[0];
diff --git a/builtin/pull.c b/builtin/pull.c
index 681c127a07..cb64146834 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -9,6 +9,7 @@
 #include "config.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "protocol.h"
 #include "exec-cmd.h"
 #include "run-command.h"
 #include "sha1-array.h"
@@ -849,6 +850,10 @@ int cmd_pull(int argc, const char **argv, const char 
*prefix)
        struct object_id rebase_fork_point;
        int autostash;
 
+       register_allowed_protocol_version(protocol_v2);
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        if (!getenv("GIT_REFLOG_ACTION"))
                set_reflog_message(argc, argv);
 
diff --git a/builtin/push.c b/builtin/push.c
index d09a42062c..10d8abe829 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "transport.h"
 #include "parse-options.h"
+#include "protocol.h"
 #include "submodule.h"
 #include "submodule-config.h"
 #include "send-pack.h"
@@ -587,6 +588,9 @@ int cmd_push(int argc, const char **argv, const char 
*prefix)
                OPT_END()
        };
 
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        packet_trace_identity("push");
        git_config(git_push_config, &flags);
        argc = parse_options(argc, argv, prefix, options, push_usage, 0);
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c17ce94e12..030cb7b7ec 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1929,6 +1929,9 @@ int cmd_receive_pack(int argc, const char **argv, const 
char *prefix)
 
        packet_trace_identity("receive-pack");
 
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 
0);
 
        if (argc > 1)
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 8e3c7490f7..f48bd1306b 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -184,6 +184,9 @@ int cmd_send_pack(int argc, const char **argv, const char 
*prefix)
                OPT_END()
        };
 
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        git_config(send_pack_config, NULL);
        argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0);
        if (argc > 0) {
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 25d9116356..791cbe80a6 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -5,6 +5,7 @@
 #include "builtin.h"
 #include "archive.h"
 #include "pkt-line.h"
+#include "protocol.h"
 #include "sideband.h"
 #include "run-command.h"
 #include "argv-array.h"
@@ -80,6 +81,8 @@ int cmd_upload_archive(int argc, const char **argv, const 
char *prefix)
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(upload_archive_usage);
 
+       register_allowed_protocol_version(protocol_v0);
+
        /*
         * Set up sideband subprocess.
         *
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
index 42dc4da5a1..293dd45b9e 100644
--- a/builtin/upload-pack.c
+++ b/builtin/upload-pack.c
@@ -33,6 +33,10 @@ int cmd_upload_pack(int argc, const char **argv, const char 
*prefix)
        packet_trace_identity("upload-pack");
        read_replace_refs = 0;
 
+       register_allowed_protocol_version(protocol_v2);
+       register_allowed_protocol_version(protocol_v1);
+       register_allowed_protocol_version(protocol_v0);
+
        argc = parse_options(argc, argv, NULL, options, upload_pack_usage, 0);
 
        if (argc != 1)
diff --git a/connect.c b/connect.c
index 94547e5056..57266b6cec 100644
--- a/connect.c
+++ b/connect.c
@@ -1046,7 +1046,7 @@ static enum ssh_variant determine_ssh_variant(const char 
*ssh_command,
  */
 static struct child_process *git_connect_git(int fd[2], char *hostandport,
                                             const char *path, const char *prog,
-                                            enum protocol_version version,
+                                            const struct strbuf 
*version_advert,
                                             int flags)
 {
        struct child_process *conn;
@@ -1084,12 +1084,9 @@ static struct child_process *git_connect_git(int fd[2], 
char *hostandport,
                    prog, path, 0,
                    target_host, 0);
 
-       /* If using a new version put that stuff here after a second null byte 
*/
-       if (version > 0) {
-               strbuf_addch(&request, '\0');
-               strbuf_addf(&request, "version=%d%c",
-                           version, '\0');
-       }
+       /* Add version fields after a second null byte */
+       strbuf_addch(&request, '\0');
+       strbuf_addf(&request, "%s%c", version_advert->buf, '\0');
 
        packet_write(fd[1], request.buf, request.len);
 
@@ -1104,14 +1101,13 @@ static struct child_process *git_connect_git(int fd[2], 
char *hostandport,
  */
 static void push_ssh_options(struct argv_array *args, struct argv_array *env,
                             enum ssh_variant variant, const char *port,
-                            enum protocol_version version, int flags)
+                            const struct strbuf *version_advert, int flags)
 {
-       if (variant == VARIANT_SSH &&
-           version > 0) {
+       if (variant == VARIANT_SSH) {
                argv_array_push(args, "-o");
                argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
-               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
-                                version);
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
+                                version_advert->buf);
        }
 
        if (flags & CONNECT_IPV4) {
@@ -1164,7 +1160,7 @@ static void push_ssh_options(struct argv_array *args, 
struct argv_array *env,
 
 /* Prepare a child_process for use by Git's SSH-tunneled transport. */
 static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
-                         const char *port, enum protocol_version version,
+                         const char *port, const struct strbuf *version_advert,
                          int flags)
 {
        const char *ssh;
@@ -1198,15 +1194,16 @@ static void fill_ssh_args(struct child_process *conn, 
const char *ssh_host,
 
                argv_array_push(&detect.args, ssh);
                argv_array_push(&detect.args, "-G");
-               push_ssh_options(&detect.args, &detect.env_array,
-                                VARIANT_SSH, port, version, flags);
+               push_ssh_options(&detect.args, &detect.env_array, VARIANT_SSH,
+                                port, version_advert, flags);
                argv_array_push(&detect.args, ssh_host);
 
                variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
        }
 
        argv_array_push(&conn->args, ssh);
-       push_ssh_options(&conn->args, &conn->env_array, variant, port, version, 
flags);
+       push_ssh_options(&conn->args, &conn->env_array, variant, port,
+                        version_advert, flags);
        argv_array_push(&conn->args, ssh_host);
 }
 
@@ -1226,16 +1223,10 @@ struct child_process *git_connect(int fd[2], const char 
*url,
 {
        char *hostandport, *path;
        struct child_process *conn;
+       struct strbuf version_advert = STRBUF_INIT;
        enum protocol protocol;
-       enum protocol_version version = get_protocol_version_config();
 
-       /*
-        * NEEDSWORK: If we are trying to use protocol v2 and we are planning
-        * to perform a push, then fallback to v0 since the client doesn't know
-        * how to push yet using v2.
-        */
-       if (version == protocol_v2 && !strcmp("git-receive-pack", prog))
-               version = protocol_v0;
+       get_client_protocol_version_advertisement(&version_advert);
 
        /* Without this we cannot rely on waitpid() to tell
         * what happened to our children.
@@ -1250,7 +1241,8 @@ struct child_process *git_connect(int fd[2], const char 
*url,
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
-               conn = git_connect_git(fd, hostandport, path, prog, version, 
flags);
+               conn = git_connect_git(fd, hostandport, path, prog,
+                                      &version_advert, flags);
        } else {
                struct strbuf cmd = STRBUF_INIT;
                const char *const *var;
@@ -1293,13 +1285,13 @@ struct child_process *git_connect(int fd[2], const char 
*url,
                                strbuf_release(&cmd);
                                return NULL;
                        }
-                       fill_ssh_args(conn, ssh_host, port, version, flags);
+                       fill_ssh_args(conn, ssh_host, port, &version_advert,
+                                     flags);
                } else {
                        transport_check_allowed("file");
-                       if (version > 0) {
-                               argv_array_pushf(&conn->env_array, 
GIT_PROTOCOL_ENVIRONMENT "=version=%d",
-                                                version);
-                       }
+                       argv_array_pushf(&conn->env_array,
+                                        GIT_PROTOCOL_ENVIRONMENT "=%s",
+                                        version_advert.buf);
                }
                argv_array_push(&conn->args, cmd.buf);
 
diff --git a/protocol.c b/protocol.c
index 5e636785d1..5664bd7a05 100644
--- a/protocol.c
+++ b/protocol.c
@@ -2,18 +2,43 @@
 #include "config.h"
 #include "protocol.h"
 
+static enum protocol_version *allowed_versions;
+static int nr_allowed_versions;
+static int alloc_allowed_versions;
+static int version_registration_locked = 0;
+
+static const char protocol_v0_string[] = "0";
+static const char protocol_v1_string[] = "1";
+static const char protocol_v2_string[] = "2";
+
 static enum protocol_version parse_protocol_version(const char *value)
 {
-       if (!strcmp(value, "0"))
+       if (!strcmp(value, protocol_v0_string))
                return protocol_v0;
-       else if (!strcmp(value, "1"))
+       else if (!strcmp(value, protocol_v1_string))
                return protocol_v1;
-       else if (!strcmp(value, "2"))
+       else if (!strcmp(value, protocol_v2_string))
                return protocol_v2;
        else
                return protocol_unknown_version;
 }
 
+/* Return the text representation of a wire protocol version. */
+static const char *format_protocol_version(enum protocol_version version)
+{
+       switch (version) {
+       case protocol_v0:
+               return protocol_v0_string;
+       case protocol_v1:
+               return protocol_v1_string;
+       case protocol_v2:
+               return protocol_v2_string;
+       case protocol_unknown_version:
+               die(_("Unrecognized protocol version"));
+       }
+       die(_("Unrecognized protocol_version"));
+}
+
 enum protocol_version get_protocol_version_config(void)
 {
        const char *value;
@@ -30,6 +55,85 @@ enum protocol_version get_protocol_version_config(void)
        return protocol_v0;
 }
 
+void register_allowed_protocol_version(enum protocol_version version)
+{
+       if (version_registration_locked)
+               BUG("late attempt to register an allowed protocol version");
+
+       ALLOC_GROW(allowed_versions, nr_allowed_versions + 1,
+                  alloc_allowed_versions);
+       allowed_versions[nr_allowed_versions++] = version;
+}
+
+void register_allowed_protocol_versions_from_env(void)
+{
+       const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT);
+       const char *version_str;
+       struct string_list version_list = STRING_LIST_INIT_DUP;
+       struct string_list_item *version;
+
+       if (!git_protocol)
+               return;
+
+       string_list_split(&version_list, git_protocol, ':', -1);
+       for_each_string_list_item(version, &version_list) {
+               if (skip_prefix(version->string, "version=", &version_str))
+                       register_allowed_protocol_version(
+                               parse_protocol_version(version_str));
+       }
+       string_list_clear(&version_list, 0);
+}
+
+static int is_allowed_protocol_version(enum protocol_version version)
+{
+       int i;
+       version_registration_locked = 1;
+       for (i = 0; i < nr_allowed_versions; i++)
+               if (version == allowed_versions[i])
+                       return 1;
+       return 0;
+}
+
+void get_client_protocol_version_advertisement(struct strbuf *advert)
+{
+       int i, tmp_nr = nr_allowed_versions;
+       enum protocol_version *tmp_allowed_versions, config_version;
+       strbuf_reset(advert);
+
+       version_registration_locked = 1;
+
+       config_version = get_protocol_version_config();
+       if (config_version == protocol_v0) {
+               strbuf_addstr(advert, "version=0");
+               return;
+       }
+
+       if (tmp_nr > 0) {
+               ALLOC_ARRAY(tmp_allowed_versions, tmp_nr);
+               copy_array(tmp_allowed_versions, allowed_versions, tmp_nr,
+                          sizeof(enum protocol_version));
+       } else {
+               ALLOC_ARRAY(tmp_allowed_versions, 1);
+               tmp_nr = 1;
+               tmp_allowed_versions[0] = config_version;
+       }
+
+       if (tmp_allowed_versions[0] != config_version)
+               for (i = 1; i < nr_allowed_versions; i++)
+                       if (tmp_allowed_versions[i] == config_version) {
+                               SWAP(tmp_allowed_versions[0],
+                                    tmp_allowed_versions[i]);
+                       }
+
+       strbuf_addf(advert, "version=%s",
+                   format_protocol_version(tmp_allowed_versions[0]));
+       for (i = 1; i < tmp_nr; i++)
+               strbuf_addf(advert, ":version=%s",
+                           format_protocol_version(tmp_allowed_versions[i]));
+
+       free(tmp_allowed_versions);
+}
+
 enum protocol_version determine_protocol_version_server(void)
 {
        const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT);
@@ -38,9 +142,10 @@ enum protocol_version 
determine_protocol_version_server(void)
        /*
         * Determine which protocol version the client has requested.  Since
         * multiple 'version' keys can be sent by the client, indicating that
-        * the client is okay to speak any of them, select the greatest version
-        * that the client has requested.  This is due to the assumption that
-        * the most recent protocol version will be the most state-of-the-art.
+        * the client is okay to speak any of them, select the first
+        * recognizable version that the client has requested.  This is due to
+        * the assumption that the protocol versions will be listed in
+        * preference order.
         */
        if (git_protocol) {
                struct string_list list = STRING_LIST_INIT_DUP;
@@ -53,8 +158,11 @@ enum protocol_version 
determine_protocol_version_server(void)
 
                        if (skip_prefix(item->string, "version=", &value)) {
                                v = parse_protocol_version(value);
-                               if (v > version)
+                               if (v != protocol_unknown_version &&
+                                   is_allowed_protocol_version(v)) {
                                        version = v;
+                                       break;
+                               }
                        }
                }
 
diff --git a/protocol.h b/protocol.h
index 2ad35e433c..88330fd0ee 100644
--- a/protocol.h
+++ b/protocol.h
@@ -14,7 +14,24 @@ enum protocol_version {
  * 'protocol.version' config.  If unconfigured, a value of 'protocol_v0' is
  * returned.
  */
-extern enum protocol_version get_protocol_version_config(void);
+enum protocol_version get_protocol_version_config(void);
+
+/*
+ * Register an allowable protocol version for a given operation. Registration
+ * must occur before attempting to advertise a version to a server process.
+ */
+void register_allowed_protocol_version(enum protocol_version version);
+
+/*
+ * Register allowable protocol versions from the GIT_PROTOCOL environment var.
+ */
+void register_allowed_protocol_versions_from_env(void);
+
+/*
+ * Fill a strbuf with a version advertisement string suitable for use in the
+ * GIT_PROTOCOL environment variable or similar version negotiation field.
+ */
+void get_client_protocol_version_advertisement(struct strbuf *advert);
 
 /*
  * Used by a server to determine which protocol version should be used based on
@@ -23,12 +40,12 @@ extern enum protocol_version 
get_protocol_version_config(void);
  * request a particular protocol version, a default of 'protocol_v0' will be
  * used.
  */
-extern enum protocol_version determine_protocol_version_server(void);
+enum protocol_version determine_protocol_version_server(void);
 
 /*
  * Used by a client to determine which protocol version the server is speaking
  * based on the server's initial response.
  */
-extern enum protocol_version determine_protocol_version_client(const char 
*server_response);
+enum protocol_version determine_protocol_version_client(const char 
*server_response);
 
 #endif /* PROTOCOL_H */
diff --git a/remote-curl.c b/remote-curl.c
index fb28309e85..6ffefe5169 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -330,6 +330,19 @@ static int get_protocol_http_header(enum protocol_version 
version,
        return 0;
 }
 
+static int get_client_protocol_http_header(const struct strbuf *version_advert,
+                                          struct strbuf *header)
+{
+       if (version_advert->len > 0) {
+               strbuf_addf(header, GIT_PROTOCOL_HEADER ": %s",
+                           version_advert->buf);
+
+               return 1;
+       }
+
+       return 0;
+}
+
 static struct discovery *discover_refs(const char *service, int for_push)
 {
        struct strbuf exp = STRBUF_INIT;
@@ -339,11 +352,11 @@ static struct discovery *discover_refs(const char 
*service, int for_push)
        struct strbuf refs_url = STRBUF_INIT;
        struct strbuf effective_url = STRBUF_INIT;
        struct strbuf protocol_header = STRBUF_INIT;
+       struct strbuf version_advert = STRBUF_INIT;
        struct string_list extra_headers = STRING_LIST_INIT_DUP;
        struct discovery *last = last_discovery;
        int http_ret, maybe_smart = 0;
        struct http_get_options http_options;
-       enum protocol_version version = get_protocol_version_config();
 
        if (last && !strcmp(service, last->service))
                return last;
@@ -360,16 +373,10 @@ static struct discovery *discover_refs(const char 
*service, int for_push)
                strbuf_addf(&refs_url, "service=%s", service);
        }
 
-       /*
-        * NEEDSWORK: If we are trying to use protocol v2 and we are planning
-        * to perform a push, then fallback to v0 since the client doesn't know
-        * how to push yet using v2.
-        */
-       if (version == protocol_v2 && !strcmp("git-receive-pack", service))
-               version = protocol_v0;
+       get_client_protocol_version_advertisement(&version_advert);
 
        /* Add the extra Git-Protocol header */
-       if (get_protocol_http_header(version, &protocol_header))
+       if (get_client_protocol_http_header(&version_advert, &protocol_header))
                string_list_append(&extra_headers, protocol_header.buf);
 
        memset(&http_options, 0, sizeof(http_options));
@@ -1327,6 +1334,8 @@ int cmd_main(int argc, const char **argv)
        struct strbuf buf = STRBUF_INIT;
        int nongit;
 
+       register_allowed_protocol_versions_from_env();
+
        setup_git_directory_gently(&nongit);
        if (argc < 2) {
                error("remote-curl: usage: git remote-curl <remote> [<url>]");
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 771f36f9ff..343bf3aafa 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -28,6 +28,7 @@ cat >exp <<EOF
 > Accept: */*
 > Accept-Encoding: ENCODINGS
 > Pragma: no-cache
+> Git-Protocol: version=0
 < HTTP/1.1 200 OK
 < Pragma: no-cache
 < Cache-Control: no-cache, max-age=0, must-revalidate
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
index 7466aad111..d528e91630 100755
--- a/t/t5570-git-daemon.sh
+++ b/t/t5570-git-daemon.sh
@@ -186,7 +186,7 @@ test_expect_success 'hostname cannot break out of 
directory' '
 test_expect_success 'daemon log records all attributes' '
        cat >expect <<-\EOF &&
        Extended attribute "host": localhost
-       Extended attribute "protocol": version=1
+       Extended attribute "protocol": version=1:version=2:version=0
        EOF
        >daemon.log &&
        GIT_OVERRIDE_VIRTUAL_HOST=localhost \
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index ddaa96ac4f..c4dbf1f779 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -346,7 +346,7 @@ expect_ssh () {
 
 test_expect_success 'clone myhost:src uses ssh' '
        git clone myhost:src ssh-clone &&
-       expect_ssh myhost src
+       expect_ssh "-o SendEnv=GIT_PROTOCOL" myhost src
 '
 
 test_expect_success !MINGW,!CYGWIN 'clone local path foo:bar' '
@@ -357,12 +357,12 @@ test_expect_success !MINGW,!CYGWIN 'clone local path 
foo:bar' '
 
 test_expect_success 'bracketed hostnames are still ssh' '
        git clone "[myhost:123]:src" ssh-bracket-clone &&
-       expect_ssh "-p 123" myhost src
+       expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src
 '
 
 test_expect_success 'OpenSSH variant passes -4' '
        git clone -4 "[myhost:123]:src" ssh-ipv4-clone &&
-       expect_ssh "-4 -p 123" myhost src
+       expect_ssh "-o SendEnv=GIT_PROTOCOL -4 -p 123" myhost src
 '
 
 test_expect_success 'variant can be overridden' '
@@ -406,7 +406,7 @@ test_expect_success 'OpenSSH-like uplink is treated as ssh' 
'
        GIT_SSH="$TRASH_DIRECTORY/uplink" &&
        test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" &&
        git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink &&
-       expect_ssh "-p 123" myhost src
+       expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src
 '
 
 test_expect_success 'plink is treated specially (as putty)' '
@@ -446,14 +446,14 @@ test_expect_success 'GIT_SSH_VARIANT overrides plink 
detection' '
        copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        GIT_SSH_VARIANT=ssh \
        git clone "[myhost:123]:src" ssh-bracket-clone-variant-1 &&
-       expect_ssh "-p 123" myhost src
+       expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src
 '
 
 test_expect_success 'ssh.variant overrides plink detection' '
        copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
        git -c ssh.variant=ssh \
                clone "[myhost:123]:src" ssh-bracket-clone-variant-2 &&
-       expect_ssh "-p 123" myhost src
+       expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src
 '
 
 test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' '
@@ -488,7 +488,7 @@ test_clone_url () {
 }
 
 test_expect_success !MINGW 'clone c:temp is ssl' '
-       test_clone_url c:temp c temp
+       test_clone_url c:temp "-o SendEnv=GIT_PROTOCOL" c temp
 '
 
 test_expect_success MINGW 'clone c:temp is dos drive' '
@@ -499,7 +499,7 @@ test_expect_success MINGW 'clone c:temp is dos drive' '
 for repo in rep rep/home/project 123
 do
        test_expect_success "clone host:$repo" '
-               test_clone_url host:$repo host $repo
+               test_clone_url host:$repo "-o SendEnv=GIT_PROTOCOL" host $repo
        '
 done
 
@@ -507,16 +507,16 @@ done
 for repo in rep rep/home/project 123
 do
        test_expect_success "clone [::1]:$repo" '
-               test_clone_url [::1]:$repo ::1 "$repo"
+               test_clone_url [::1]:$repo "-o SendEnv=GIT_PROTOCOL" ::1 "$repo"
        '
 done
 #home directory
 test_expect_success "clone host:/~repo" '
-       test_clone_url host:/~repo host "~repo"
+       test_clone_url host:/~repo "-o SendEnv=GIT_PROTOCOL" host "~repo"
 '
 
 test_expect_success "clone [::1]:/~repo" '
-       test_clone_url [::1]:/~repo ::1 "~repo"
+       test_clone_url [::1]:/~repo "-o SendEnv=GIT_PROTOCOL" ::1 "~repo"
 '
 
 # Corner cases
@@ -532,22 +532,22 @@ done
 for tcol in "" :
 do
        test_expect_success "clone ssh://host.xz$tcol/home/user/repo" '
-               test_clone_url "ssh://host.xz$tcol/home/user/repo" host.xz 
/home/user/repo
+               test_clone_url "ssh://host.xz$tcol/home/user/repo" "-o 
SendEnv=GIT_PROTOCOL" host.xz /home/user/repo
        '
        # from home directory
        test_expect_success "clone ssh://host.xz$tcol/~repo" '
-       test_clone_url "ssh://host.xz$tcol/~repo" host.xz "~repo"
+       test_clone_url "ssh://host.xz$tcol/~repo" "-o SendEnv=GIT_PROTOCOL" 
host.xz "~repo"
 '
 done
 
 # with port number
 test_expect_success 'clone ssh://host.xz:22/home/user/repo' '
-       test_clone_url "ssh://host.xz:22/home/user/repo" "-p 22 host.xz" 
"/home/user/repo"
+       test_clone_url "ssh://host.xz:22/home/user/repo" "-o 
SendEnv=GIT_PROTOCOL -p 22 host.xz" "/home/user/repo"
 '
 
 # from home directory with port number
 test_expect_success 'clone ssh://host.xz:22/~repo' '
-       test_clone_url "ssh://host.xz:22/~repo" "-p 22 host.xz" "~repo"
+       test_clone_url "ssh://host.xz:22/~repo" "-o SendEnv=GIT_PROTOCOL -p 22 
host.xz" "~repo"
 '
 
 #IPv6
@@ -555,7 +555,7 @@ for tuah in ::1 [::1] [::1]: user@::1 user@[::1] 
user@[::1]: [user@::1] [user@::
 do
        ehost=$(echo $tuah | sed -e "s/1]:/1]/" | tr -d "[]")
        test_expect_success "clone ssh://$tuah/home/user/repo" "
-         test_clone_url ssh://$tuah/home/user/repo $ehost /home/user/repo
+         test_clone_url ssh://$tuah/home/user/repo '-o SendEnv=GIT_PROTOCOL' 
$ehost /home/user/repo
        "
 done
 
@@ -564,7 +564,7 @@ for tuah in ::1 [::1] user@::1 user@[::1] [user@::1]
 do
        euah=$(echo $tuah | tr -d "[]")
        test_expect_success "clone ssh://$tuah/~repo" "
-         test_clone_url ssh://$tuah/~repo $euah '~repo'
+         test_clone_url ssh://$tuah/~repo '-o SendEnv=GIT_PROTOCOL' $euah 
'~repo'
        "
 done
 
@@ -573,7 +573,7 @@ for tuah in [::1] user@[::1] [user@::1]
 do
        euah=$(echo $tuah | tr -d "[]")
        test_expect_success "clone ssh://$tuah:22/home/user/repo" "
-         test_clone_url ssh://$tuah:22/home/user/repo '-p 22' $euah 
/home/user/repo
+         test_clone_url ssh://$tuah:22/home/user/repo '-o SendEnv=GIT_PROTOCOL 
-p 22' $euah /home/user/repo
        "
 done
 
@@ -582,7 +582,7 @@ for tuah in [::1] user@[::1] [user@::1]
 do
        euah=$(echo $tuah | tr -d "[]")
        test_expect_success "clone ssh://$tuah:22/~repo" "
-         test_clone_url ssh://$tuah:22/~repo '-p 22' $euah '~repo'
+         test_clone_url ssh://$tuah:22/~repo '-o SendEnv=GIT_PROTOCOL -p 22' 
$euah '~repo'
        "
 done
 
diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh
index ba86a44eb1..2e56c79233 100755
--- a/t/t5700-protocol-v1.sh
+++ b/t/t5700-protocol-v1.sh
@@ -26,7 +26,7 @@ test_expect_success 'clone with git:// using protocol v1' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v1
-       grep "clone> .*\\\0\\\0version=1\\\0$" log &&
+       grep "clone> .*\\\0\\\0version=1.*\\\0$" log &&
        # Server responded using protocol v1
        grep "clone< version 1" log
 '
@@ -42,7 +42,7 @@ test_expect_success 'fetch with git:// using protocol v1' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v1
-       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       grep "fetch> .*\\\0\\\0version=1.*\\\0$" log &&
        # Server responded using protocol v1
        grep "fetch< version 1" log
 '
@@ -56,7 +56,7 @@ test_expect_success 'pull with git:// using protocol v1' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v1
-       grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
+       grep "fetch> .*\\\0\\\0version=1.*\\\0$" log &&
        # Server responded using protocol v1
        grep "fetch< version 1" log
 '
@@ -74,7 +74,7 @@ test_expect_success 'push with git:// using protocol v1' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v1
-       grep "push> .*\\\0\\\0version=1\\\0$" log &&
+       grep "push> .*\\\0\\\0version=1.*\\\0$" log &&
        # Server responded using protocol v1
        grep "push< version 1" log
 '
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index 3beeed4546..78c17c25e4 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -24,7 +24,7 @@ test_expect_success 'list refs with git:// using protocol v2' 
'
                ls-remote --symref "$GIT_DAEMON_URL/parent" >actual &&
 
        # Client requested to use protocol v2
-       grep "git> .*\\\0\\\0version=2\\\0$" log &&
+       grep "git> .*\\\0\\\0version=2.*\\\0$" log &&
        # Server responded using protocol v2
        grep "git< version 2" log &&
 
@@ -56,7 +56,7 @@ test_expect_success 'clone with git:// using protocol v2' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v2
-       grep "clone> .*\\\0\\\0version=2\\\0$" log &&
+       grep "clone> .*\\\0\\\0version=2.*\\\0$" log &&
        # Server responded using protocol v2
        grep "clone< version 2" log
 '
@@ -74,7 +74,7 @@ test_expect_success 'fetch with git:// using protocol v2' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v2
-       grep "fetch> .*\\\0\\\0version=2\\\0$" log &&
+       grep "fetch> .*\\\0\\\0version=2.*\\\0$" log &&
        # Server responded using protocol v2
        grep "fetch< version 2" log
 '
@@ -90,7 +90,7 @@ test_expect_success 'pull with git:// using protocol v2' '
        test_cmp expect actual &&
 
        # Client requested to use protocol v2
-       grep "fetch> .*\\\0\\\0version=2\\\0$" log &&
+       grep "fetch> .*\\\0\\\0version=2.*\\\0$" log &&
        # Server responded using protocol v2
        grep "fetch< version 2" log
 '
@@ -476,7 +476,7 @@ test_expect_success 'push with http:// and a config of v2 
does not request v2' '
        test_when_finished "rm -f log" &&
        # Till v2 for push is designed, make sure that if a client has
        # protocol.version configured to use v2, that the client instead falls
-       # back and uses v0.
+       # back to previous versions.
 
        test_commit -C http_child three &&
 
@@ -489,10 +489,8 @@ test_expect_success 'push with http:// and a config of v2 
does not request v2' '
        git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s 
client_branch >expect &&
        test_cmp expect actual &&
 
-       # Client didnt request to use protocol v2
-       ! grep "Git-Protocol: version=2" log &&
-       # Server didnt respond using protocol v2
-       ! grep "git< version 2" log
+       # Server responded with version 1
+       grep "git< version 1" log
 '
 
 
diff --git a/transport-helper.c b/transport-helper.c
index 143ca008c8..ac1937f1e1 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -105,6 +105,7 @@ static struct child_process *get_helper(struct transport 
*transport)
 {
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
+       struct strbuf version_advert = STRBUF_INIT;
        struct child_process *helper;
        int duped;
        int code;
@@ -127,6 +128,11 @@ static struct child_process *get_helper(struct transport 
*transport)
                argv_array_pushf(&helper->env_array, "%s=%s",
                                 GIT_DIR_ENVIRONMENT, get_git_dir());
 
+       get_client_protocol_version_advertisement(&version_advert);
+       if (version_advert.len > 0)
+               argv_array_pushf(&helper->env_array, "%s=%s",
+                                GIT_PROTOCOL_ENVIRONMENT, version_advert.buf);
+
        code = start_command(helper);
        if (code < 0 && errno == ENOENT)
                die(_("unable to find remote helper for '%s'"), data->name);
-- 
2.20.1.415.g653613c723-goog

Reply via email to