__docformat__ = 'restructuredText'
-from io import BytesIO
+from contextlib import closing
+from io import BytesIO, BufferedReader
import dulwich
import select
+import shlex
import socket
import subprocess
import sys
else:
ok.add(ref)
ref_status[ref] = status
- raise UpdateRefsError(b', '.join([ref for ref in ref_status
- if ref not in ok]) +
- b' failed to update',
- ref_status=ref_status)
+ # TODO(jelmer): don't assume encoding of refs is ascii.
+ raise UpdateRefsError(', '.join([
+ ref.decode('ascii') for ref in ref_status if ref not in ok]) +
+ ' failed to update', ref_status=ref_status)
def handle_packet(self, pkt):
"""Handle a packet.
"""
if determine_wants is None:
determine_wants = target.object_store.determine_wants_all
+
f, commit, abort = target.object_store.add_pack()
try:
result = self.fetch_pack(
"""
raise NotImplementedError(self.fetch_pack)
+ def get_refs(self, path):
+ """Retrieve the current refs from a git smart server.
+
+ :param path: Path to the repo to fetch from.
+ """
+ raise NotImplementedError(self.get_refs)
+
def _parse_status_report(self, proto):
unpack = proto.read_pkt_line().strip()
if unpack != b'unpack ok':
else:
ok.add(ref)
ref_status[ref] = status
- raise UpdateRefsError(b', '.join([ref for ref in ref_status
+ raise UpdateRefsError(', '.join([ref for ref in ref_status
if ref not in ok]) +
b' failed to update',
ref_status=ref_status)
proto, negotiated_capabilities, graph_walker, pack_data, progress)
return refs
+ def get_refs(self, path):
+ """Retrieve the current refs from a git smart server."""
+ # stock `git ls-remote` uses upload-pack
+ proto, _ = self._connect(b'upload-pack', path)
+ with proto:
+ refs, _ = read_pkt_refs(proto)
+ return refs
+
def archive(self, path, committish, write_data, progress=None,
write_error=None):
proto, can_read = self._connect(b'upload-archive', path)
TraditionalGitClient.__init__(self, *args, **kwargs)
def _connect(self, cmd, path):
+ if type(cmd) is not bytes:
+ raise TypeError(path)
+ if type(path) is not bytes:
+ raise TypeError(path)
sockaddrs = socket.getaddrinfo(
self._host, self._port, socket.AF_UNSPEC, socket.SOCK_STREAM)
s = None
report_activity=self._report_activity)
if path.startswith(b"/~"):
path = path[1:]
- proto.send_cmd(b'git-' + cmd, path, b'host=' + self._host)
+ # TODO(jelmer): Alternative to ascii?
+ proto.send_cmd(b'git-' + cmd, path, b'host=' + self._host.encode('ascii'))
return proto, lambda: _fileno_can_read(s)
def __init__(self, proc):
self.proc = proc
- self.read = proc.stdout.read
+ if sys.version_info[0] == 2:
+ self.read = proc.stdout.read
+ else:
+ self.read = BufferedReader(proc.stdout).read
self.write = proc.stdin.write
def can_read(self):
self.proc.wait()
+def find_git_command():
+ """Find command to run for system Git (usually C Git).
+ """
+ if sys.platform == 'win32': # support .exe, .bat and .cmd
+ try: # to avoid overhead
+ import win32api
+ except ImportError: # run through cmd.exe with some overhead
+ return ['cmd', '/c', 'git']
+ else:
+ status, git = win32api.FindExecutable('git')
+ return [git]
+ else:
+ return ['git']
+
+
class SubprocessGitClient(TraditionalGitClient):
"""Git client that talks to a server using a subprocess."""
del kwargs['stderr']
TraditionalGitClient.__init__(self, *args, **kwargs)
+ git_command = None
+
def _connect(self, service, path):
+ if type(service) is not bytes:
+ raise TypeError(path)
+ if type(path) is not bytes:
+ raise TypeError(path)
import subprocess
- argv = ['git', service, path]
+ if self.git_command is None:
+ git_command = find_git_command()
+ argv = git_command + [service.decode('ascii'), path]
p = SubprocessWrapper(
subprocess.Popen(argv, bufsize=0, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
"""
from dulwich.repo import Repo
- target = Repo(path)
- old_refs = target.get_refs()
- new_refs = determine_wants(old_refs)
+ with closing(Repo(path)) as target:
+ old_refs = target.get_refs()
+ new_refs = determine_wants(dict(old_refs))
- have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
- want = []
- all_refs = set(new_refs.keys()).union(set(old_refs.keys()))
- for refname in all_refs:
- old_sha1 = old_refs.get(refname, ZERO_SHA)
- new_sha1 = new_refs.get(refname, ZERO_SHA)
- if new_sha1 not in have and new_sha1 != ZERO_SHA:
- want.append(new_sha1)
+ have = [sha1 for sha1 in old_refs.values() if sha1 != ZERO_SHA]
+ want = []
+ all_refs = set(new_refs.keys()).union(set(old_refs.keys()))
+ for refname in all_refs:
+ old_sha1 = old_refs.get(refname, ZERO_SHA)
+ new_sha1 = new_refs.get(refname, ZERO_SHA)
+ if new_sha1 not in have and new_sha1 != ZERO_SHA:
+ want.append(new_sha1)
- if not want and old_refs == new_refs:
- return new_refs
+ if not want and old_refs == new_refs:
+ return new_refs
- target.object_store.add_objects(generate_pack_contents(have, want))
+ target.object_store.add_objects(generate_pack_contents(have, want))
- for name, sha in new_refs.items():
- target.refs[name] = sha
+ for name, sha in new_refs.items():
+ target.refs[name] = sha
return new_refs
:return: remote refs as dictionary
"""
from dulwich.repo import Repo
- r = Repo(path)
- return r.fetch(target, determine_wants=determine_wants,
- progress=progress)
+ with closing(Repo(path)) as r:
+ return r.fetch(target, determine_wants=determine_wants,
+ progress=progress)
def fetch_pack(self, path, determine_wants, graph_walker, pack_data,
progress=None):
:param progress: Callback for progress reports (strings)
"""
from dulwich.repo import Repo
- r = Repo(path)
- objects_iter = r.fetch_objects(determine_wants, graph_walker, progress)
+ with closing(Repo(path)) as r:
+ objects_iter = r.fetch_objects(determine_wants, graph_walker, progress)
- # Did the process short-circuit (e.g. in a stateless RPC call)? Note
- # that the client still expects a 0-object pack in most cases.
- if objects_iter is None:
- return
- write_pack_objects(ProtocolFile(None, pack_data), objects_iter)
+ # Did the process short-circuit (e.g. in a stateless RPC call)? Note
+ # that the client still expects a 0-object pack in most cases.
+ if objects_iter is None:
+ return
+ write_pack_objects(ProtocolFile(None, pack_data), objects_iter)
+
+ def get_refs(self, path):
+ """Retrieve the current refs from a git smart server."""
+ from dulwich.repo import Repo
+
+ with closing(Repo(path)) as target:
+ return target.get_refs()
# What Git client to use for local access
-default_local_git_client_cls = SubprocessGitClient
+default_local_git_client_cls = LocalGitClient
class SSHVendor(object):
with the remote command.
:param host: Host name
- :param command: Command to run
+ :param command: Command to run (as argv array)
:param username: Optional ame of user to log in as
:param port: Optional SSH port to use
"""
"""SSH vendor that shells out to the local 'ssh' command."""
def run_command(self, host, command, username=None, port=None):
+ if (type(command) is not list or
+ 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']
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
channel = client.get_transport().open_session()
# Run commands
- channel.exec_command(*command)
+ channel.exec_command(subprocess.list2cmdline(command))
return ParamikoWrapper(
client, channel, progress_stderr=progress_stderr)
self.alternative_paths = {}
def _get_cmd_path(self, cmd):
- return self.alternative_paths.get(cmd, b'git-' + cmd)
+ cmd = self.alternative_paths.get(cmd, b'git-' + cmd)
+ assert isinstance(cmd, bytes)
+ if sys.version_info[:2] <= (2, 6):
+ return shlex.split(cmd)
+ else:
+ # TODO(jelmer): Don't decode/encode here
+ return [x.encode('ascii') for x in shlex.split(cmd.decode('ascii'))]
def _connect(self, cmd, path):
+ if type(cmd) is not bytes:
+ raise TypeError(path)
+ if type(path) is not bytes:
+ raise TypeError(path)
if path.startswith(b"/~"):
path = path[1:]
+ argv = self._get_cmd_path(cmd) + [path]
con = get_ssh_vendor().run_command(
- self.host, [self._get_cmd_path(cmd) + b" '" + path + b"'"],
- port=self.port, username=self.username)
+ self.host, argv, port=self.port, username=self.username)
return (Protocol(con.read, con.write, con.close,
report_activity=self._report_activity),
con.can_read)
self.opener = opener
GitClient.__init__(self, *args, **kwargs)
+ def __repr__(self):
+ return "%s(%r, dumb=%r)" % (type(self).__name__, self.base_url, self.dumb)
+
def _get_url(self, path):
return urlparse.urljoin(self.base_url, path).rstrip("/") + "/"
finally:
resp.close()
+ def get_refs(self, path):
+ """Retrieve the current refs from a git smart server."""
+ url = self._get_url(path)
+ refs, _ = self._discover_references(
+ b"git-upload-pack", url)
+ return refs
+
def get_transport_and_path_from_url(url, config=None, **kwargs):
"""Obtain a git client from a URL.