зеркало из https://github.com/microsoft/git.git
Merge branch 'bw/protocol-v1'
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:
Коммит
4c6dad0059
|
@ -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
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -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
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
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);
|
||||
|
||||
|
|
71
daemon.c
71
daemon.c
|
@ -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
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)));
|
||||
|
|
|
@ -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
|
|
@ -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)' '
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче