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 last
This commit is contained in:
Junio C Hamano 2017-12-06 09:23:44 -08:00
Родитель f65ab57444 6464679d96
Коммит 4c6dad0059
19 изменённых файлов: 969 добавлений и 150 удалений

Просмотреть файл

@ -2111,12 +2111,31 @@ ssh.variant::
Depending on the value of the environment variables `GIT_SSH` or
`GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git
auto-detects whether to adjust its command-line parameters for use
with plink or tortoiseplink, as opposed to the default (OpenSSH).
with ssh (OpenSSH), plink or tortoiseplink, as opposed to the default
(simple).
+
The config variable `ssh.variant` can be set to override this auto-detection;
valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value
will be treated as normal ssh. This setting can be overridden via the
environment variable `GIT_SSH_VARIANT`.
valid values are `ssh`, `simple`, `plink`, `putty` or `tortoiseplink`. Any
other value will be treated as normal ssh. This setting can be overridden via
the environment variable `GIT_SSH_VARIANT`.
+
The current command-line parameters used for each variant are as
follows:
+
--
* `ssh` - [-p port] [-4] [-6] [-o option] [username@]host command
* `simple` - [username@]host command
* `plink` or `putty` - [-P port] [-4] [-6] [username@]host command
* `tortoiseplink` - [-P port] [-4] [-6] -batch [username@]host command
--
+
Except for the `simple` variant, command-line parameters are likely to
change as git gains new features.
i18n.commitEncoding::
Character encoding the commit messages are stored in; Git itself
@ -2544,6 +2563,23 @@ The protocol names currently used by git are:
`hg` to allow the `git-remote-hg` helper)
--
protocol.version::
Experimental. If set, clients will attempt to communicate with a
server using the specified protocol version. If unset, no
attempt will be made by the client to communicate using a
particular protocol version, this results in protocol version 0
being used.
Supported versions:
+
--
* `0` - the original wire protocol.
* `1` - the original wire protocol with the addition of a version string
in the initial response from the server.
--
pull.ff::
By default, Git does not create an extra merge commit when merging
a commit that is a descendant of the current commit. Instead, the

Просмотреть файл

@ -522,11 +522,10 @@ other
If either of these environment variables is set then 'git fetch'
and 'git push' will use the specified command instead of 'ssh'
when they need to connect to a remote system.
The command will be given exactly two or four arguments: the
'username@host' (or just 'host') from the URL and the shell
command to execute on that remote system, optionally preceded by
`-p` (literally) and the 'port' from the URL when it specifies
something other than the default SSH port.
The command-line parameters passed to the configured command are
determined by the ssh variant. See `ssh.variant` option in
linkgit:git-config[1] for details.
+
`$GIT_SSH_COMMAND` takes precedence over `$GIT_SSH`, and is interpreted
by the shell, which allows additional arguments to be included.
@ -705,6 +704,12 @@ of clones and fetches.
which feed potentially-untrusted URLS to git commands. See
linkgit:git-config[1] for more details.
`GIT_PROTOCOL`::
For internal use only. Used in handshaking the wire protocol.
Contains a colon ':' separated list of keys with optional values
'key[=value]'. Presence of unknown keys and values must be
ignored.
`GIT_OPTIONAL_LOCKS`::
If set to `0`, Git will complete any requested operation without
performing any optional sub-operations that require taking a lock.

Просмотреть файл

@ -219,6 +219,10 @@ smart server reply:
S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n
S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n
The client may send Extra Parameters (see
Documentation/technical/pack-protocol.txt) as a colon-separated string
in the Git-Protocol HTTP header.
Dumb Server Response
^^^^^^^^^^^^^^^^^^^^
Dumb servers MUST respond with the dumb server reply format.
@ -269,7 +273,11 @@ the C locale ordering. The stream SHOULD include the default ref
named `HEAD` as the first ref. The stream MUST include capability
declarations behind a NUL on the first ref.
The returned response contains "version 1" if "version=1" was sent as an
Extra Parameter.
smart_reply = PKT-LINE("# service=$servicename" LF)
*1("version 1")
ref_list
"0000"
ref_list = empty_list / non_empty_list

Просмотреть файл

@ -39,6 +39,19 @@ communicates with that invoked process over the SSH connection.
The file:// transport runs the 'upload-pack' or 'receive-pack'
process locally and communicates with it over a pipe.
Extra Parameters
----------------
The protocol provides a mechanism in which clients can send additional
information in its first message to the server. These are called "Extra
Parameters", and are supported by the Git, SSH, and HTTP protocols.
Each Extra Parameter takes the form of `<key>=<value>` or `<key>`.
Servers that receive any such Extra Parameters MUST ignore all
unrecognized keys. Currently, the only Extra Parameter recognized is
"version=1".
Git Transport
-------------
@ -46,18 +59,25 @@ The Git transport starts off by sending the command and repository
on the wire using the pkt-line format, followed by a NUL byte and a
hostname parameter, terminated by a NUL byte.
0032git-upload-pack /project.git\0host=myserver.com\0
0033git-upload-pack /project.git\0host=myserver.com\0
The transport may send Extra Parameters by adding an additional NUL
byte, and then adding one or more NUL-terminated strings:
003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0
--
git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
git-proto-request = request-command SP pathname NUL
[ host-parameter NUL ] [ NUL extra-parameters ]
request-command = "git-upload-pack" / "git-receive-pack" /
"git-upload-archive" ; case sensitive
pathname = *( %x01-ff ) ; exclude NUL
host-parameter = "host=" hostname [ ":" port ]
extra-parameters = 1*extra-parameter
extra-parameter = 1*( %x01-ff ) NUL
--
Only host-parameter is allowed in the git-proto-request. Clients
MUST NOT attempt to send additional parameters. It is used for the
host-parameter is used for the
git-daemon name based virtual hosting. See --interpolated-path
option to git daemon, with the %H/%CH format characters.
@ -117,6 +137,12 @@ we execute it without the leading '/'.
v
ssh user@example.com "git-upload-pack '~alice/project.git'"
Depending on the value of the `protocol.version` configuration variable,
Git may attempt to send Extra Parameters as a colon-separated string in
the GIT_PROTOCOL environment variable. This is done only if
the `ssh.variant` configuration variable indicates that the ssh command
supports passing environment variables as an argument.
A few things to remember here:
- The "command name" is spelled with dash (e.g. git-upload-pack), but
@ -137,11 +163,13 @@ Reference Discovery
-------------------
When the client initially connects the server will immediately respond
with a listing of each reference it has (all branches and tags) along
with a version number (if "version=1" is sent as an Extra Parameter),
and a listing of each reference it has (all branches and tags) along
with the object name that each reference currently points to.
$ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
$ echo -e -n "0044git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" |
nc -v example.com 9418
000aversion 1
00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
side-band side-band-64k ofs-delta shallow no-progress include-tag
00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
@ -165,7 +193,8 @@ immediately after the ref itself, if presented. A conforming server
MUST peel the ref if it's an annotated tag.
----
advertised-refs = (no-refs / list-of-refs)
advertised-refs = *1("version 1")
(no-refs / list-of-refs)
*shallow
flush-pkt

Просмотреть файл

@ -849,6 +849,7 @@ LIB_OBJS += pretty.o
LIB_OBJS += prio-queue.o
LIB_OBJS += progress.o
LIB_OBJS += prompt.o
LIB_OBJS += protocol.o
LIB_OBJS += quote.o
LIB_OBJS += reachable.o
LIB_OBJS += read-cache.o

Просмотреть файл

@ -24,6 +24,7 @@
#include "tmp-objdir.h"
#include "oidset.h"
#include "packfile.h"
#include "protocol.h"
static const char * const receive_pack_usage[] = {
N_("git receive-pack <git-dir>"),
@ -1961,6 +1962,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
else if (0 <= receive_unpack_limit)
unpack_limit = receive_unpack_limit;
switch (determine_protocol_version_server()) {
case protocol_v1:
/*
* v1 is just the original protocol with a version string,
* so just fall through after writing the version string.
*/
if (advertise_refs || !stateless_rpc)
packet_write_fmt(1, "version 1\n");
/* fallthrough */
case protocol_v0:
break;
case protocol_unknown_version:
BUG("unknown protocol version");
}
if (advertise_refs || !stateless_rpc) {
write_head_info();
}

10
cache.h
Просмотреть файл

@ -450,6 +450,16 @@ static inline enum object_type object_type(unsigned int mode)
#define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
#define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
/*
* Environment variable used in handshaking the wire protocol.
* Contains a colon ':' separated list of keys with optional values
* 'key[=value]'. Presence of unknown keys and values must be
* ignored.
*/
#define GIT_PROTOCOL_ENVIRONMENT "GIT_PROTOCOL"
/* HTTP header used to handshake the wire protocol */
#define GIT_PROTOCOL_HEADER "Git-Protocol"
/*
* This environment variable is expected to contain a boolean indicating
* whether we should or should not treat:

356
connect.c
Просмотреть файл

@ -11,6 +11,8 @@
#include "string-list.h"
#include "sha1-array.h"
#include "transport.h"
#include "strbuf.h"
#include "protocol.h"
static char *server_capabilities;
static const char *parse_feature_value(const char *, const char *, int *);
@ -107,6 +109,118 @@ static void annotate_refs_with_symref_info(struct ref *ref)
string_list_clear(&symref, 0);
}
/*
* Read one line of a server's ref advertisement into packet_buffer.
*/
static int read_remote_ref(int in, char **src_buf, size_t *src_len,
int *responded)
{
int len = packet_read(in, src_buf, src_len,
packet_buffer, sizeof(packet_buffer),
PACKET_READ_GENTLE_ON_EOF |
PACKET_READ_CHOMP_NEWLINE);
const char *arg;
if (len < 0)
die_initial_contact(*responded);
if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg))
die("remote error: %s", arg);
*responded = 1;
return len;
}
#define EXPECTING_PROTOCOL_VERSION 0
#define EXPECTING_FIRST_REF 1
#define EXPECTING_REF 2
#define EXPECTING_SHALLOW 3
/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */
static int process_protocol_version(void)
{
switch (determine_protocol_version_client(packet_buffer)) {
case protocol_v1:
return 1;
case protocol_v0:
return 0;
default:
die("server is speaking an unknown protocol");
}
}
static void process_capabilities(int *len)
{
int nul_location = strlen(packet_buffer);
if (nul_location == *len)
return;
server_capabilities = xstrdup(packet_buffer + nul_location + 1);
*len = nul_location;
}
static int process_dummy_ref(void)
{
struct object_id oid;
const char *name;
if (parse_oid_hex(packet_buffer, &oid, &name))
return 0;
if (*name != ' ')
return 0;
name++;
return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
}
static void check_no_capabilities(int len)
{
if (strlen(packet_buffer) != len)
warning("Ignoring capabilities after first line '%s'",
packet_buffer + strlen(packet_buffer));
}
static int process_ref(int len, struct ref ***list, unsigned int flags,
struct oid_array *extra_have)
{
struct object_id old_oid;
const char *name;
if (parse_oid_hex(packet_buffer, &old_oid, &name))
return 0;
if (*name != ' ')
return 0;
name++;
if (extra_have && !strcmp(name, ".have")) {
oid_array_append(extra_have, &old_oid);
} else if (!strcmp(name, "capabilities^{}")) {
die("protocol error: unexpected capabilities^{}");
} else if (check_ref(name, flags)) {
struct ref *ref = alloc_ref(name);
oidcpy(&ref->old_oid, &old_oid);
**list = ref;
*list = &ref->next;
}
check_no_capabilities(len);
return 1;
}
static int process_shallow(int len, struct oid_array *shallow_points)
{
const char *arg;
struct object_id old_oid;
if (!skip_prefix(packet_buffer, "shallow ", &arg))
return 0;
if (get_oid_hex(arg, &old_oid))
die("protocol error: expected shallow sha-1, got '%s'", arg);
if (!shallow_points)
die("repository on the other end cannot be shallow");
oid_array_append(shallow_points, &old_oid);
check_no_capabilities(len);
return 1;
}
/*
* Read all the refs from the other end
*/
@ -123,76 +237,41 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
* willing to talk to us. A hang-up before seeing any
* response does not necessarily mean an ACL problem, though.
*/
int saw_response;
int got_dummy_ref_with_capabilities_declaration = 0;
int responded = 0;
int len;
int state = EXPECTING_PROTOCOL_VERSION;
*list = NULL;
for (saw_response = 0; ; saw_response = 1) {
struct ref *ref;
struct object_id old_oid;
char *name;
int len, name_len;
char *buffer = packet_buffer;
const char *arg;
len = packet_read(in, &src_buf, &src_len,
packet_buffer, sizeof(packet_buffer),
PACKET_READ_GENTLE_ON_EOF |
PACKET_READ_CHOMP_NEWLINE);
if (len < 0)
die_initial_contact(saw_response);
if (!len)
break;
if (len > 4 && skip_prefix(buffer, "ERR ", &arg))
die("remote error: %s", arg);
if (len == GIT_SHA1_HEXSZ + strlen("shallow ") &&
skip_prefix(buffer, "shallow ", &arg)) {
if (get_oid_hex(arg, &old_oid))
die("protocol error: expected shallow sha-1, got '%s'", arg);
if (!shallow_points)
die("repository on the other end cannot be shallow");
oid_array_append(shallow_points, &old_oid);
continue;
while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) {
switch (state) {
case EXPECTING_PROTOCOL_VERSION:
if (process_protocol_version()) {
state = EXPECTING_FIRST_REF;
break;
}
state = EXPECTING_FIRST_REF;
/* fallthrough */
case EXPECTING_FIRST_REF:
process_capabilities(&len);
if (process_dummy_ref()) {
state = EXPECTING_SHALLOW;
break;
}
state = EXPECTING_REF;
/* fallthrough */
case EXPECTING_REF:
if (process_ref(len, &list, flags, extra_have))
break;
state = EXPECTING_SHALLOW;
/* fallthrough */
case EXPECTING_SHALLOW:
if (process_shallow(len, shallow_points))
break;
die("protocol error: unexpected '%s'", packet_buffer);
default:
die("unexpected state %d", state);
}
if (len < GIT_SHA1_HEXSZ + 2 || get_oid_hex(buffer, &old_oid) ||
buffer[GIT_SHA1_HEXSZ] != ' ')
die("protocol error: expected sha/ref, got '%s'", buffer);
name = buffer + GIT_SHA1_HEXSZ + 1;
name_len = strlen(name);
if (len != name_len + GIT_SHA1_HEXSZ + 1) {
free(server_capabilities);
server_capabilities = xstrdup(name + name_len + 1);
}
if (extra_have && !strcmp(name, ".have")) {
oid_array_append(extra_have, &old_oid);
continue;
}
if (!strcmp(name, "capabilities^{}")) {
if (saw_response)
die("protocol error: unexpected capabilities^{}");
if (got_dummy_ref_with_capabilities_declaration)
die("protocol error: multiple capabilities^{}");
got_dummy_ref_with_capabilities_declaration = 1;
continue;
}
if (!check_ref(name, flags))
continue;
if (got_dummy_ref_with_capabilities_declaration)
die("protocol error: unexpected ref after capabilities^{}");
ref = alloc_ref(buffer + GIT_SHA1_HEXSZ + 1);
oidcpy(&ref->old_oid, &old_oid);
*list = ref;
list = &ref->next;
}
annotate_refs_with_symref_info(*orig_list);
@ -697,37 +776,44 @@ static const char *get_ssh_command(void)
return NULL;
}
static int override_ssh_variant(int *port_option, int *needs_batch)
{
char *variant;
enum ssh_variant {
VARIANT_SIMPLE,
VARIANT_SSH,
VARIANT_PLINK,
VARIANT_PUTTY,
VARIANT_TORTOISEPLINK,
};
variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT"));
if (!variant &&
git_config_get_string("ssh.variant", &variant))
static int override_ssh_variant(enum ssh_variant *ssh_variant)
{
const char *variant = getenv("GIT_SSH_VARIANT");
if (!variant && git_config_get_string_const("ssh.variant", &variant))
return 0;
if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) {
*port_option = 'P';
*needs_batch = 0;
} else if (!strcmp(variant, "tortoiseplink")) {
*port_option = 'P';
*needs_batch = 1;
} else {
*port_option = 'p';
*needs_batch = 0;
}
free(variant);
if (!strcmp(variant, "plink"))
*ssh_variant = VARIANT_PLINK;
else if (!strcmp(variant, "putty"))
*ssh_variant = VARIANT_PUTTY;
else if (!strcmp(variant, "tortoiseplink"))
*ssh_variant = VARIANT_TORTOISEPLINK;
else if (!strcmp(variant, "simple"))
*ssh_variant = VARIANT_SIMPLE;
else
*ssh_variant = VARIANT_SSH;
return 1;
}
static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
int *port_option, int *needs_batch)
static enum ssh_variant determine_ssh_variant(const char *ssh_command,
int is_cmdline)
{
enum ssh_variant ssh_variant = VARIANT_SIMPLE;
const char *variant;
char *p = NULL;
if (override_ssh_variant(port_option, needs_batch))
return;
if (override_ssh_variant(&ssh_variant))
return ssh_variant;
if (!is_cmdline) {
p = xstrdup(ssh_command);
@ -746,19 +832,22 @@ static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
free(ssh_argv);
} else {
free(p);
return;
return ssh_variant;
}
}
if (!strcasecmp(variant, "plink") ||
!strcasecmp(variant, "plink.exe"))
*port_option = 'P';
if (!strcasecmp(variant, "ssh") ||
!strcasecmp(variant, "ssh.exe"))
ssh_variant = VARIANT_SSH;
else if (!strcasecmp(variant, "plink") ||
!strcasecmp(variant, "plink.exe"))
ssh_variant = VARIANT_PLINK;
else if (!strcasecmp(variant, "tortoiseplink") ||
!strcasecmp(variant, "tortoiseplink.exe")) {
*port_option = 'P';
*needs_batch = 1;
}
!strcasecmp(variant, "tortoiseplink.exe"))
ssh_variant = VARIANT_TORTOISEPLINK;
free(p);
return ssh_variant;
}
/*
@ -792,6 +881,7 @@ 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) {
struct strbuf request = STRBUF_INIT;
/*
* Set up virtual host information based on where we will
* connect, unless the user has overridden us in
@ -819,13 +909,25 @@ struct child_process *git_connect(int fd[2], const char *url,
* Note: Do not add any other headers here! Doing so
* will cause older git-daemon servers to crash.
*/
packet_write_fmt(fd[1],
"%s %s%chost=%s%c",
prog, path, 0,
target_host, 0);
strbuf_addf(&request,
"%s %s%chost=%s%c",
prog, path, 0,
target_host, 0);
/* If using a new version put that stuff here after a second null byte */
if (get_protocol_version_config() > 0) {
strbuf_addch(&request, '\0');
strbuf_addf(&request, "version=%d%c",
get_protocol_version_config(), '\0');
}
packet_write(fd[1], request.buf, request.len);
free(target_host);
strbuf_release(&request);
} else {
struct strbuf cmd = STRBUF_INIT;
const char *const *var;
conn = xmalloc(sizeof(*conn));
child_process_init(conn);
@ -838,13 +940,14 @@ struct child_process *git_connect(int fd[2], const char *url,
sq_quote_buf(&cmd, path);
/* remove repo-local variables from the environment */
conn->env = local_repo_env;
for (var = local_repo_env; *var; var++)
argv_array_push(&conn->env_array, *var);
conn->use_shell = 1;
conn->in = conn->out = -1;
if (protocol == PROTO_SSH) {
const char *ssh;
int needs_batch = 0;
int port_option = 'p';
enum ssh_variant variant;
char *ssh_host = hostandport;
const char *port = NULL;
transport_check_allowed("ssh");
@ -871,10 +974,9 @@ struct child_process *git_connect(int fd[2], const char *url,
die("strange hostname '%s' blocked", ssh_host);
ssh = get_ssh_command();
if (ssh)
handle_ssh_variant(ssh, 1, &port_option,
&needs_batch);
else {
if (ssh) {
variant = determine_ssh_variant(ssh, 1);
} else {
/*
* GIT_SSH is the no-shell version of
* GIT_SSH_COMMAND (and must remain so for
@ -885,27 +987,45 @@ struct child_process *git_connect(int fd[2], const char *url,
ssh = getenv("GIT_SSH");
if (!ssh)
ssh = "ssh";
else
handle_ssh_variant(ssh, 0,
&port_option,
&needs_batch);
variant = determine_ssh_variant(ssh, 0);
}
argv_array_push(&conn->args, ssh);
if (flags & CONNECT_IPV4)
argv_array_push(&conn->args, "-4");
else if (flags & CONNECT_IPV6)
argv_array_push(&conn->args, "-6");
if (needs_batch)
if (variant == VARIANT_SSH &&
get_protocol_version_config() > 0) {
argv_array_push(&conn->args, "-o");
argv_array_push(&conn->args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
get_protocol_version_config());
}
if (variant != VARIANT_SIMPLE) {
if (flags & CONNECT_IPV4)
argv_array_push(&conn->args, "-4");
else if (flags & CONNECT_IPV6)
argv_array_push(&conn->args, "-6");
}
if (variant == VARIANT_TORTOISEPLINK)
argv_array_push(&conn->args, "-batch");
if (port) {
argv_array_pushf(&conn->args,
"-%c", port_option);
if (port && variant != VARIANT_SIMPLE) {
if (variant == VARIANT_SSH)
argv_array_push(&conn->args, "-p");
else
argv_array_push(&conn->args, "-P");
argv_array_push(&conn->args, port);
}
argv_array_push(&conn->args, ssh_host);
} else {
transport_check_allowed("file");
if (get_protocol_version_config() > 0) {
argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
get_protocol_version_config());
}
}
argv_array_push(&conn->args, cmd.buf);

Просмотреть файл

@ -282,7 +282,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
return NULL; /* Fallthrough. Deny by default */
}
typedef int (*daemon_service_fn)(void);
typedef int (*daemon_service_fn)(const struct argv_array *env);
struct daemon_service {
const char *name;
const char *config_name;
@ -363,7 +363,7 @@ error_return:
}
static int run_service(const char *dir, struct daemon_service *service,
struct hostinfo *hi)
struct hostinfo *hi, const struct argv_array *env)
{
const char *path;
int enabled = service->enabled;
@ -422,7 +422,7 @@ static int run_service(const char *dir, struct daemon_service *service,
*/
signal(SIGTERM, SIG_IGN);
return service->fn();
return service->fn(env);
}
static void copy_to_log(int fd)
@ -462,25 +462,34 @@ static int run_service_command(struct child_process *cld)
return finish_command(cld);
}
static int upload_pack(void)
static int upload_pack(const struct argv_array *env)
{
struct child_process cld = CHILD_PROCESS_INIT;
argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL);
argv_array_pushf(&cld.args, "--timeout=%u", timeout);
argv_array_pushv(&cld.env_array, env->argv);
return run_service_command(&cld);
}
static int upload_archive(void)
static int upload_archive(const struct argv_array *env)
{
struct child_process cld = CHILD_PROCESS_INIT;
argv_array_push(&cld.args, "upload-archive");
argv_array_pushv(&cld.env_array, env->argv);
return run_service_command(&cld);
}
static int receive_pack(void)
static int receive_pack(const struct argv_array *env)
{
struct child_process cld = CHILD_PROCESS_INIT;
argv_array_push(&cld.args, "receive-pack");
argv_array_pushv(&cld.env_array, env->argv);
return run_service_command(&cld);
}
@ -573,8 +582,11 @@ static void canonicalize_client(struct strbuf *out, const char *in)
/*
* Read the host as supplied by the client connection.
*
* Returns a pointer to the character after the NUL byte terminating the host
* arguemnt, or 'extra_args' if there is no host arguemnt.
*/
static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
{
char *val;
int vallen;
@ -602,6 +614,43 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
if (extra_args < end && *extra_args)
die("Invalid request");
}
return extra_args;
}
static void parse_extra_args(struct hostinfo *hi, struct argv_array *env,
char *extra_args, int buflen)
{
const char *end = extra_args + buflen;
struct strbuf git_protocol = STRBUF_INIT;
/* First look for the host argument */
extra_args = parse_host_arg(hi, extra_args, buflen);
/* Look for additional arguments places after a second NUL byte */
for (; extra_args < end; extra_args += strlen(extra_args) + 1) {
const char *arg = extra_args;
/*
* Parse the extra arguments, adding most to 'git_protocol'
* which will be used to set the 'GIT_PROTOCOL' envvar in the
* service that will be run.
*
* If there ends up being a particular arg in the future that
* git-daemon needs to parse specificly (like the 'host' arg)
* then it can be parsed here and not added to 'git_protocol'.
*/
if (*arg) {
if (git_protocol.len > 0)
strbuf_addch(&git_protocol, ':');
strbuf_addstr(&git_protocol, arg);
}
}
if (git_protocol.len > 0)
argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
git_protocol.buf);
strbuf_release(&git_protocol);
}
/*
@ -695,6 +744,7 @@ static int execute(void)
int pktlen, len, i;
char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
struct hostinfo hi;
struct argv_array env = ARGV_ARRAY_INIT;
hostinfo_init(&hi);
@ -716,8 +766,9 @@ static int execute(void)
pktlen--;
}
/* parse additional args hidden behind a NUL byte */
if (len != pktlen)
parse_host_arg(&hi, line + len + 1, pktlen - len - 1);
parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1);
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
struct daemon_service *s = &(daemon_service[i]);
@ -730,13 +781,15 @@ static int execute(void)
* Note: The directory here is probably context sensitive,
* and might depend on the actual service being performed.
*/
int rc = run_service(arg, s, &hi);
int rc = run_service(arg, s, &hi, &env);
hostinfo_clear(&hi);
argv_array_clear(&env);
return rc;
}
}
hostinfo_clear(&hi);
argv_array_clear(&env);
logerror("Protocol error: '%s'", line);
return -1;
}

18
http.c
Просмотреть файл

@ -12,6 +12,7 @@
#include "gettext.h"
#include "transport.h"
#include "packfile.h"
#include "protocol.h"
static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
#if LIBCURL_VERSION_NUM >= 0x070a08
@ -898,6 +899,21 @@ static void set_from_env(const char **var, const char *envname)
*var = val;
}
static void protocol_http_header(void)
{
if (get_protocol_version_config() > 0) {
struct strbuf protocol_header = STRBUF_INIT;
strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d",
get_protocol_version_config());
extra_http_headers = curl_slist_append(extra_http_headers,
protocol_header.buf);
strbuf_release(&protocol_header);
}
}
void http_init(struct remote *remote, const char *url, int proactive_auth)
{
char *low_speed_limit;
@ -928,6 +944,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
if (remote)
var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
protocol_http_header();
pragma_header = curl_slist_append(http_copy_default_headers(),
"Pragma: no-cache");
no_pragma_header = curl_slist_append(http_copy_default_headers(),

Просмотреть файл

@ -188,6 +188,12 @@ static int packet_write_gently(const int fd_out, const char *buf, size_t size)
return 0;
}
void packet_write(int fd_out, const char *buf, size_t size)
{
if (packet_write_gently(fd_out, buf, size))
die_errno("packet write failed");
}
void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
{
va_list args;

Просмотреть файл

@ -22,6 +22,7 @@
void packet_flush(int fd);
void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
void packet_buf_flush(struct strbuf *buf);
void packet_write(int fd_out, const char *buf, size_t size);
void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
int packet_flush_gently(int fd);
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));

79
protocol.c Normal file
Просмотреть файл

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

33
protocol.h Normal file
Просмотреть файл

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

Просмотреть файл

@ -67,6 +67,9 @@ LockFile accept.lock
<IfModule !mod_unixd.c>
LoadModule unixd_module modules/mod_unixd.so
</IfModule>
<IfModule !mod_setenvif.c>
LoadModule setenvif_module modules/mod_setenvif.so
</IfModule>
</IfVersion>
PassEnv GIT_VALGRIND
@ -76,6 +79,10 @@ PassEnv ASAN_OPTIONS
PassEnv GIT_TRACE
PassEnv GIT_CONFIG_NOSYSTEM
<IfVersion >= 2.4>
SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
</IfVersion>
Alias /dumb/ www/
Alias /auth/dumb/ www/auth/dumb/

Просмотреть файл

@ -308,10 +308,10 @@ test_expect_success 'clone checking out a tag' '
setup_ssh_wrapper () {
test_expect_success 'setup ssh wrapper' '
rm -f "$TRASH_DIRECTORY/ssh-wrapper$X" &&
rm -f "$TRASH_DIRECTORY/ssh$X" &&
cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
"$TRASH_DIRECTORY/ssh-wrapper$X" &&
GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" &&
"$TRASH_DIRECTORY/ssh$X" &&
GIT_SSH="$TRASH_DIRECTORY/ssh$X" &&
export GIT_SSH &&
export TRASH_DIRECTORY &&
>"$TRASH_DIRECTORY"/ssh-output
@ -320,7 +320,7 @@ setup_ssh_wrapper () {
copy_ssh_wrapper_as () {
rm -f "${1%$X}$X" &&
cp "$TRASH_DIRECTORY/ssh-wrapper$X" "${1%$X}$X" &&
cp "$TRASH_DIRECTORY/ssh$X" "${1%$X}$X" &&
GIT_SSH="${1%$X}$X" &&
export GIT_SSH
}
@ -364,10 +364,26 @@ test_expect_success 'bracketed hostnames are still ssh' '
expect_ssh "-p 123" myhost src
'
test_expect_success 'uplink is not treated as putty' '
test_expect_success 'OpenSSH variant passes -4' '
git clone -4 "[myhost:123]:src" ssh-ipv4-clone &&
expect_ssh "-4 -p 123" myhost src
'
test_expect_success 'variant can be overriden' '
git -c ssh.variant=simple clone -4 "[myhost:123]:src" ssh-simple-clone &&
expect_ssh myhost src
'
test_expect_success 'simple is treated as simple' '
copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" &&
git clone -4 "[myhost:123]:src" ssh-bracket-clone-simple &&
expect_ssh myhost src
'
test_expect_success 'uplink is treated as simple' '
copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" &&
git clone "[myhost:123]:src" ssh-bracket-clone-uplink &&
expect_ssh "-p 123" myhost src
expect_ssh myhost src
'
test_expect_success 'plink is treated specially (as putty)' '

294
t/t5700-protocol-v1.sh Executable file
Просмотреть файл

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

Просмотреть файл

@ -18,6 +18,7 @@
#include "parse-options.h"
#include "argv-array.h"
#include "prio-queue.h"
#include "protocol.h"
static const char * const upload_pack_usage[] = {
N_("git upload-pack [<options>] <dir>"),
@ -1066,6 +1067,23 @@ int cmd_main(int argc, const char **argv)
die("'%s' does not appear to be a git repository", dir);
git_config(upload_pack_config, NULL);
upload_pack();
switch (determine_protocol_version_server()) {
case protocol_v1:
/*
* v1 is just the original protocol with a version string,
* so just fall through after writing the version string.
*/
if (advertise_refs || !stateless_rpc)
packet_write_fmt(1, "version 1\n");
/* fallthrough */
case protocol_v0:
upload_pack();
break;
case protocol_unknown_version:
BUG("unknown protocol version");
}
return 0;
}