|
24 | 24 | import tempfile
|
25 | 25 | import time
|
26 | 26 |
|
| 27 | +from git_command import git |
27 | 28 | import platform_utils
|
28 | 29 | from repo_trace import Trace
|
29 | 30 |
|
@@ -211,7 +212,33 @@ def _open_unlocked(self, host, port=None):
|
211 | 212 | # and print to the log there.
|
212 | 213 | pass
|
213 | 214 |
|
214 |
| - command = command_base[:1] + ["-M", "-N"] + command_base[1:] |
| 215 | + # Git protocol V2 is a new feature in git 2.18.0, made default in |
| 216 | + # git 2.26.0 |
| 217 | + # It is faster and more efficient than V1. |
| 218 | + # To enable it when using SSH, the environment variable GIT_PROTOCOL |
| 219 | + # must be set in the SSH side channel when establishing the connection |
| 220 | + # to the git server. |
| 221 | + # See https://git-scm.com/docs/protocol-v2#_ssh_and_file_transport |
| 222 | + # Normally git does this by itself. But here, where the SSH connection |
| 223 | + # is established manually over ControlMaster via the repo-tool, it must |
| 224 | + # be passed in explicitly instead. |
| 225 | + # Based on https://git-scm.com/docs/gitprotocol-pack#_extra_parameters, |
| 226 | + # GIT_PROTOCOL is considered an "Extra Parameter" and must be ignored |
| 227 | + # by servers that do not understand it. This means that it is safe to |
| 228 | + # set it even when connecting to older servers. |
| 229 | + # It should also be safe to set the environment variable for older |
| 230 | + # local git versions, since it is only part of the ssh side channel. |
| 231 | + git_protocol_version = _get_git_protocol_version() |
| 232 | + ssh_git_protocol_args = [ |
| 233 | + "-o", |
| 234 | + f"SetEnv GIT_PROTOCOL=version={git_protocol_version}", |
| 235 | + ] |
| 236 | + |
| 237 | + command = ( |
| 238 | + command_base[:1] |
| 239 | + + ["-M", "-N", *ssh_git_protocol_args] |
| 240 | + + command_base[1:] |
| 241 | + ) |
215 | 242 | p = None
|
216 | 243 | try:
|
217 | 244 | with Trace("Call to ssh: %s", " ".join(command)):
|
@@ -293,3 +320,32 @@ def sock(self, create=True):
|
293 | 320 | tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens
|
294 | 321 | )
|
295 | 322 | return self._sock_path
|
| 323 | + |
| 324 | + |
| 325 | +@functools.lru_cache(maxsize=1) |
| 326 | +def _get_git_protocol_version() -> str: |
| 327 | + """Return the git protocol version. |
| 328 | +
|
| 329 | + The version is found by first reading the global git config. |
| 330 | + If no git config for protocol version exists, try to deduce the default |
| 331 | + protocol version based on the git version. |
| 332 | +
|
| 333 | + See https://git-scm.com/docs/gitprotocol-v2 for details. |
| 334 | + """ |
| 335 | + try: |
| 336 | + return subprocess.check_output( |
| 337 | + ["git", "config", "--get", "--global", "protocol.version"], |
| 338 | + encoding="utf-8", |
| 339 | + stderr=subprocess.PIPE, |
| 340 | + ).strip() |
| 341 | + except subprocess.CalledProcessError as e: |
| 342 | + if e.returncode == 1: |
| 343 | + # Exit code 1 means that the git config key was not found. |
| 344 | + # Try to imitate the defaults that git would have used. |
| 345 | + git_version = git.version_tuple() |
| 346 | + if git_version >= (2, 26, 0): |
| 347 | + # Since git version 2.26, protocol v2 is the default. |
| 348 | + return "2" |
| 349 | + return "1" |
| 350 | + # Other exit codes indicate error with reading the config. |
| 351 | + raise |
0 commit comments