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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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