__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 urllib2
-import urlparse
+import sys
+
+try:
+ import urllib2
+ import urlparse
+except ImportError:
+ import urllib.request as urllib2
+ import urllib.parse as urlparse
from dulwich.errors import (
GitProtocolError,
)
from dulwich.protocol import (
_RBUFSIZE,
+ CAPABILITY_DELETE_REFS,
+ CAPABILITY_MULTI_ACK,
+ CAPABILITY_MULTI_ACK_DETAILED,
+ CAPABILITY_OFS_DELTA,
+ CAPABILITY_REPORT_STATUS,
+ CAPABILITY_SIDE_BAND_64K,
+ CAPABILITY_THIN_PACK,
+ COMMAND_DONE,
+ COMMAND_HAVE,
+ COMMAND_WANT,
+ SIDE_BAND_CHANNEL_DATA,
+ SIDE_BAND_CHANNEL_PROGRESS,
+ SIDE_BAND_CHANNEL_FATAL,
PktLineParser,
Protocol,
ProtocolFile,
"""Check if a file descriptor is readable."""
return len(select.select([fileno], [], [], 0)[0]) > 0
-COMMON_CAPABILITIES = ['ofs-delta', 'side-band-64k']
-FETCH_CAPABILITIES = (['thin-pack', 'multi_ack', 'multi_ack_detailed'] +
+COMMON_CAPABILITIES = [CAPABILITY_OFS_DELTA, CAPABILITY_SIDE_BAND_64K]
+FETCH_CAPABILITIES = ([CAPABILITY_THIN_PACK, CAPABILITY_MULTI_ACK,
+ CAPABILITY_MULTI_ACK_DETAILED] +
COMMON_CAPABILITIES)
-SEND_CAPABILITIES = ['report-status'] + COMMON_CAPABILITIES
+SEND_CAPABILITIES = [CAPABILITY_REPORT_STATUS] + COMMON_CAPABILITIES
class ReportStatusParser(object):
:raise SendPackError: Raised when the server could not unpack
:raise UpdateRefsError: Raised when refs could not be updated
"""
- if self._pack_status not in ('unpack ok', None):
+ if self._pack_status not in (b'unpack ok', None):
raise SendPackError(self._pack_status)
if not self._ref_status_ok:
ref_status = {}
ok = set()
for status in self._ref_statuses:
- if ' ' not in status:
+ if b' ' not in status:
# malformed response, move on to the next one
continue
- status, ref = status.split(' ', 1)
+ status, ref = status.split(b' ', 1)
- if status == 'ng':
- if ' ' in ref:
- ref, status = ref.split(' ', 1)
+ if status == b'ng':
+ if b' ' in ref:
+ ref, status = ref.split(b' ', 1)
else:
ok.add(ref)
ref_status[ref] = status
- raise UpdateRefsError('%s failed to update' %
- ', '.join([ref for ref in ref_status
- if ref not in ok]),
- 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.
else:
ref_status = pkt.strip()
self._ref_statuses.append(ref_status)
- if not ref_status.startswith('ok '):
+ if not ref_status.startswith(b'ok '):
self._ref_status_ok = False
refs = {}
# Receive refs from server
for pkt in proto.read_pkt_seq():
- (sha, ref) = pkt.rstrip('\n').split(None, 1)
- if sha == 'ERR':
+ (sha, ref) = pkt.rstrip(b'\n').split(None, 1)
+ if sha == b'ERR':
raise GitProtocolError(ref)
if server_capabilities is None:
(ref, server_capabilities) = extract_capabilities(ref)
self._fetch_capabilities = set(FETCH_CAPABILITIES)
self._send_capabilities = set(SEND_CAPABILITIES)
if not thin_packs:
- self._fetch_capabilities.remove('thin-pack')
+ self._fetch_capabilities.remove(CAPABILITY_THIN_PACK)
def send_pack(self, path, determine_wants, generate_pack_contents,
- progress=None):
+ progress=None, write_pack=write_pack_objects):
"""Upload a pack to a remote repository.
:param path: Repository path
:param generate_pack_contents: Function that can return a sequence of
the shas of the objects to upload.
:param progress: Optional progress function
+ :param write_pack: Function called with (file, iterable of objects) to
+ write the objects returned by generate_pack_contents to the server.
:raises SendPackError: if server rejects the pack data
:raises UpdateRefsError: if the server supports report-status
"""
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 != 'unpack ok':
+ if unpack != b'unpack ok':
st = True
# flush remaining error data
while st is not None:
while ref_status:
ref_status = ref_status.strip()
statuses.append(ref_status)
- if not ref_status.startswith('ok '):
+ if not ref_status.startswith(b'ok '):
errs = True
ref_status = proto.read_pkt_line()
ref_status = {}
ok = set()
for status in statuses:
- if ' ' not in status:
+ if b' ' not in status:
# malformed response, move on to the next one
continue
- status, ref = status.split(' ', 1)
+ status, ref = status.split(b' ', 1)
- if status == 'ng':
- if ' ' in ref:
- ref, status = ref.split(' ', 1)
+ if status == b'ng':
+ if b' ' in ref:
+ ref, status = ref.split(b' ', 1)
else:
ok.add(ref)
ref_status[ref] = status
- raise UpdateRefsError('%s failed to update' %
- ', '.join([ref for ref in ref_status
- if ref not in ok]),
+ raise UpdateRefsError(', '.join([ref for ref in ref_status
+ if ref not in ok]) +
+ b' failed to update',
ref_status=ref_status)
def _read_side_band64k_data(self, proto, channel_callbacks):
handlers to use. None for a callback discards channel data.
"""
for pkt in proto.read_pkt_seq():
- channel = ord(pkt[0])
+ channel = ord(pkt[:1])
pkt = pkt[1:]
try:
cb = channel_callbacks[channel]
have = [x for x in old_refs.values() if not x == ZERO_SHA]
sent_capabilities = False
- for refname in set(new_refs.keys() + old_refs.keys()):
+ 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 old_sha1 != new_sha1:
if sent_capabilities:
- proto.write_pkt_line('%s %s %s' % (
- old_sha1, new_sha1, refname))
+ proto.write_pkt_line(old_sha1 + b' ' + new_sha1 + b' ' + refname)
else:
proto.write_pkt_line(
- '%s %s %s\0%s' % (old_sha1, new_sha1, refname,
- ' '.join(capabilities)))
+ old_sha1 + b' ' + new_sha1 + b' ' + refname + b'\0' +
+ b' '.join(capabilities))
sent_capabilities = True
if new_sha1 not in have and new_sha1 != ZERO_SHA:
want.append(new_sha1)
:param capabilities: List of negotiated capabilities
:param progress: Optional progress reporting function
"""
- if "side-band-64k" in capabilities:
+ if b"side-band-64k" in capabilities:
if progress is None:
progress = lambda x: None
channel_callbacks = {2: progress}
- if 'report-status' in capabilities:
+ if CAPABILITY_REPORT_STATUS in capabilities:
channel_callbacks[1] = PktLineParser(
self._report_status_parser.handle_packet).parse
self._read_side_band64k_data(proto, channel_callbacks)
else:
- if 'report-status' in capabilities:
+ if CAPABILITY_REPORT_STATUS in capabilities:
for pkt in proto.read_pkt_seq():
self._report_status_parser.handle_packet(pkt)
if self._report_status_parser is not None:
:param can_read: function that returns a boolean that indicates
whether there is extra graph data to read on proto
"""
- assert isinstance(wants, list) and isinstance(wants[0], str)
- proto.write_pkt_line('want %s %s\n' % (
- wants[0], ' '.join(capabilities)))
+ assert isinstance(wants, list) and isinstance(wants[0], bytes)
+ proto.write_pkt_line(COMMAND_WANT + b' ' + wants[0] + b' ' + b' '.join(capabilities) + b'\n')
for want in wants[1:]:
- proto.write_pkt_line('want %s\n' % want)
+ proto.write_pkt_line(COMMAND_WANT + b' ' + want + b'\n')
proto.write_pkt_line(None)
have = next(graph_walker)
while have:
- proto.write_pkt_line('have %s\n' % have)
+ proto.write_pkt_line(COMMAND_HAVE + b' ' + have + b'\n')
if can_read():
pkt = proto.read_pkt_line()
- parts = pkt.rstrip('\n').split(' ')
- if parts[0] == 'ACK':
+ parts = pkt.rstrip(b'\n').split(b' ')
+ if parts[0] == b'ACK':
graph_walker.ack(parts[1])
- if parts[2] in ('continue', 'common'):
+ if parts[2] in (b'continue', b'common'):
pass
- elif parts[2] == 'ready':
+ elif parts[2] == b'ready':
break
else:
raise AssertionError(
"%s not in ('continue', 'ready', 'common)" %
parts[2])
have = next(graph_walker)
- proto.write_pkt_line('done\n')
+ proto.write_pkt_line(COMMAND_DONE + b'\n')
def _handle_upload_pack_tail(self, proto, capabilities, graph_walker,
pack_data, progress=None, rbufsize=_RBUFSIZE):
"""
pkt = proto.read_pkt_line()
while pkt:
- parts = pkt.rstrip('\n').split(' ')
- if parts[0] == 'ACK':
- graph_walker.ack(pkt.split(' ')[1])
+ parts = pkt.rstrip(b'\n').split(b' ')
+ if parts[0] == b'ACK':
+ graph_walker.ack(parts[1])
if len(parts) < 3 or parts[2] not in (
- 'ready', 'continue', 'common'):
+ b'ready', b'continue', b'common'):
break
pkt = proto.read_pkt_line()
- if "side-band-64k" in capabilities:
+ if CAPABILITY_SIDE_BAND_64K in capabilities:
if progress is None:
# Just ignore progress data
progress = lambda x: None
- self._read_side_band64k_data(proto, {1: pack_data, 2: progress})
+ self._read_side_band64k_data(proto, {
+ SIDE_BAND_CHANNEL_DATA: pack_data,
+ SIDE_BAND_CHANNEL_PROGRESS: progress}
+ )
else:
while True:
data = proto.read(rbufsize)
- if data == "":
+ if data == b"":
break
pack_data(data)
raise NotImplementedError()
def send_pack(self, path, determine_wants, generate_pack_contents,
- progress=None):
+ progress=None, write_pack=write_pack_objects):
"""Upload a pack to a remote repository.
:param path: Repository path
:param generate_pack_contents: Function that can return a sequence of
the shas of the objects to upload.
:param progress: Optional callback called with progress updates
+ :param write_pack: Function called with (file, iterable of objects) to
+ write the objects returned by generate_pack_contents to the server.
:raises SendPackError: if server rejects the pack data
:raises UpdateRefsError: if the server supports report-status
and rejects ref updates
"""
- proto, unused_can_read = self._connect('receive-pack', path)
+ proto, unused_can_read = self._connect(b'receive-pack', path)
with proto:
old_refs, server_capabilities = read_pkt_refs(proto)
negotiated_capabilities = self._send_capabilities & server_capabilities
- if 'report-status' in negotiated_capabilities:
+ if CAPABILITY_REPORT_STATUS in negotiated_capabilities:
self._report_status_parser = ReportStatusParser()
report_status_parser = self._report_status_parser
proto.write_pkt_line(None)
raise
- if not 'delete-refs' in server_capabilities:
+ if not CAPABILITY_DELETE_REFS in server_capabilities:
# Server does not support deletions. Fail later.
- def remove_del(pair):
- if pair[1] == ZERO_SHA:
- if 'report-status' in negotiated_capabilities:
+ new_refs = dict(orig_new_refs)
+ for ref, sha in orig_new_refs.items():
+ if sha == ZERO_SHA:
+ if CAPABILITY_REPORT_STATUS in negotiated_capabilities:
report_status_parser._ref_statuses.append(
- 'ng %s remote does not support deleting refs'
- % pair[1])
+ b'ng ' + sha + b' remote does not support deleting refs')
report_status_parser._ref_status_ok = False
- return False
- else:
- return True
-
- new_refs = dict(
- filter(
- remove_del,
- [(ref, sha) for ref, sha in new_refs.iteritems()]))
+ del new_refs[ref]
if new_refs is None:
proto.write_pkt_line(None)
if len(new_refs) == 0 and len(orig_new_refs):
# NOOP - Original new refs filtered out by policy
proto.write_pkt_line(None)
- if self._report_status_parser is not None:
- self._report_status_parser.check()
+ if report_status_parser is not None:
+ report_status_parser.check()
return old_refs
(have, want) = self._handle_receive_pack_head(
if not want and old_refs == new_refs:
return new_refs
objects = generate_pack_contents(have, want)
- if len(objects) > 0:
- entries, sha = write_pack_objects(proto.write_file(), objects)
- elif len(set(new_refs.values()) - set([ZERO_SHA])) > 0:
- # Check for valid create/update refs
- filtered_new_refs = \
- dict([(ref, sha) for ref, sha in new_refs.iteritems()
- if sha != ZERO_SHA])
- if len(set(filtered_new_refs.iteritems()) -
- set(old_refs.iteritems())) > 0:
- entries, sha = write_pack_objects(proto.write_file(), objects)
+
+ dowrite = len(objects) > 0
+ dowrite = dowrite or any(old_refs.get(ref) != sha
+ for (ref, sha) in new_refs.items()
+ if sha != ZERO_SHA)
+ if dowrite:
+ write_pack(proto.write_file(), objects)
self._handle_receive_pack_tail(
proto, negotiated_capabilities, progress)
:param pack_data: Callback called for each bit of data in the pack
:param progress: Callback for progress reports (strings)
"""
- proto, can_read = self._connect('upload-pack', path)
+ proto, can_read = self._connect(b'upload-pack', path)
with proto:
refs, server_capabilities = read_pkt_refs(proto)
negotiated_capabilities = (
proto, negotiated_capabilities, graph_walker, pack_data, progress)
return refs
- def archive(self, path, committish, write_data, progress=None):
+ 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)
with proto:
- proto.write_pkt_line("argument %s" % committish)
+ proto.write_pkt_line(b"argument " + committish)
proto.write_pkt_line(None)
pkt = proto.read_pkt_line()
- if pkt == "NACK\n":
+ if pkt == b"NACK\n":
return
- elif pkt == "ACK\n":
+ elif pkt == b"ACK\n":
pass
- elif pkt.startswith("ERR "):
- raise GitProtocolError(pkt[4:].rstrip("\n"))
+ elif pkt.startswith(b"ERR "):
+ raise GitProtocolError(pkt[4:].rstrip(b"\n"))
else:
raise AssertionError("invalid response %r" % pkt)
ret = proto.read_pkt_line()
if ret is not None:
raise AssertionError("expected pkt tail")
- self._read_side_band64k_data(proto, {1: write_data, 2: progress})
+ self._read_side_band64k_data(proto, {
+ SIDE_BAND_CHANNEL_DATA: write_data,
+ SIDE_BAND_CHANNEL_PROGRESS: progress,
+ SIDE_BAND_CHANNEL_FATAL: write_error})
class TCPGitClient(TraditionalGitClient):
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
proto = Protocol(rfile.read, wfile.write, close,
report_activity=self._report_activity)
- if path.startswith("/~"):
+ if path.startswith(b"/~"):
path = path[1:]
- proto.send_cmd('git-%s' % cmd, path, 'host=%s' % 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):
from msvcrt import get_osfhandle
from win32pipe import PeekNamedPipe
handle = get_osfhandle(self.proc.stdout.fileno())
- return PeekNamedPipe(handle, 0)[2] != 0
+ data, total_bytes_avail, msg_bytes_left = PeekNamedPipe(handle, 0)
+ return total_bytes_avail != 0
else:
return _fileno_can_read(self.proc.stdout.fileno())
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,
# Ignore the thin_packs argument
def send_pack(self, path, determine_wants, generate_pack_contents,
- progress=None):
+ progress=None, write_pack=write_pack_objects):
"""Upload a pack to a remote repository.
:param path: Repository path
:param generate_pack_contents: Function that can return a sequence of
the shas of the objects to upload.
:param progress: Optional progress function
+ :param write_pack: Function called with (file, iterable of objects) to
+ write the objects returned by generate_pack_contents to the server.
:raises SendPackError: if server rejects the pack data
:raises UpdateRefsError: if the server supports report-status
and rejects ref updates
"""
- raise NotImplementedError(self.send_pack)
+ from dulwich.repo import Repo
+
+ 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)
+
+ if not want and old_refs == new_refs:
+ return new_refs
+
+ target.object_store.add_objects(generate_pack_contents(have, want))
+
+ for name, sha in new_refs.items():
+ target.refs[name] = sha
+
+ return new_refs
def fetch(self, path, target, determine_wants=None, progress=None):
"""Fetch into a target repository.
: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, 'git-%s' % 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 path.startswith("/~"):
+ 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, ["%s '%s'" % (self._get_cmd_path(cmd), path)],
- port=self.port, username=self.username)
- return (Protocol(con.read, con.write, con.close,
- report_activity=self._report_activity),
+ 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("/") + "/"
return resp
def send_pack(self, path, determine_wants, generate_pack_contents,
- progress=None):
+ progress=None, write_pack=write_pack_objects):
"""Upload a pack to a remote repository.
:param path: Repository path
:param generate_pack_contents: Function that can return a sequence of
the shas of the objects to upload.
:param progress: Optional progress function
+ :param write_pack: Function called with (file, iterable of objects) to
+ write the objects returned by generate_pack_contents to the server.
:raises SendPackError: if server rejects the pack data
:raises UpdateRefsError: if the server supports report-status
"""
url = self._get_url(path)
old_refs, server_capabilities = self._discover_references(
- "git-receive-pack", url)
+ b"git-receive-pack", url)
negotiated_capabilities = self._send_capabilities & server_capabilities
- if 'report-status' in negotiated_capabilities:
+ if CAPABILITY_REPORT_STATUS in negotiated_capabilities:
self._report_status_parser = ReportStatusParser()
new_refs = determine_wants(dict(old_refs))
return new_refs
objects = generate_pack_contents(have, want)
if len(objects) > 0:
- entries, sha = write_pack_objects(req_proto.write_file(), objects)
- resp = self._smart_request("git-receive-pack", url,
+ write_pack(req_proto.write_file(), objects)
+ resp = self._smart_request(b"git-receive-pack", url,
data=req_data.getvalue())
try:
resp_proto = Protocol(resp.read, None)
"""
url = self._get_url(path)
refs, server_capabilities = self._discover_references(
- "git-upload-pack", url)
+ b"git-upload-pack", url)
negotiated_capabilities = self._fetch_capabilities & server_capabilities
wants = determine_wants(refs)
if wants is not None:
req_proto, negotiated_capabilities, graph_walker, wants,
lambda: False)
resp = self._smart_request(
- "git-upload-pack", url, data=req_data.getvalue())
+ b"git-upload-pack", url, data=req_data.getvalue())
try:
resp_proto = Protocol(resp.read, None)
self._handle_upload_pack_tail(resp_proto, negotiated_capabilities,
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.
except ValueError:
pass
+ if (sys.platform == 'win32' and
+ location[0].isalpha() and location[1:3] == ':\\'):
+ # Windows local path
+ return default_local_git_client_cls(**kwargs), location
+
if ':' in location and not '@' in location:
# SSH with no user@, zero or one leading slash.
(hostname, path) = location.split(':')