Skip to content

Commit

Permalink
make Git protocol v2 work over SSH connections
Browse files Browse the repository at this point in the history
  • Loading branch information
stspdotname committed Dec 19, 2023
1 parent 6cca700 commit 1711c75
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 3 deletions.
20 changes: 19 additions & 1 deletion dulwich/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* include-tag
"""

import copy
import logging
import os
import select
Expand Down Expand Up @@ -1794,6 +1795,7 @@ def run_command(
password=None,
key_filename=None,
ssh_command=None,
protocol_version: Optional[int] = None,
):
"""Connect to an SSH server.
Expand Down Expand Up @@ -1833,6 +1835,7 @@ def run_command(
password=None,
key_filename=None,
ssh_command=None,
protocol_version=None,
):
if password is not None:
raise NotImplementedError(
Expand All @@ -1852,6 +1855,9 @@ def run_command(
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}"
if host.startswith("-"):
Expand Down Expand Up @@ -1880,6 +1886,7 @@ def run_command(
password=None,
key_filename=None,
ssh_command=None,
protocol_version: Optional[int] = None,
):
if ssh_command:
import shlex
Expand Down Expand Up @@ -1911,12 +1918,22 @@ def run_command(
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],
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env
)
return SubprocessWrapper(proc)

Expand Down Expand Up @@ -2011,7 +2028,8 @@ def _connect(self, cmd, path, protocol_version=None):
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(
Expand Down
6 changes: 6 additions & 0 deletions dulwich/contrib/paramiko_vendor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
This implementation is experimental and does not have any tests.
"""

from typing import Optional

import paramiko
import paramiko.client

Expand Down Expand Up @@ -85,6 +87,7 @@ def run_command(
password=None,
pkey=None,
key_filename=None,
protocol_version: Optional[int] = None,
**kwargs,
):
client = paramiko.SSHClient()
Expand All @@ -110,6 +113,9 @@ def run_command(
# 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)

Expand Down
6 changes: 6 additions & 0 deletions dulwich/tests/compat/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,16 +429,22 @@ def run_command(
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)

Expand Down
8 changes: 6 additions & 2 deletions dulwich/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ def run_command(
password=None,
key_filename=None,
ssh_command=None,
protocol_version=None,
):
self.host = host
self.command = command
Expand All @@ -722,6 +723,7 @@ def run_command(
self.password = password
self.key_filename = key_filename
self.ssh_command = ssh_command
self.protocol_version = protocol_version

class Subprocess:
pass
Expand Down Expand Up @@ -1492,6 +1494,7 @@ def test_run_command_with_port_username_and_privkey(self):
"2200",
"-i",
"/tmp/id_rsa",
"-o", "SetEnv GIT_PROTOCOL=version=2",
"user@host",
"git-clone-url",
]
Expand All @@ -1515,6 +1518,7 @@ def test_run_with_ssh_command(self):
"-o",
"Option=Value",
"-x",
"-o", "SetEnv GIT_PROTOCOL=version=2",
"host",
"git-clone-url",
]
Expand Down Expand Up @@ -1657,12 +1661,12 @@ def test_run_command_with_port_username_and_privkey(self):
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",
Expand Down

0 comments on commit 1711c75

Please sign in to comment.