From eede374e3ec446d5f03c12a886efcb2d8f946917 Mon Sep 17 00:00:00 2001 From: Erik Elmeke Date: Thu, 29 Feb 2024 17:33:51 +0100 Subject: [PATCH] ssh: Set git protocol version 2 on SSH ControlMaster According to https://git-scm.com/docs/protocol-v2#_ssh_and_file_transport, when using SSH, the environment variable GIT_PROTOCOL must be set when establishing the connection to the git server. Normally git does this by itself. But in repo-tool where the SSH connection is managed by the repo-tool, it must be passed in explicitly instead. Under some circumstances of environment configuration, this caused all repo sync commands over ssh to always use git protocol version 1. Even when git was configured to use version 2. Using git protocol v2 can significantly improve fetch speeds, since it uses server side filtering of refs, reducing the amount of unneccessary objects to send. Change-Id: I6d4c3b7300a6090d707480b1a638ed03622fa71a Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/411362 Tested-by: Erik Elmeke Reviewed-by: Mike Frysinger Commit-Queue: Erik Elmeke --- ssh.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/ssh.py b/ssh.py index 54a77304..ffa0d6c0 100644 --- a/ssh.py +++ b/ssh.py @@ -24,6 +24,7 @@ import sys import tempfile import time +from git_command import git import platform_utils from repo_trace import Trace @@ -211,7 +212,33 @@ class ProxyManager: # and print to the log there. pass - command = command_base[:1] + ["-M", "-N"] + command_base[1:] + # Git protocol V2 is a new feature in git 2.18.0, made default in + # git 2.26.0 + # It is faster and more efficient than V1. + # To enable it when using SSH, the environment variable GIT_PROTOCOL + # must be set in the SSH side channel when establishing the connection + # to the git server. + # See https://git-scm.com/docs/protocol-v2#_ssh_and_file_transport + # Normally git does this by itself. But here, where the SSH connection + # is established manually over ControlMaster via the repo-tool, it must + # be passed in explicitly instead. + # Based on https://git-scm.com/docs/gitprotocol-pack#_extra_parameters, + # GIT_PROTOCOL is considered an "Extra Parameter" and must be ignored + # by servers that do not understand it. This means that it is safe to + # set it even when connecting to older servers. + # It should also be safe to set the environment variable for older + # local git versions, since it is only part of the ssh side channel. + git_protocol_version = _get_git_protocol_version() + ssh_git_protocol_args = [ + "-o", + f"SetEnv GIT_PROTOCOL=version={git_protocol_version}", + ] + + command = ( + command_base[:1] + + ["-M", "-N", *ssh_git_protocol_args] + + command_base[1:] + ) p = None try: with Trace("Call to ssh: %s", " ".join(command)): @@ -293,3 +320,32 @@ class ProxyManager: tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens ) return self._sock_path + + +@functools.lru_cache(maxsize=1) +def _get_git_protocol_version() -> str: + """Return the git protocol version. + + The version is found by first reading the global git config. + If no git config for protocol version exists, try to deduce the default + protocol version based on the git version. + + See https://git-scm.com/docs/gitprotocol-v2 for details. + """ + try: + return subprocess.check_output( + ["git", "config", "--get", "--global", "protocol.version"], + encoding="utf-8", + stderr=subprocess.PIPE, + ).strip() + except subprocess.CalledProcessError as e: + if e.returncode == 1: + # Exit code 1 means that the git config key was not found. + # Try to imitate the defaults that git would have used. + git_version = git.version_tuple() + if git_version >= (2, 26, 0): + # Since git version 2.26, protocol v2 is the default. + return "2" + return "1" + # Other exit codes indicate error with reading the config. + raise