Git fork

connect: tell server that the client understands v1

Teach the connection logic to tell a serve that it understands protocol
v1. This is done in 2 different ways for the builtin transports, both
of which ultimately set 'GIT_PROTOCOL' to 'version=1' on the server.

1. git://
A normal request to git-daemon is structured as
"command path/to/repo\0host=..\0" and due to a bug introduced in
49ba83fb6 (Add virtualization support to git-daemon, 2006-09-19) we
aren't able to place any extra arguments (separated by NULs) besides
the host otherwise the parsing of those arguments would enter an
infinite loop. This bug was fixed in 73bb33a94 (daemon: Strictly
parse the "extra arg" part of the command, 2009-06-04) but a check
was put in place to disallow extra arguments so that new clients
wouldn't trigger this bug in older servers.

In order to get around this limitation git-daemon was taught to
recognize additional request arguments hidden behind a second
NUL byte. Requests can then be structured like:
"command path/to/repo\0host=..\0\0version=1\0key=value\0".
git-daemon can then parse out the extra arguments and set
'GIT_PROTOCOL' accordingly.

By placing these extra arguments behind a second NUL byte we can
skirt around both the infinite loop bug in 49ba83fb6 (Add
virtualization support to git-daemon, 2006-09-19) as well as the
explicit disallowing of extra arguments introduced in 73bb33a94
(daemon: Strictly parse the "extra arg" part of the command,
2009-06-04) because both of these versions of git-daemon check for a
single NUL byte after the host argument before terminating the
argument parsing.

2. ssh://, file://
Set 'GIT_PROTOCOL' environment variable with the desired protocol
version. With the file:// transport, 'GIT_PROTOCOL' can be set
explicitly in the locally running git-upload-pack or git-receive-pack
processes. With the ssh:// transport and OpenSSH compliant ssh
programs, 'GIT_PROTOCOL' can be sent across ssh by using '-o
SendEnv=GIT_PROTOCOL' and having the server whitelist this
environment variable.

Signed-off-by: Brandon Williams <bmwill@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Brandon Williams and committed by
Junio C Hamano
0c2f0d27 2609043d

+255 -5
+32 -5
connect.c
··· 871 871 printf("Diag: path=%s\n", path ? path : "NULL"); 872 872 conn = NULL; 873 873 } else if (protocol == PROTO_GIT) { 874 + struct strbuf request = STRBUF_INIT; 874 875 /* 875 876 * Set up virtual host information based on where we will 876 877 * connect, unless the user has overridden us in ··· 898 899 * Note: Do not add any other headers here! Doing so 899 900 * will cause older git-daemon servers to crash. 900 901 */ 901 - packet_write_fmt(fd[1], 902 - "%s %s%chost=%s%c", 903 - prog, path, 0, 904 - target_host, 0); 902 + strbuf_addf(&request, 903 + "%s %s%chost=%s%c", 904 + prog, path, 0, 905 + target_host, 0); 906 + 907 + /* If using a new version put that stuff here after a second null byte */ 908 + if (get_protocol_version_config() > 0) { 909 + strbuf_addch(&request, '\0'); 910 + strbuf_addf(&request, "version=%d%c", 911 + get_protocol_version_config(), '\0'); 912 + } 913 + 914 + packet_write(fd[1], request.buf, request.len); 915 + 905 916 free(target_host); 917 + strbuf_release(&request); 906 918 } else { 907 919 struct strbuf cmd = STRBUF_INIT; 920 + const char *const *var; 908 921 909 922 conn = xmalloc(sizeof(*conn)); 910 923 child_process_init(conn); ··· 917 930 sq_quote_buf(&cmd, path); 918 931 919 932 /* remove repo-local variables from the environment */ 920 - conn->env = local_repo_env; 933 + for (var = local_repo_env; *var; var++) 934 + argv_array_push(&conn->env_array, *var); 935 + 921 936 conn->use_shell = 1; 922 937 conn->in = conn->out = -1; 923 938 if (protocol == PROTO_SSH) { ··· 971 986 } 972 987 973 988 argv_array_push(&conn->args, ssh); 989 + 990 + if (get_protocol_version_config() > 0) { 991 + argv_array_push(&conn->args, "-o"); 992 + argv_array_push(&conn->args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT); 993 + argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", 994 + get_protocol_version_config()); 995 + } 996 + 974 997 if (flags & CONNECT_IPV4) 975 998 argv_array_push(&conn->args, "-4"); 976 999 else if (flags & CONNECT_IPV6) ··· 985 1008 argv_array_push(&conn->args, ssh_host); 986 1009 } else { 987 1010 transport_check_allowed("file"); 1011 + if (get_protocol_version_config() > 0) { 1012 + argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", 1013 + get_protocol_version_config()); 1014 + } 988 1015 } 989 1016 argv_array_push(&conn->args, cmd.buf); 990 1017
+223
t/t5700-protocol-v1.sh
··· 1 + #!/bin/sh 2 + 3 + test_description='test git wire-protocol transition' 4 + 5 + TEST_NO_CREATE_REPO=1 6 + 7 + . ./test-lib.sh 8 + 9 + # Test protocol v1 with 'git://' transport 10 + # 11 + . "$TEST_DIRECTORY"/lib-git-daemon.sh 12 + start_git_daemon --export-all --enable=receive-pack 13 + daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent 14 + 15 + test_expect_success 'create repo to be served by git-daemon' ' 16 + git init "$daemon_parent" && 17 + test_commit -C "$daemon_parent" one 18 + ' 19 + 20 + test_expect_success 'clone with git:// using protocol v1' ' 21 + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ 22 + clone "$GIT_DAEMON_URL/parent" daemon_child 2>log && 23 + 24 + git -C daemon_child log -1 --format=%s >actual && 25 + git -C "$daemon_parent" log -1 --format=%s >expect && 26 + test_cmp expect actual && 27 + 28 + # Client requested to use protocol v1 29 + grep "clone> .*\\\0\\\0version=1\\\0$" log && 30 + # Server responded using protocol v1 31 + grep "clone< version 1" log 32 + ' 33 + 34 + test_expect_success 'fetch with git:// using protocol v1' ' 35 + test_commit -C "$daemon_parent" two && 36 + 37 + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ 38 + fetch 2>log && 39 + 40 + git -C daemon_child log -1 --format=%s origin/master >actual && 41 + git -C "$daemon_parent" log -1 --format=%s >expect && 42 + test_cmp expect actual && 43 + 44 + # Client requested to use protocol v1 45 + grep "fetch> .*\\\0\\\0version=1\\\0$" log && 46 + # Server responded using protocol v1 47 + grep "fetch< version 1" log 48 + ' 49 + 50 + test_expect_success 'pull with git:// using protocol v1' ' 51 + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ 52 + pull 2>log && 53 + 54 + git -C daemon_child log -1 --format=%s >actual && 55 + git -C "$daemon_parent" log -1 --format=%s >expect && 56 + test_cmp expect actual && 57 + 58 + # Client requested to use protocol v1 59 + grep "fetch> .*\\\0\\\0version=1\\\0$" log && 60 + # Server responded using protocol v1 61 + grep "fetch< version 1" log 62 + ' 63 + 64 + test_expect_success 'push with git:// using protocol v1' ' 65 + test_commit -C daemon_child three && 66 + 67 + # Push to another branch, as the target repository has the 68 + # master branch checked out and we cannot push into it. 69 + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ 70 + push origin HEAD:client_branch 2>log && 71 + 72 + git -C daemon_child log -1 --format=%s >actual && 73 + git -C "$daemon_parent" log -1 --format=%s client_branch >expect && 74 + test_cmp expect actual && 75 + 76 + # Client requested to use protocol v1 77 + grep "push> .*\\\0\\\0version=1\\\0$" log && 78 + # Server responded using protocol v1 79 + grep "push< version 1" log 80 + ' 81 + 82 + stop_git_daemon 83 + 84 + # Test protocol v1 with 'file://' transport 85 + # 86 + test_expect_success 'create repo to be served by file:// transport' ' 87 + git init file_parent && 88 + test_commit -C file_parent one 89 + ' 90 + 91 + test_expect_success 'clone with file:// using protocol v1' ' 92 + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ 93 + clone "file://$(pwd)/file_parent" file_child 2>log && 94 + 95 + git -C file_child log -1 --format=%s >actual && 96 + git -C file_parent log -1 --format=%s >expect && 97 + test_cmp expect actual && 98 + 99 + # Server responded using protocol v1 100 + grep "clone< version 1" log 101 + ' 102 + 103 + test_expect_success 'fetch with file:// using protocol v1' ' 104 + test_commit -C file_parent two && 105 + 106 + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ 107 + fetch 2>log && 108 + 109 + git -C file_child log -1 --format=%s origin/master >actual && 110 + git -C file_parent log -1 --format=%s >expect && 111 + test_cmp expect actual && 112 + 113 + # Server responded using protocol v1 114 + grep "fetch< version 1" log 115 + ' 116 + 117 + test_expect_success 'pull with file:// using protocol v1' ' 118 + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ 119 + pull 2>log && 120 + 121 + git -C file_child log -1 --format=%s >actual && 122 + git -C file_parent log -1 --format=%s >expect && 123 + test_cmp expect actual && 124 + 125 + # Server responded using protocol v1 126 + grep "fetch< version 1" log 127 + ' 128 + 129 + test_expect_success 'push with file:// using protocol v1' ' 130 + test_commit -C file_child three && 131 + 132 + # Push to another branch, as the target repository has the 133 + # master branch checked out and we cannot push into it. 134 + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ 135 + push origin HEAD:client_branch 2>log && 136 + 137 + git -C file_child log -1 --format=%s >actual && 138 + git -C file_parent log -1 --format=%s client_branch >expect && 139 + test_cmp expect actual && 140 + 141 + # Server responded using protocol v1 142 + grep "push< version 1" log 143 + ' 144 + 145 + # Test protocol v1 with 'ssh://' transport 146 + # 147 + test_expect_success 'setup ssh wrapper' ' 148 + GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" && 149 + export GIT_SSH && 150 + export TRASH_DIRECTORY && 151 + >"$TRASH_DIRECTORY"/ssh-output 152 + ' 153 + 154 + expect_ssh () { 155 + test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' && 156 + echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" && 157 + (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) 158 + } 159 + 160 + test_expect_success 'create repo to be served by ssh:// transport' ' 161 + git init ssh_parent && 162 + test_commit -C ssh_parent one 163 + ' 164 + 165 + test_expect_success 'clone with ssh:// using protocol v1' ' 166 + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ 167 + clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log && 168 + expect_ssh git-upload-pack && 169 + 170 + git -C ssh_child log -1 --format=%s >actual && 171 + git -C ssh_parent log -1 --format=%s >expect && 172 + test_cmp expect actual && 173 + 174 + # Server responded using protocol v1 175 + grep "clone< version 1" log 176 + ' 177 + 178 + test_expect_success 'fetch with ssh:// using protocol v1' ' 179 + test_commit -C ssh_parent two && 180 + 181 + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ 182 + fetch 2>log && 183 + expect_ssh git-upload-pack && 184 + 185 + git -C ssh_child log -1 --format=%s origin/master >actual && 186 + git -C ssh_parent log -1 --format=%s >expect && 187 + test_cmp expect actual && 188 + 189 + # Server responded using protocol v1 190 + grep "fetch< version 1" log 191 + ' 192 + 193 + test_expect_success 'pull with ssh:// using protocol v1' ' 194 + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ 195 + pull 2>log && 196 + expect_ssh git-upload-pack && 197 + 198 + git -C ssh_child log -1 --format=%s >actual && 199 + git -C ssh_parent log -1 --format=%s >expect && 200 + test_cmp expect actual && 201 + 202 + # Server responded using protocol v1 203 + grep "fetch< version 1" log 204 + ' 205 + 206 + test_expect_success 'push with ssh:// using protocol v1' ' 207 + test_commit -C ssh_child three && 208 + 209 + # Push to another branch, as the target repository has the 210 + # master branch checked out and we cannot push into it. 211 + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ 212 + push origin HEAD:client_branch 2>log && 213 + expect_ssh git-receive-pack && 214 + 215 + git -C ssh_child log -1 --format=%s >actual && 216 + git -C ssh_parent log -1 --format=%s client_branch >expect && 217 + test_cmp expect actual && 218 + 219 + # Server responded using protocol v1 220 + grep "push< version 1" log 221 + ' 222 + 223 + test_done