commit 2400933b32d3f966f3bcdbfa2dad3c4e9e712100 from: Stefan Sperling date: Tue Jun 25 08:54:01 2024 UTC make Git protocol v2 work over SSH connections commit - 325de7d3731d19a8290c08a540a09024b4b1c7fd commit + 2400933b32d3f966f3bcdbfa2dad3c4e9e712100 blob - 80b203c147d755d8dc92268c9b30b1954670e04f blob + d29341f13ea650841c8f93db9a5ba2e3262bb2ba --- dulwich/client.py +++ dulwich/client.py @@ -38,6 +38,7 @@ Known capabilities that are not supported: * include-tag """ +import copy import logging import os import select @@ -1847,6 +1848,7 @@ class SSHVendor: password=None, key_filename=None, ssh_command=None, + protocol_version: Optional[int] = None, ): """Connect to an SSH server. @@ -1886,6 +1888,7 @@ class SubprocessSSHVendor(SSHVendor): password=None, key_filename=None, ssh_command=None, + protocol_version=None, ): if password is not None: raise NotImplementedError( @@ -1904,6 +1907,9 @@ class SubprocessSSHVendor(SSHVendor): if key_filename: args.extend(["-i", str(key_filename)]) + + if protocol_version is None or protocol_version == 2: + args.extend(["-o", "SetEnv GIT_PROTOCOL=version=2"]) if username: host = f"{username}@{host}" @@ -1933,6 +1939,7 @@ class PLinkSSHVendor(SSHVendor): password=None, key_filename=None, ssh_command=None, + protocol_version: Optional[int] = None, ): if ssh_command: import shlex @@ -1963,6 +1970,15 @@ class PLinkSSHVendor(SSHVendor): if host.startswith("-"): raise StrangeHostname(hostname=host) args.append(host) + + # plink.exe does not provide a way to pass environment variables + # via the command line. The best we can do is set an environment + # variable and hope that plink will pass it to the server. If this + # does not work then the server should behave as if we had requested + # protocol version 0. + env = copy.deepcopy(os.environ) + if protocol_version is None or protocol_version == 2: + env["GIT_PROTOCOL"] = "version=2" proc = subprocess.Popen( [*args, command], @@ -1970,6 +1986,7 @@ class PLinkSSHVendor(SSHVendor): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=env, ) return SubprocessWrapper(proc) @@ -2064,7 +2081,12 @@ class SSHGitClient(TraditionalGitClient): if self.ssh_command is not None: kwargs["ssh_command"] = self.ssh_command con = self.ssh_vendor.run_command( - self.host, argv, port=self.port, username=self.username, **kwargs + self.host, + argv, + port=self.port, + username=self.username, + protocol_version=protocol_version, + **kwargs, ) return ( Protocol( blob - f3189a3c04aef30ef342851ab68cf441b1a6170b blob + 4d6da7f464136e8b4c2d476f062395a4343e837b --- dulwich/contrib/paramiko_vendor.py +++ dulwich/contrib/paramiko_vendor.py @@ -85,6 +85,7 @@ class ParamikoSSHVendor: password=None, pkey=None, key_filename=None, + protocol_version=None, **kwargs, ): client = paramiko.SSHClient() @@ -110,6 +111,9 @@ class ParamikoSSHVendor: # Open SSH session channel = client.get_transport().open_session() + if protocol_version is None or protocol_version == 2: + channel.set_environment_variable(name="GIT_PROTOCOL", value="version=2") + # Run commands channel.exec_command(command) blob - 7763d62967420378b4d71d935f47a3c781a01656 blob + f58683b13734e562019c2ede56f3d1029ba87b39 --- tests/compat/test_client.py +++ tests/compat/test_client.py @@ -429,16 +429,22 @@ class TestSSHVendor: port=None, password=None, key_filename=None, + protocol_version=None, ): cmd, path = command.split(" ") cmd = cmd.split("-", 1) path = path.replace("'", "") + env = copy.deepcopy(os.environ) + if protocol_version is None or protocol_version == 2: + env["GIT_PROTOCOL"] = "version=2" + p = subprocess.Popen( [*cmd, path], bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=env, ) return client.SubprocessWrapper(p) blob - 4c577545c96c8286d0eceac5624dfecf0d6fdf85 blob + 5bc1ada62218f301e6261516d1f0f1ed2c2c6a2a --- tests/test_client.py +++ tests/test_client.py @@ -714,6 +714,7 @@ class TestSSHVendor: password=None, key_filename=None, ssh_command=None, + protocol_version=None, ): self.host = host self.command = command @@ -722,6 +723,7 @@ class TestSSHVendor: self.password = password self.key_filename = key_filename self.ssh_command = ssh_command + self.protocol_version = protocol_version class Subprocess: pass @@ -1537,6 +1539,8 @@ class SubprocessSSHVendorTests(TestCase): "2200", "-i", "/tmp/id_rsa", + "-o", + "SetEnv GIT_PROTOCOL=version=2", "user@host", "git-clone-url", ] @@ -1560,6 +1564,8 @@ class SubprocessSSHVendorTests(TestCase): "-o", "Option=Value", "-x", + "-o", + "SetEnv GIT_PROTOCOL=version=2", "host", "git-clone-url", ] @@ -1702,12 +1708,12 @@ class PLinkSSHVendorTests(TestCase): def test_run_with_ssh_command(self): expected = [ "/path/to/plink", - "-x", + "-ssh", "host", "git-clone-url", ] - vendor = SubprocessSSHVendor() + vendor = PLinkSSHVendor() command = vendor.run_command( "host", "git-clone-url",