ssh: 'auto' variant to select between 'ssh' and 'simple'

Android's "repo" tool is a tool for managing a large codebase
consisting of multiple smaller repositories, similar to Git's
submodule feature.  Starting with Git 94b8ae5a (ssh: introduce a
'simple' ssh variant, 2017-10-16), users noticed that it stopped
handling the port in ssh:// URLs.

The cause: when it encounters ssh:// URLs, repo pre-connects to the
server and sets GIT_SSH to a helper ".repo/repo/git_ssh" that reuses
that connection.  Before 94b8ae5a, the helper was assumed to support
OpenSSH options for lack of a better guess and got passed a -p option
to set the port.  After that patch, it uses the new default of a
simple helper that does not accept an option to set the port.

The next release of "repo" will set GIT_SSH_VARIANT to "ssh" to avoid
that.  But users of old versions and of other similar GIT_SSH
implementations would not get the benefit of that fix.

So update the default to use OpenSSH options again, with a twist.  As
observed in 94b8ae5a, we cannot assume that $GIT_SSH always handles
OpenSSH options: common helpers such as travis-ci's dpl[*] are
configured using GIT_SSH and do not accept OpenSSH options.  So make
the default a new variant "auto", with the following behavior:

 1. First, check for a recognized basename, like today.

 2. If the basename is not recognized, check whether $GIT_SSH supports
    OpenSSH options by running

	$GIT_SSH -G <options> <host>

    This returns status 0 and prints configuration in OpenSSH if it
    recognizes all <options> and returns status 255 if it encounters
    an unrecognized option.  A wrapper script like

	exec ssh -- "$@"

    would fail with

	ssh: Could not resolve hostname -g: Name or service not known

    , correctly reflecting that it does not support OpenSSH options.
    The command is run with stdin, stdout, and stderr redirected to
    /dev/null so even a command that expects a terminal would exit
    immediately.

 3. Based on the result from step (2), behave like "ssh" (if it
    succeeded) or "simple" (if it failed).

This way, the default ssh variant for unrecognized commands can handle
both the repo and dpl cases as intended.

This autodetection has been running on Google workstations since
2017-10-23 with no reported negative effects.

[*] 6c3fddfda1/lib/dpl/provider.rb (L215)

Reported-by: William Yan <wyan@google.com>
Improved-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jonathan Nieder 2017-11-20 13:30:04 -08:00 committed by Junio C Hamano
parent 957e2ad282
commit 0da0e49ba1
3 changed files with 61 additions and 16 deletions

View File

@ -2081,16 +2081,22 @@ matched against are those given directly to Git commands. This means any URLs
visited as a result of a redirection do not participate in matching.
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 ssh (OpenSSH), plink or tortoiseplink, as opposed to the default
(simple).
By default, Git determines the command line arguments to use
based on the basename of the configured SSH command (configured
using the environment variable `GIT_SSH` or `GIT_SSH_COMMAND` or
the config setting `core.sshCommand`). If the basename is
unrecognized, Git will attempt to detect support of OpenSSH
options by first invoking the configured SSH command with the
`-G` (print configuration) option and will subsequently use
OpenSSH options (if that is successful) or no options besides
the host and remote command (if it fails).
+
The config variable `ssh.variant` can be set to override this auto-detection;
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 config variable `ssh.variant` can be set to override this detection.
Valid values are `ssh` (to use OpenSSH options), `plink`, `putty`,
`tortoiseplink`, `simple` (no options except the host and remote command).
The default auto-detection can be explicitly requested using the value
`auto`. Any other value is treated as `ssh`. This setting can also be
overridden via the environment variable `GIT_SSH_VARIANT`.
+
The current command-line parameters used for each variant are as
follows:

View File

@ -788,6 +788,7 @@ static const char *get_ssh_command(void)
}
enum ssh_variant {
VARIANT_AUTO,
VARIANT_SIMPLE,
VARIANT_SSH,
VARIANT_PLINK,
@ -795,14 +796,16 @@ enum ssh_variant {
VARIANT_TORTOISEPLINK,
};
static int override_ssh_variant(enum ssh_variant *ssh_variant)
static void 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;
return;
if (!strcmp(variant, "plink"))
if (!strcmp(variant, "auto"))
*ssh_variant = VARIANT_AUTO;
else if (!strcmp(variant, "plink"))
*ssh_variant = VARIANT_PLINK;
else if (!strcmp(variant, "putty"))
*ssh_variant = VARIANT_PUTTY;
@ -812,18 +815,18 @@ static int override_ssh_variant(enum ssh_variant *ssh_variant)
*ssh_variant = VARIANT_SIMPLE;
else
*ssh_variant = VARIANT_SSH;
return 1;
}
static enum ssh_variant determine_ssh_variant(const char *ssh_command,
int is_cmdline)
{
enum ssh_variant ssh_variant = VARIANT_SIMPLE;
enum ssh_variant ssh_variant = VARIANT_AUTO;
const char *variant;
char *p = NULL;
if (override_ssh_variant(&ssh_variant))
override_ssh_variant(&ssh_variant);
if (ssh_variant != VARIANT_AUTO)
return ssh_variant;
if (!is_cmdline) {
@ -982,6 +985,21 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
variant = determine_ssh_variant(ssh, 0);
}
if (variant == VARIANT_AUTO) {
struct child_process detect = CHILD_PROCESS_INIT;
detect.use_shell = conn->use_shell;
detect.no_stdin = detect.no_stdout = detect.no_stderr = 1;
argv_array_push(&detect.args, ssh);
argv_array_push(&detect.args, "-G");
push_ssh_options(&detect.args, &detect.env_array,
VARIANT_SSH, port, flags);
argv_array_push(&detect.args, ssh_host);
variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
}
argv_array_push(&conn->args, ssh);
push_ssh_options(&conn->args, &conn->env_array, variant, port, flags);
argv_array_push(&conn->args, ssh_host);

View File

@ -369,6 +369,12 @@ test_expect_success 'variant can be overriden' '
expect_ssh myhost src
'
test_expect_success 'variant=auto picks based on basename' '
copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
git -c ssh.variant=auto clone -4 "[myhost:123]:src" ssh-auto-clone &&
expect_ssh "-4 -P 123" 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 &&
@ -381,6 +387,21 @@ test_expect_success 'uplink is treated as simple' '
expect_ssh myhost src
'
test_expect_success 'OpenSSH-like uplink is treated as ssh' '
write_script "$TRASH_DIRECTORY/uplink" <<-EOF &&
if test "\$1" = "-G"
then
exit 0
fi &&
exec "\$TRASH_DIRECTORY/ssh$X" "\$@"
EOF
test_when_finished "rm -f \"\$TRASH_DIRECTORY/uplink\"" &&
GIT_SSH="$TRASH_DIRECTORY/uplink" &&
test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" &&
git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink &&
expect_ssh "-p 123" myhost src
'
test_expect_success 'plink is treated specially (as putty)' '
copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" &&
git clone "[myhost:123]:src" ssh-bracket-clone-plink-0 &&