def send_pack(self, path, determine_wants, generate_pack_contents, progress=None): """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 :raises SendPackError: if server rejects the pack data :raises UpdateRefsError: if the server supports report-status and rejects ref updates """ url = self._get_url(path) old_refs, server_capabilities = self._discover_references("git-receive-pack", url) negotiated_capabilities = self._send_capabilities & server_capabilities new_refs = determine_wants(dict(old_refs)) if new_refs is None: return old_refs if self.dumb: raise NotImplementedError(self.fetch_pack) req_data = StringIO() req_proto = Protocol(None, req_data.write) (have, want) = self._handle_receive_pack_head(req_proto, negotiated_capabilities, old_refs, new_refs) 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(req_proto.write_file(), objects) resp = self._smart_request("git-receive-pack", url, data=req_data.getvalue()) resp_proto = Protocol(resp.read, None) self._handle_receive_pack_tail(resp_proto, negotiated_capabilities, progress) return new_refs
def send_pack(self, path, determine_wants, generate_pack_contents, progress=None, write_pack=write_pack_objects): """Upload a pack to a remote repository. :param path: Repository path (as bytestring) :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 :return: new_refs dictionary containing the changes that were made {refname: new_ref}, including deleted refs. """ url = self._get_url(path) old_refs, server_capabilities = self._discover_references( b"git-receive-pack", url) negotiated_capabilities = self._send_capabilities & server_capabilities if CAPABILITY_REPORT_STATUS in negotiated_capabilities: self._report_status_parser = ReportStatusParser() new_refs = determine_wants(dict(old_refs)) if new_refs is None: # Determine wants function is aborting the push. return old_refs if self.dumb: raise NotImplementedError(self.fetch_pack) req_data = BytesIO() req_proto = Protocol(None, req_data.write) (have, want) = self._handle_receive_pack_head(req_proto, negotiated_capabilities, old_refs, new_refs) if not want and set(new_refs.items()).issubset(set(old_refs.items())): return new_refs objects = generate_pack_contents(have, want) if len(objects) > 0: write_pack(req_proto.write_file(), objects) resp = self._smart_request("git-receive-pack", url, data=req_data.getvalue()) try: resp_proto = Protocol(resp.read, None) self._handle_receive_pack_tail(resp_proto, negotiated_capabilities, progress) return new_refs finally: resp.close()
def send_pack(self, path, determine_wants, generate_pack_contents, progress=None, write_pack=write_pack_objects): """Upload a pack to a remote repository. :param path: Repository path (as bytestring) :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 :return: new_refs dictionary containing the changes that were made {refname: new_ref}, including deleted refs. """ url = self._get_url(path) old_refs, server_capabilities = self._discover_references( b"git-receive-pack", url) negotiated_capabilities = self._send_capabilities & server_capabilities if CAPABILITY_REPORT_STATUS in negotiated_capabilities: self._report_status_parser = ReportStatusParser() new_refs = determine_wants(dict(old_refs)) if new_refs is None: # Determine wants function is aborting the push. return old_refs if self.dumb: raise NotImplementedError(self.fetch_pack) req_data = BytesIO() req_proto = Protocol(None, req_data.write) (have, want) = self._handle_receive_pack_head( req_proto, negotiated_capabilities, old_refs, new_refs) if not want and set(new_refs.items()).issubset(set(old_refs.items())): return new_refs objects = generate_pack_contents(have, want) if len(objects) > 0: write_pack(req_proto.write_file(), objects) resp = self._smart_request("git-receive-pack", url, data=req_data.getvalue()) try: resp_proto = Protocol(resp.read, None) self._handle_receive_pack_tail(resp_proto, negotiated_capabilities, progress) return new_refs finally: resp.close()
def send_pack(self, path, determine_wants, generate_pack_contents, progress=None): """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 :raises SendPackError: if server rejects the pack data :raises UpdateRefsError: if the server supports report-status and rejects ref updates """ url = self._get_url(path) old_refs, server_capabilities = self._discover_references( "git-receive-pack", url) negotiated_capabilities = self._send_capabilities & server_capabilities if 'report-status' in negotiated_capabilities: self._report_status_parser = ReportStatusParser() new_refs = determine_wants(dict(old_refs)) if new_refs is None: return old_refs if self.dumb: raise NotImplementedError(self.fetch_pack) req_data = BytesIO() req_proto = Protocol(None, req_data.write) (have, want) = self._handle_receive_pack_head(req_proto, negotiated_capabilities, old_refs, new_refs) 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(req_proto.write_file(), objects) resp = self._smart_request("git-receive-pack", url, data=req_data.getvalue()) try: resp_proto = Protocol(resp.read, None) self._handle_receive_pack_tail(resp_proto, negotiated_capabilities, progress) return new_refs finally: resp.close()
class GitClient(object): """Git smart server client. """ def __init__(self, can_read, read, write, thin_packs=True, report_activity=None): """Create a new GitClient instance. :param can_read: Function that returns True if there is data available to be read. :param read: Callback for reading data, takes number of bytes to read :param write: Callback for writing data :param thin_packs: Whether or not thin packs should be retrieved :param report_activity: Optional callback for reporting transport activity. """ self.proto = Protocol(read, write, report_activity) self._can_read = can_read self._capabilities = list(CAPABILITIES) if thin_packs: self._capabilities.append("thin-pack") def capabilities(self): return " ".join(self._capabilities) def read_refs(self): server_capabilities = None refs = {} # Receive refs from server for pkt in self.proto.read_pkt_seq(): (sha, ref) = pkt.rstrip("\n").split(" ", 1) if server_capabilities is None: (ref, server_capabilities) = extract_capabilities(ref) refs[ref] = sha return refs, server_capabilities def send_pack(self, path, determine_wants, generate_pack_contents): """Upload a pack to a remote repository. :param path: Repository path :param generate_pack_contents: Function that can return the shas of the objects to upload. """ old_refs, server_capabilities = self.read_refs() new_refs = determine_wants(old_refs) if not new_refs: self.proto.write_pkt_line(None) return {} want = [] have = [x for x in old_refs.values() if not x == "0" * 40] sent_capabilities = False for refname in set(new_refs.keys() + old_refs.keys()): old_sha1 = old_refs.get(refname, "0" * 40) new_sha1 = new_refs.get(refname, "0" * 40) if old_sha1 != new_sha1: if sent_capabilities: self.proto.write_pkt_line("%s %s %s" % (old_sha1, new_sha1, refname)) else: self.proto.write_pkt_line("%s %s %s\0%s" % (old_sha1, new_sha1, refname, self.capabilities())) sent_capabilities = True if not new_sha1 in (have, "0" * 40): want.append(new_sha1) self.proto.write_pkt_line(None) if not want: return new_refs objects = generate_pack_contents(have, want) (entries, sha) = write_pack_data(self.proto.write_file(), objects, len(objects)) # read the final confirmation sha client_sha = self.proto.read(20) if not client_sha in (None, "", sha): raise ChecksumMismatch(sha, client_sha) return new_refs def fetch_pack(self, path, determine_wants, graph_walker, pack_data, progress): """Retrieve a pack from a git smart server. :param determine_wants: Callback that returns list of commits to fetch :param graph_walker: Object with next() and ack(). :param pack_data: Callback called for each bit of data in the pack :param progress: Callback for progress reports (strings) """ (refs, server_capabilities) = self.read_refs() wants = determine_wants(refs) if not wants: self.proto.write_pkt_line(None) return refs assert isinstance(wants, list) and type(wants[0]) == str self.proto.write_pkt_line("want %s %s\n" % (wants[0], self.capabilities())) for want in wants[1:]: self.proto.write_pkt_line("want %s\n" % want) self.proto.write_pkt_line(None) have = graph_walker.next() while have: self.proto.write_pkt_line("have %s\n" % have) if self._can_read(): pkt = self.proto.read_pkt_line() parts = pkt.rstrip("\n").split(" ") if parts[0] == "ACK": graph_walker.ack(parts[1]) assert parts[2] == "continue" have = graph_walker.next() self.proto.write_pkt_line("done\n") pkt = self.proto.read_pkt_line() while pkt: parts = pkt.rstrip("\n").split(" ") if parts[0] == "ACK": graph_walker.ack(pkt.split(" ")[1]) if len(parts) < 3 or parts[2] != "continue": break pkt = self.proto.read_pkt_line() for pkt in self.proto.read_pkt_seq(): channel = ord(pkt[0]) pkt = pkt[1:] if channel == 1: pack_data(pkt) elif channel == 2: progress(pkt) else: raise AssertionError("Invalid sideband channel %d" % channel) return refs
class GitClient(object): """Git smart server client. """ def __init__(self, can_read, read, write, thin_packs=True, report_activity=None): """Create a new GitClient instance. :param can_read: Function that returns True if there is data available to be read. :param read: Callback for reading data, takes number of bytes to read :param write: Callback for writing data :param thin_packs: Whether or not thin packs should be retrieved :param report_activity: Optional callback for reporting transport activity. """ self.proto = Protocol(read, write, report_activity) self._can_read = can_read self._capabilities = list(CAPABILITIES) if thin_packs: self._capabilities.append("thin-pack") def capabilities(self): return " ".join(self._capabilities) def read_refs(self): server_capabilities = None refs = {} # Receive refs from server for pkt in self.proto.read_pkt_seq(): (sha, ref) = pkt.rstrip("\n").split(" ", 1) if server_capabilities is None: (ref, server_capabilities) = extract_capabilities(ref) refs[ref] = sha return refs, server_capabilities def send_pack(self, path, determine_wants, generate_pack_contents): """Upload a pack to a remote repository. :param path: Repository path :param generate_pack_contents: Function that can return the shas of the objects to upload. """ old_refs, server_capabilities = self.read_refs() new_refs = determine_wants(old_refs) if not new_refs: self.proto.write_pkt_line(None) return {} want = [] have = [x for x in old_refs.values() if not x == "0" * 40] sent_capabilities = False for refname in set(new_refs.keys() + old_refs.keys()): old_sha1 = old_refs.get(refname, "0" * 40) new_sha1 = new_refs.get(refname, "0" * 40) if old_sha1 != new_sha1: if sent_capabilities: self.proto.write_pkt_line("%s %s %s" % (old_sha1, new_sha1, refname)) else: self.proto.write_pkt_line( "%s %s %s\0%s" % (old_sha1, new_sha1, refname, self.capabilities())) sent_capabilities = True if not new_sha1 in (have, "0" * 40): want.append(new_sha1) self.proto.write_pkt_line(None) if not want: return new_refs objects = generate_pack_contents(have, want) (entries, sha) = write_pack_data(self.proto.write_file(), objects, len(objects)) # read the final confirmation sha client_sha = self.proto.read(20) if not client_sha in (None, "", sha): raise ChecksumMismatch(sha, client_sha) return new_refs def fetch_pack(self, path, determine_wants, graph_walker, pack_data, progress): """Retrieve a pack from a git smart server. :param determine_wants: Callback that returns list of commits to fetch :param graph_walker: Object with next() and ack(). :param pack_data: Callback called for each bit of data in the pack :param progress: Callback for progress reports (strings) """ (refs, server_capabilities) = self.read_refs() wants = determine_wants(refs) if not wants: self.proto.write_pkt_line(None) return refs assert isinstance(wants, list) and type(wants[0]) == str self.proto.write_pkt_line("want %s %s\n" % (wants[0], self.capabilities())) for want in wants[1:]: self.proto.write_pkt_line("want %s\n" % want) self.proto.write_pkt_line(None) have = graph_walker.next() while have: self.proto.write_pkt_line("have %s\n" % have) if self._can_read(): pkt = self.proto.read_pkt_line() parts = pkt.rstrip("\n").split(" ") if parts[0] == "ACK": graph_walker.ack(parts[1]) assert parts[2] == "continue" have = graph_walker.next() self.proto.write_pkt_line("done\n") pkt = self.proto.read_pkt_line() while pkt: parts = pkt.rstrip("\n").split(" ") if parts[0] == "ACK": graph_walker.ack(pkt.split(" ")[1]) if len(parts) < 3 or parts[2] != "continue": break pkt = self.proto.read_pkt_line() for pkt in self.proto.read_pkt_seq(): channel = ord(pkt[0]) pkt = pkt[1:] if channel == 1: pack_data(pkt) elif channel == 2: progress(pkt) else: raise AssertionError("Invalid sideband channel %d" % channel) return refs