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 <erik@haleytek.corp-partner.google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Commit-Queue: Erik Elmeke <erik@haleytek.corp-partner.google.com>
This commit is contained in:
Erik Elmeke 2024-02-29 17:33:51 +01:00 committed by LUCI
parent 2c5fb84d35
commit eede374e3e

58
ssh.py
View File

@ -24,6 +24,7 @@ import sys
import tempfile import tempfile
import time import time
from git_command import git
import platform_utils import platform_utils
from repo_trace import Trace from repo_trace import Trace
@ -211,7 +212,33 @@ class ProxyManager:
# and print to the log there. # and print to the log there.
pass 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 p = None
try: try:
with Trace("Call to ssh: %s", " ".join(command)): with Trace("Call to ssh: %s", " ".join(command)):
@ -293,3 +320,32 @@ class ProxyManager:
tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens
) )
return self._sock_path 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