Browse Source
A new mechanism to upgrade the wire protocol in place is proposed and demonstrated that it works with the older versions of Git without harming them. * bw/protocol-v1: Documentation: document Extra Parameters ssh: introduce a 'simple' ssh variant i5700: add interop test for protocol transition http: tell server that the client understands v1 connect: tell server that the client understands v1 connect: teach client to recognize v1 server response upload-pack, receive-pack: introduce protocol version 1 daemon: recognize hidden request arguments protocol: introduce protocol extension mechanisms pkt-line: add packet_write function connect: in ref advertisement, shallows are lastmaint

19 changed files with 968 additions and 149 deletions
@ -0,0 +1,79 @@ |
|||||||
|
#include "cache.h" |
||||||
|
#include "config.h" |
||||||
|
#include "protocol.h" |
||||||
|
|
||||||
|
static enum protocol_version parse_protocol_version(const char *value) |
||||||
|
{ |
||||||
|
if (!strcmp(value, "0")) |
||||||
|
return protocol_v0; |
||||||
|
else if (!strcmp(value, "1")) |
||||||
|
return protocol_v1; |
||||||
|
else |
||||||
|
return protocol_unknown_version; |
||||||
|
} |
||||||
|
|
||||||
|
enum protocol_version get_protocol_version_config(void) |
||||||
|
{ |
||||||
|
const char *value; |
||||||
|
if (!git_config_get_string_const("protocol.version", &value)) { |
||||||
|
enum protocol_version version = parse_protocol_version(value); |
||||||
|
|
||||||
|
if (version == protocol_unknown_version) |
||||||
|
die("unknown value for config 'protocol.version': %s", |
||||||
|
value); |
||||||
|
|
||||||
|
return version; |
||||||
|
} |
||||||
|
|
||||||
|
return protocol_v0; |
||||||
|
} |
||||||
|
|
||||||
|
enum protocol_version determine_protocol_version_server(void) |
||||||
|
{ |
||||||
|
const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT); |
||||||
|
enum protocol_version version = protocol_v0; |
||||||
|
|
||||||
|
/* |
||||||
|
* 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. |
||||||
|
*/ |
||||||
|
if (git_protocol) { |
||||||
|
struct string_list list = STRING_LIST_INIT_DUP; |
||||||
|
const struct string_list_item *item; |
||||||
|
string_list_split(&list, git_protocol, ':', -1); |
||||||
|
|
||||||
|
for_each_string_list_item(item, &list) { |
||||||
|
const char *value; |
||||||
|
enum protocol_version v; |
||||||
|
|
||||||
|
if (skip_prefix(item->string, "version=", &value)) { |
||||||
|
v = parse_protocol_version(value); |
||||||
|
if (v > version) |
||||||
|
version = v; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
string_list_clear(&list, 0); |
||||||
|
} |
||||||
|
|
||||||
|
return version; |
||||||
|
} |
||||||
|
|
||||||
|
enum protocol_version determine_protocol_version_client(const char *server_response) |
||||||
|
{ |
||||||
|
enum protocol_version version = protocol_v0; |
||||||
|
|
||||||
|
if (skip_prefix(server_response, "version ", &server_response)) { |
||||||
|
version = parse_protocol_version(server_response); |
||||||
|
|
||||||
|
if (version == protocol_unknown_version) |
||||||
|
die("server is speaking an unknown protocol"); |
||||||
|
if (version == protocol_v0) |
||||||
|
die("protocol error: server explicitly said version 0"); |
||||||
|
} |
||||||
|
|
||||||
|
return version; |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
#ifndef PROTOCOL_H |
||||||
|
#define PROTOCOL_H |
||||||
|
|
||||||
|
enum protocol_version { |
||||||
|
protocol_unknown_version = -1, |
||||||
|
protocol_v0 = 0, |
||||||
|
protocol_v1 = 1, |
||||||
|
}; |
||||||
|
|
||||||
|
/* |
||||||
|
* Used by a client to determine which protocol version to request be used when |
||||||
|
* communicating with a server, reflecting the configured value of the |
||||||
|
* 'protocol.version' config. If unconfigured, a value of 'protocol_v0' is |
||||||
|
* returned. |
||||||
|
*/ |
||||||
|
extern enum protocol_version get_protocol_version_config(void); |
||||||
|
|
||||||
|
/* |
||||||
|
* Used by a server to determine which protocol version should be used based on |
||||||
|
* a client's request, communicated via the 'GIT_PROTOCOL' environment variable |
||||||
|
* by setting appropriate values for the key 'version'. If a client doesn't |
||||||
|
* request a particular protocol version, a default of 'protocol_v0' will be |
||||||
|
* used. |
||||||
|
*/ |
||||||
|
extern 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); |
||||||
|
|
||||||
|
#endif /* PROTOCOL_H */ |
@ -0,0 +1,68 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
VERSION_A=. |
||||||
|
VERSION_B=v2.0.0 |
||||||
|
|
||||||
|
: ${LIB_GIT_DAEMON_PORT:=5700} |
||||||
|
LIB_GIT_DAEMON_COMMAND='git.b daemon' |
||||||
|
|
||||||
|
test_description='clone and fetch by client who is trying to use a new protocol' |
||||||
|
. ./interop-lib.sh |
||||||
|
. "$TEST_DIRECTORY"/lib-git-daemon.sh |
||||||
|
|
||||||
|
start_git_daemon --export-all |
||||||
|
|
||||||
|
repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo |
||||||
|
|
||||||
|
test_expect_success "create repo served by $VERSION_B" ' |
||||||
|
git.b init "$repo" && |
||||||
|
git.b -C "$repo" commit --allow-empty -m one |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success "git:// clone with $VERSION_A and protocol v1" ' |
||||||
|
GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone "$GIT_DAEMON_URL/repo" child 2>log && |
||||||
|
git.a -C child log -1 --format=%s >actual && |
||||||
|
git.b -C "$repo" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
grep "version=1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success "git:// fetch with $VERSION_A and protocol v1" ' |
||||||
|
git.b -C "$repo" commit --allow-empty -m two && |
||||||
|
git.b -C "$repo" log -1 --format=%s >expect && |
||||||
|
|
||||||
|
GIT_TRACE_PACKET=1 git.a -C child -c protocol.version=1 fetch 2>log && |
||||||
|
git.a -C child log -1 --format=%s FETCH_HEAD >actual && |
||||||
|
|
||||||
|
test_cmp expect actual && |
||||||
|
grep "version=1" log && |
||||||
|
! grep "version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
stop_git_daemon |
||||||
|
|
||||||
|
test_expect_success "create repo served by $VERSION_B" ' |
||||||
|
git.b init parent && |
||||||
|
git.b -C parent commit --allow-empty -m one |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success "file:// clone with $VERSION_A and protocol v1" ' |
||||||
|
GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone --upload-pack="git.b upload-pack" parent child2 2>log && |
||||||
|
git.a -C child2 log -1 --format=%s >actual && |
||||||
|
git.b -C parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
! grep "version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success "file:// fetch with $VERSION_A and protocol v1" ' |
||||||
|
git.b -C parent commit --allow-empty -m two && |
||||||
|
git.b -C parent log -1 --format=%s >expect && |
||||||
|
|
||||||
|
GIT_TRACE_PACKET=1 git.a -C child2 -c protocol.version=1 fetch --upload-pack="git.b upload-pack" 2>log && |
||||||
|
git.a -C child2 log -1 --format=%s FETCH_HEAD >actual && |
||||||
|
|
||||||
|
test_cmp expect actual && |
||||||
|
! grep "version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_done |
@ -0,0 +1,294 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
test_description='test git wire-protocol transition' |
||||||
|
|
||||||
|
TEST_NO_CREATE_REPO=1 |
||||||
|
|
||||||
|
. ./test-lib.sh |
||||||
|
|
||||||
|
# Test protocol v1 with 'git://' transport |
||||||
|
# |
||||||
|
. "$TEST_DIRECTORY"/lib-git-daemon.sh |
||||||
|
start_git_daemon --export-all --enable=receive-pack |
||||||
|
daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent |
||||||
|
|
||||||
|
test_expect_success 'create repo to be served by git-daemon' ' |
||||||
|
git init "$daemon_parent" && |
||||||
|
test_commit -C "$daemon_parent" one |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'clone with git:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -c protocol.version=1 \ |
||||||
|
clone "$GIT_DAEMON_URL/parent" daemon_child 2>log && |
||||||
|
|
||||||
|
git -C daemon_child log -1 --format=%s >actual && |
||||||
|
git -C "$daemon_parent" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Client requested to use protocol v1 |
||||||
|
grep "clone> .*\\\0\\\0version=1\\\0$" log && |
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "clone< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'fetch with git:// using protocol v1' ' |
||||||
|
test_commit -C "$daemon_parent" two && |
||||||
|
|
||||||
|
GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ |
||||||
|
fetch 2>log && |
||||||
|
|
||||||
|
git -C daemon_child log -1 --format=%s origin/master >actual && |
||||||
|
git -C "$daemon_parent" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Client requested to use protocol v1 |
||||||
|
grep "fetch> .*\\\0\\\0version=1\\\0$" log && |
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "fetch< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'pull with git:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ |
||||||
|
pull 2>log && |
||||||
|
|
||||||
|
git -C daemon_child log -1 --format=%s >actual && |
||||||
|
git -C "$daemon_parent" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Client requested to use protocol v1 |
||||||
|
grep "fetch> .*\\\0\\\0version=1\\\0$" log && |
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "fetch< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'push with git:// using protocol v1' ' |
||||||
|
test_commit -C daemon_child three && |
||||||
|
|
||||||
|
# Push to another branch, as the target repository has the |
||||||
|
# master branch checked out and we cannot push into it. |
||||||
|
GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ |
||||||
|
push origin HEAD:client_branch 2>log && |
||||||
|
|
||||||
|
git -C daemon_child log -1 --format=%s >actual && |
||||||
|
git -C "$daemon_parent" log -1 --format=%s client_branch >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Client requested to use protocol v1 |
||||||
|
grep "push> .*\\\0\\\0version=1\\\0$" log && |
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "push< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
stop_git_daemon |
||||||
|
|
||||||
|
# Test protocol v1 with 'file://' transport |
||||||
|
# |
||||||
|
test_expect_success 'create repo to be served by file:// transport' ' |
||||||
|
git init file_parent && |
||||||
|
test_commit -C file_parent one |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'clone with file:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -c protocol.version=1 \ |
||||||
|
clone "file://$(pwd)/file_parent" file_child 2>log && |
||||||
|
|
||||||
|
git -C file_child log -1 --format=%s >actual && |
||||||
|
git -C file_parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "clone< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'fetch with file:// using protocol v1' ' |
||||||
|
test_commit -C file_parent two && |
||||||
|
|
||||||
|
GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ |
||||||
|
fetch 2>log && |
||||||
|
|
||||||
|
git -C file_child log -1 --format=%s origin/master >actual && |
||||||
|
git -C file_parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "fetch< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'pull with file:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ |
||||||
|
pull 2>log && |
||||||
|
|
||||||
|
git -C file_child log -1 --format=%s >actual && |
||||||
|
git -C file_parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "fetch< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'push with file:// using protocol v1' ' |
||||||
|
test_commit -C file_child three && |
||||||
|
|
||||||
|
# Push to another branch, as the target repository has the |
||||||
|
# master branch checked out and we cannot push into it. |
||||||
|
GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ |
||||||
|
push origin HEAD:client_branch 2>log && |
||||||
|
|
||||||
|
git -C file_child log -1 --format=%s >actual && |
||||||
|
git -C file_parent log -1 --format=%s client_branch >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "push< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
# Test protocol v1 with 'ssh://' transport |
||||||
|
# |
||||||
|
test_expect_success 'setup ssh wrapper' ' |
||||||
|
GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" && |
||||||
|
export GIT_SSH && |
||||||
|
GIT_SSH_VARIANT=ssh && |
||||||
|
export GIT_SSH_VARIANT && |
||||||
|
export TRASH_DIRECTORY && |
||||||
|
>"$TRASH_DIRECTORY"/ssh-output |
||||||
|
' |
||||||
|
|
||||||
|
expect_ssh () { |
||||||
|
test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' && |
||||||
|
echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" && |
||||||
|
(cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) |
||||||
|
} |
||||||
|
|
||||||
|
test_expect_success 'create repo to be served by ssh:// transport' ' |
||||||
|
git init ssh_parent && |
||||||
|
test_commit -C ssh_parent one |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'clone with ssh:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -c protocol.version=1 \ |
||||||
|
clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log && |
||||||
|
expect_ssh git-upload-pack && |
||||||
|
|
||||||
|
git -C ssh_child log -1 --format=%s >actual && |
||||||
|
git -C ssh_parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "clone< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'fetch with ssh:// using protocol v1' ' |
||||||
|
test_commit -C ssh_parent two && |
||||||
|
|
||||||
|
GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ |
||||||
|
fetch 2>log && |
||||||
|
expect_ssh git-upload-pack && |
||||||
|
|
||||||
|
git -C ssh_child log -1 --format=%s origin/master >actual && |
||||||
|
git -C ssh_parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "fetch< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'pull with ssh:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ |
||||||
|
pull 2>log && |
||||||
|
expect_ssh git-upload-pack && |
||||||
|
|
||||||
|
git -C ssh_child log -1 --format=%s >actual && |
||||||
|
git -C ssh_parent log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "fetch< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'push with ssh:// using protocol v1' ' |
||||||
|
test_commit -C ssh_child three && |
||||||
|
|
||||||
|
# Push to another branch, as the target repository has the |
||||||
|
# master branch checked out and we cannot push into it. |
||||||
|
GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ |
||||||
|
push origin HEAD:client_branch 2>log && |
||||||
|
expect_ssh git-receive-pack && |
||||||
|
|
||||||
|
git -C ssh_child log -1 --format=%s >actual && |
||||||
|
git -C ssh_parent log -1 --format=%s client_branch >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "push< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
# Test protocol v1 with 'http://' transport |
||||||
|
# |
||||||
|
. "$TEST_DIRECTORY"/lib-httpd.sh |
||||||
|
start_httpd |
||||||
|
|
||||||
|
test_expect_success 'create repo to be served by http:// transport' ' |
||||||
|
git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && |
||||||
|
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true && |
||||||
|
test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'clone with http:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 GIT_TRACE_CURL=1 git -c protocol.version=1 \ |
||||||
|
clone "$HTTPD_URL/smart/http_parent" http_child 2>log && |
||||||
|
|
||||||
|
git -C http_child log -1 --format=%s >actual && |
||||||
|
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Client requested to use protocol v1 |
||||||
|
grep "Git-Protocol: version=1" log && |
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "git< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'fetch with http:// using protocol v1' ' |
||||||
|
test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && |
||||||
|
|
||||||
|
GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ |
||||||
|
fetch 2>log && |
||||||
|
|
||||||
|
git -C http_child log -1 --format=%s origin/master >actual && |
||||||
|
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "git< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'pull with http:// using protocol v1' ' |
||||||
|
GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ |
||||||
|
pull 2>log && |
||||||
|
|
||||||
|
git -C http_child log -1 --format=%s >actual && |
||||||
|
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "git< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
test_expect_success 'push with http:// using protocol v1' ' |
||||||
|
test_commit -C http_child three && |
||||||
|
|
||||||
|
# Push to another branch, as the target repository has the |
||||||
|
# master branch checked out and we cannot push into it. |
||||||
|
GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ |
||||||
|
push origin HEAD:client_branch && #2>log && |
||||||
|
|
||||||
|
git -C http_child log -1 --format=%s >actual && |
||||||
|
git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect && |
||||||
|
test_cmp expect actual && |
||||||
|
|
||||||
|
# Server responded using protocol v1 |
||||||
|
grep "git< version 1" log |
||||||
|
' |
||||||
|
|
||||||
|
stop_httpd |
||||||
|
|
||||||
|
test_done |
Loading…
Reference in new issue