|
@@ -1,6 +1,5 @@
|
|
|
-# client.py -- Implementation of the server side git protocols
|
|
|
+# client.py -- Implementation of the client side git protocols
|
|
|
# Copyright (C) 2008-2013 Jelmer Vernooij <jelmer@samba.org>
|
|
|
-# Copyright (C) 2008 John Carr
|
|
|
#
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
# modify it under the terms of the GNU General Public License
|
|
@@ -26,6 +25,7 @@ The Dulwich client supports the following capabilities:
|
|
|
* multi_ack
|
|
|
* side-band-64k
|
|
|
* ofs-delta
|
|
|
+ * quiet
|
|
|
* report-status
|
|
|
* delete-refs
|
|
|
|
|
@@ -62,10 +62,12 @@ from dulwich.errors import (
|
|
|
)
|
|
|
from dulwich.protocol import (
|
|
|
_RBUFSIZE,
|
|
|
+ capability_agent,
|
|
|
CAPABILITY_DELETE_REFS,
|
|
|
CAPABILITY_MULTI_ACK,
|
|
|
CAPABILITY_MULTI_ACK_DETAILED,
|
|
|
CAPABILITY_OFS_DELTA,
|
|
|
+ CAPABILITY_QUIET,
|
|
|
CAPABILITY_REPORT_STATUS,
|
|
|
CAPABILITY_SIDE_BAND_64K,
|
|
|
CAPABILITY_THIN_PACK,
|
|
@@ -94,6 +96,7 @@ def _fileno_can_read(fileno):
|
|
|
"""Check if a file descriptor is readable."""
|
|
|
return len(select.select([fileno], [], [], 0)[0]) > 0
|
|
|
|
|
|
+
|
|
|
COMMON_CAPABILITIES = [CAPABILITY_OFS_DELTA, CAPABILITY_SIDE_BAND_64K]
|
|
|
FETCH_CAPABILITIES = ([CAPABILITY_THIN_PACK, CAPABILITY_MULTI_ACK,
|
|
|
CAPABILITY_MULTI_ACK_DETAILED] +
|
|
@@ -184,7 +187,7 @@ class GitClient(object):
|
|
|
|
|
|
"""
|
|
|
|
|
|
- def __init__(self, thin_packs=True, report_activity=None):
|
|
|
+ def __init__(self, thin_packs=True, report_activity=None, quiet=False):
|
|
|
"""Create a new GitClient instance.
|
|
|
|
|
|
:param thin_packs: Whether or not thin packs should be retrieved
|
|
@@ -194,7 +197,11 @@ class GitClient(object):
|
|
|
self._report_activity = report_activity
|
|
|
self._report_status_parser = None
|
|
|
self._fetch_capabilities = set(FETCH_CAPABILITIES)
|
|
|
+ self._fetch_capabilities.add(capability_agent())
|
|
|
self._send_capabilities = set(SEND_CAPABILITIES)
|
|
|
+ self._send_capabilities.add(capability_agent())
|
|
|
+ if quiet:
|
|
|
+ self._send_capabilities.add(CAPABILITY_QUIET)
|
|
|
if not thin_packs:
|
|
|
self._fetch_capabilities.remove(CAPABILITY_THIN_PACK)
|
|
|
|
|
@@ -597,12 +604,12 @@ class TraditionalGitClient(GitClient):
|
|
|
class TCPGitClient(TraditionalGitClient):
|
|
|
"""A Git Client that works over TCP directly (i.e. git://)."""
|
|
|
|
|
|
- def __init__(self, host, port=None, *args, **kwargs):
|
|
|
+ def __init__(self, host, port=None, **kwargs):
|
|
|
if port is None:
|
|
|
port = TCP_GIT_PORT
|
|
|
self._host = host
|
|
|
self._port = port
|
|
|
- TraditionalGitClient.__init__(self, *args, **kwargs)
|
|
|
+ TraditionalGitClient.__init__(self, **kwargs)
|
|
|
|
|
|
def _connect(self, cmd, path):
|
|
|
if type(cmd) is not bytes:
|
|
@@ -690,13 +697,13 @@ def find_git_command():
|
|
|
class SubprocessGitClient(TraditionalGitClient):
|
|
|
"""Git client that talks to a server using a subprocess."""
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
+ def __init__(self, **kwargs):
|
|
|
self._connection = None
|
|
|
self._stderr = None
|
|
|
self._stderr = kwargs.get('stderr')
|
|
|
if 'stderr' in kwargs:
|
|
|
del kwargs['stderr']
|
|
|
- TraditionalGitClient.__init__(self, *args, **kwargs)
|
|
|
+ TraditionalGitClient.__init__(self, **kwargs)
|
|
|
|
|
|
git_command = None
|
|
|
|
|
@@ -705,7 +712,6 @@ class SubprocessGitClient(TraditionalGitClient):
|
|
|
raise TypeError(path)
|
|
|
if type(path) is not bytes:
|
|
|
raise TypeError(path)
|
|
|
- import subprocess
|
|
|
if self.git_command is None:
|
|
|
git_command = find_git_command()
|
|
|
argv = git_command + [service.decode('ascii'), path]
|
|
@@ -848,7 +854,6 @@ class SubprocessSSHVendor(SSHVendor):
|
|
|
not all([isinstance(b, bytes) for b in command])):
|
|
|
raise TypeError(command)
|
|
|
|
|
|
- import subprocess
|
|
|
#FIXME: This has no way to deal with passwords..
|
|
|
args = ['ssh', '-x']
|
|
|
if port is not None:
|
|
@@ -862,116 +867,13 @@ class SubprocessSSHVendor(SSHVendor):
|
|
|
return SubprocessWrapper(proc)
|
|
|
|
|
|
|
|
|
-try:
|
|
|
- import paramiko
|
|
|
-except ImportError:
|
|
|
- pass
|
|
|
-else:
|
|
|
- import threading
|
|
|
-
|
|
|
- class ParamikoWrapper(object):
|
|
|
- STDERR_READ_N = 2048 # 2k
|
|
|
-
|
|
|
- def __init__(self, client, channel, progress_stderr=None):
|
|
|
- self.client = client
|
|
|
- self.channel = channel
|
|
|
- self.progress_stderr = progress_stderr
|
|
|
- self.should_monitor = bool(progress_stderr) or True
|
|
|
- self.monitor_thread = None
|
|
|
- self.stderr = ''
|
|
|
-
|
|
|
- # Channel must block
|
|
|
- self.channel.setblocking(True)
|
|
|
-
|
|
|
- # Start
|
|
|
- if self.should_monitor:
|
|
|
- self.monitor_thread = threading.Thread(
|
|
|
- target=self.monitor_stderr)
|
|
|
- self.monitor_thread.start()
|
|
|
-
|
|
|
- def monitor_stderr(self):
|
|
|
- while self.should_monitor:
|
|
|
- # Block and read
|
|
|
- data = self.read_stderr(self.STDERR_READ_N)
|
|
|
-
|
|
|
- # Socket closed
|
|
|
- if not data:
|
|
|
- self.should_monitor = False
|
|
|
- break
|
|
|
-
|
|
|
- # Emit data
|
|
|
- if self.progress_stderr:
|
|
|
- self.progress_stderr(data)
|
|
|
-
|
|
|
- # Append to buffer
|
|
|
- self.stderr += data
|
|
|
-
|
|
|
- def stop_monitoring(self):
|
|
|
- # Stop StdErr thread
|
|
|
- if self.should_monitor:
|
|
|
- self.should_monitor = False
|
|
|
- self.monitor_thread.join()
|
|
|
-
|
|
|
- # Get left over data
|
|
|
- data = self.channel.in_stderr_buffer.empty()
|
|
|
- self.stderr += data
|
|
|
-
|
|
|
- def can_read(self):
|
|
|
- return self.channel.recv_ready()
|
|
|
-
|
|
|
- def write(self, data):
|
|
|
- return self.channel.sendall(data)
|
|
|
-
|
|
|
- def read_stderr(self, n):
|
|
|
- return self.channel.recv_stderr(n)
|
|
|
-
|
|
|
- def read(self, n=None):
|
|
|
- data = self.channel.recv(n)
|
|
|
- data_len = len(data)
|
|
|
-
|
|
|
- # Closed socket
|
|
|
- if not data:
|
|
|
- return
|
|
|
-
|
|
|
- # Read more if needed
|
|
|
- if n and data_len < n:
|
|
|
- diff_len = n - data_len
|
|
|
- return data + self.read(diff_len)
|
|
|
- return data
|
|
|
-
|
|
|
- def close(self):
|
|
|
- self.channel.close()
|
|
|
- self.stop_monitoring()
|
|
|
-
|
|
|
- class ParamikoSSHVendor(object):
|
|
|
-
|
|
|
- def __init__(self):
|
|
|
- self.ssh_kwargs = {}
|
|
|
-
|
|
|
- def run_command(self, host, command, username=None, port=None,
|
|
|
- progress_stderr=None):
|
|
|
- if (type(command) is not list or
|
|
|
- not all([isinstance(b, bytes) for b in command])):
|
|
|
- raise TypeError(command)
|
|
|
- # Paramiko needs an explicit port. None is not valid
|
|
|
- if port is None:
|
|
|
- port = 22
|
|
|
-
|
|
|
- client = paramiko.SSHClient()
|
|
|
-
|
|
|
- policy = paramiko.client.MissingHostKeyPolicy()
|
|
|
- client.set_missing_host_key_policy(policy)
|
|
|
- client.connect(host, username=username, port=port,
|
|
|
- **self.ssh_kwargs)
|
|
|
-
|
|
|
- # Open SSH session
|
|
|
- channel = client.get_transport().open_session()
|
|
|
-
|
|
|
- # Run commands
|
|
|
- channel.exec_command(subprocess.list2cmdline(command))
|
|
|
-
|
|
|
- return ParamikoWrapper(
|
|
|
- client, channel, progress_stderr=progress_stderr)
|
|
|
+def ParamikoSSHVendor(**kwargs):
|
|
|
+ import warnings
|
|
|
+ warnings.warn(
|
|
|
+ "ParamikoSSHVendor has been moved to dulwich.contrib.paramiko_vendor.",
|
|
|
+ DeprecationWarning)
|
|
|
+ from dulwich.contrib.paramiko_vendor import ParamikoSSHVendor
|
|
|
+ return ParamikoSSHVendor(**kwargs)
|
|
|
|
|
|
|
|
|
# Can be overridden by users
|
|
@@ -980,12 +882,16 @@ get_ssh_vendor = SubprocessSSHVendor
|
|
|
|
|
|
class SSHGitClient(TraditionalGitClient):
|
|
|
|
|
|
- def __init__(self, host, port=None, username=None, *args, **kwargs):
|
|
|
+ def __init__(self, host, port=None, username=None, vendor=None, **kwargs):
|
|
|
self.host = host
|
|
|
self.port = port
|
|
|
self.username = username
|
|
|
- TraditionalGitClient.__init__(self, *args, **kwargs)
|
|
|
+ TraditionalGitClient.__init__(self, **kwargs)
|
|
|
self.alternative_paths = {}
|
|
|
+ if vendor is not None:
|
|
|
+ self.ssh_vendor = vendor
|
|
|
+ else:
|
|
|
+ self.ssh_vendor = get_ssh_vendor()
|
|
|
|
|
|
def _get_cmd_path(self, cmd):
|
|
|
cmd = self.alternative_paths.get(cmd, b'git-' + cmd)
|
|
@@ -1004,7 +910,7 @@ class SSHGitClient(TraditionalGitClient):
|
|
|
if path.startswith(b"/~"):
|
|
|
path = path[1:]
|
|
|
argv = self._get_cmd_path(cmd) + [path]
|
|
|
- con = get_ssh_vendor().run_command(
|
|
|
+ con = self.ssh_vendor.run_command(
|
|
|
self.host, argv, port=self.port, username=self.username)
|
|
|
return (Protocol(con.read, con.write, con.close,
|
|
|
report_activity=self._report_activity),
|
|
@@ -1036,15 +942,14 @@ def default_urllib2_opener(config):
|
|
|
|
|
|
class HttpGitClient(GitClient):
|
|
|
|
|
|
- def __init__(self, base_url, dumb=None, opener=None, config=None, *args,
|
|
|
- **kwargs):
|
|
|
+ def __init__(self, base_url, dumb=None, opener=None, config=None, **kwargs):
|
|
|
self.base_url = base_url.rstrip("/") + "/"
|
|
|
self.dumb = dumb
|
|
|
if opener is None:
|
|
|
self.opener = default_urllib2_opener(config)
|
|
|
else:
|
|
|
self.opener = opener
|
|
|
- GitClient.__init__(self, *args, **kwargs)
|
|
|
+ GitClient.__init__(self, **kwargs)
|
|
|
|
|
|
def __repr__(self):
|
|
|
return "%s(%r, dumb=%r)" % (type(self).__name__, self.base_url, self.dumb)
|