def _discover_references(self, service, url): assert url[-1] == "/" url = urlparse.urljoin(url, "info/refs") headers = {} if self.dumb is not False: url += "?service=%s" % service.decode('ascii') headers["Content-Type"] = "application/x-%s-request" % ( service.decode('ascii')) resp = self._http_request(url, headers) try: content_type = resp.info().gettype() except AttributeError: content_type = resp.info().get_content_type() try: self.dumb = (not content_type.startswith("application/x-git-")) if not self.dumb: proto = Protocol(resp.read, None) # The first line should mention the service try: [pkt] = list(proto.read_pkt_seq()) except ValueError: raise GitProtocolError( "unexpected number of packets received") if pkt.rstrip(b'\n') != (b'# service=' + service): raise GitProtocolError( "unexpected first line %r from smart server" % pkt) return read_pkt_refs(proto) else: return read_info_refs(resp), set() finally: resp.close()
def _connect(self, cmd, path): sockaddrs = socket.getaddrinfo( self._host, self._port, socket.AF_UNSPEC, socket.SOCK_STREAM) s = None err = socket.error("no address found for %s" % self._host) for (family, socktype, proto, canonname, sockaddr) in sockaddrs: s = socket.socket(family, socktype, proto) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) try: s.connect(sockaddr) break except socket.error as err: if s is not None: s.close() s = None if s is None: raise err # -1 means system default buffering rfile = s.makefile('rb', -1) # 0 means unbuffered wfile = s.makefile('wb', 0) def close(): rfile.close() wfile.close() s.close() proto = Protocol(rfile.read, wfile.write, close, report_activity=self._report_activity) if path.startswith(b"/~"): path = path[1:] proto.send_cmd(b'git-' + cmd, path, b'host=' + self._host) return proto, lambda: _fileno_can_read(s)
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 _connect(self, cmd, path): s = socket.socket(type=socket.SOCK_STREAM) s.connect((self._host, self._port)) # -1 means system default buffering rfile = s.makefile("rb", -1) # 0 means unbuffered wfile = s.makefile("wb", 0) proto = Protocol(rfile.read, wfile.write, report_activity=self._report_activity) proto.send_cmd("git-%s" % cmd, path, "host=%s" % self._host) return proto, lambda: _fileno_can_read(s)
def handle(self): proto = Protocol(self.rfile.read, self.wfile.write) command, args = proto.read_cmd() # switch case to handle the specific git command if command == 'git-upload-pack': cls = UploadPackHandler elif command == 'git-receive-pack': cls = ReceivePackHandler else: return h = cls(self.server.backend, self.rfile.read, self.wfile.write) h.handle()
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 serve_command(handler_cls, argv=sys.argv, backend=None, inf=sys.stdin, outf=sys.stdout): """Serve a single command. This is mostly useful for the implementation of commands used by e.g. git+ssh. :param handler_cls: `Handler` class to use for the request :param argv: execv-style command-line arguments. Defaults to sys.argv. :param backend: `Backend` to use :param inf: File-like object to read from, defaults to standard input. :param outf: File-like object to write to, defaults to standard output. :return: Exit code for use with sys.exit. 0 on success, 1 on failure. """ if backend is None: backend = FileSystemBackend() def send_fn(data): outf.write(data) outf.flush() proto = Protocol(inf.read, send_fn) handler = handler_cls(backend, argv[1:], proto) # FIXME: Catch exceptions and write a single-line summary to outf. handler.handle() return 0
def _connect(self, cmd, path): """ Override connection establishment in SSHGitClient class so that pubkey is used. """ # FIXME: This has no way to deal with passphrases.. # FIXME: can we rely on ssh being in PATH here ? args = ['ssh', '-x', '-oStrictHostKeyChecking=no'] if not (os.path.exists(self.pubkey) and os.access(self.pubkey, os.R_OK)): raise GitProtocolError( "Public key file is missing or incaccesible") args.extend(['-i', self.pubkey]) if self.port is not None: args.extend(['-p', str(self.port)]) if self.username is not None: host = '{0}@{1}'.format(self.username, self.host) else: host = self.host args.append(host) args.extend(["{0} '{1}'".format(self._get_cmd_path(cmd), path)]) proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) con = SubprocessWrapper(proc) logging.info("Connected to repo {0}:{1} via ssh, cmd: {2}".format( self.host, self.port if self.port else 22, cmd)) return (Protocol(con.read, con.write, report_activity=self._report_activity), con.can_read)
def _discover_references(self, service, url): assert url[-1] == "/" url = urlparse.urljoin(url, "info/refs") headers = {} if self.dumb != False: url += "?service=%s" % service headers["Content-Type"] = "application/x-%s-request" % service resp = self._http_request(url, headers) self.dumb = not resp.info().gettype().startswith("application/x-git-") proto = Protocol(resp.read, None) if not self.dumb: # The first line should mention the service pkts = list(proto.read_pkt_seq()) if pkts != [("# service=%s\n" % service)]: raise GitProtocolError("unexpected first line %r from smart server" % pkts) return self._read_refs(proto)
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 = 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 _discover_references(self, service, url): assert url[-1] == "/" url = urlparse.urljoin(url, "info/refs") headers = {} if self.dumb != False: url += "?service=%s" % service headers["Content-Type"] = "application/x-%s-request" % service resp = self._http_request(url, headers) self.dumb = (not resp.info().gettype().startswith("application/x-git-")) proto = Protocol(resp.read, None) if not self.dumb: # The first line should mention the service pkts = list(proto.read_pkt_seq()) if pkts != [('# service=%s\n' % service)]: raise GitProtocolError( "unexpected first line %r from smart server" % pkts) return self._read_refs(proto)
def _connect(self, cmd, path): con = get_ssh_vendor().connect_ssh( self.host, ["%s '%s'" % (self._get_cmd_path(cmd), path)], port=self.port, username=self.username) return (Protocol(con.read, con.write, report_activity=self._report_activity), con.can_read)
def _connect(self, cmd, path): if path.startswith("/~"): path = path[1:] 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, report_activity=self._report_activity), con.can_read)
def _connect(self, service, path): import subprocess argv = ['git', service, path] p = SubprocessWrapper( subprocess.Popen(argv, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=self._stderr)) return Protocol(p.read, p.write, report_activity=self._report_activity), p.can_read
def handle(self): try: proto = Protocol(self.rfile.read, self.wfile.write) command, args = proto.read_cmd() # switch case to handle the specific git command if command == 'git-upload-pack': cls = UploadPackHandler elif command == 'git-receive-pack': cls = ReceivePackHandler else: return h = cls(self.server.backend, self.rfile.read, self.wfile.write, args) h.handle() except: pass
def _connect(self, cmd, path): if path.startswith("/~"): path = path[1:] command = ["%s '%s'" % (self._get_cmd_path(cmd), path)] con = get_ssh_vendor().connect_ssh(self.host, command, **self._ssh_kwargs()) return (Protocol(con.read, con.write, report_activity=self._report_activity), con.can_read)
def _discover_references(self, service, url): url = urlparse.urljoin(url+"/", "info/refs") if not self.dumb: url += "?service=%s" % service req = urllib2.Request(url) resp = self._perform(req) if resp.getcode() == 404: raise NotGitRepository() if resp.getcode() != 200: raise GitProtocolError("unexpected http response %d" % resp.getcode()) self.dumb = (not resp.info().gettype().startswith("application/x-git-")) proto = Protocol(resp.read, None) if not self.dumb: # The first line should mention the service pkts = list(proto.read_pkt_seq()) if pkts != [('# service=%s\n' % service)]: raise GitProtocolError( "unexpected first line %r from smart server" % pkts) return self._read_refs(proto)
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 err = socket.error("no address found for %s" % self._host) for (family, socktype, proto, canonname, sockaddr) in sockaddrs: s = socket.socket(family, socktype, proto) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) try: s.connect(sockaddr) break except socket.error as err: if s is not None: s.close() s = None if s is None: raise err # -1 means system default buffering rfile = s.makefile('rb', -1) # 0 means unbuffered wfile = s.makefile('wb', 0) def close(): rfile.close() wfile.close() s.close() proto = Protocol(rfile.read, wfile.write, close, report_activity=self._report_activity) if path.startswith(b"/~"): path = path[1:] # 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, 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 _connect(self, service, path): import subprocess if self.git_command is None: git_command = find_git_command() argv = git_command + [service, path] argv = ['git', service.decode('ascii'), path] p = SubprocessWrapper( subprocess.Popen(argv, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=self._stderr)) return Protocol(p.read, p.write, p.close, report_activity=self._report_activity), p.can_read
def fetch_pack(self, path, determine_wants, graph_walker, pack_data, progress=None): """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) :return: Dictionary with all remote refs (not just those fetched) """ url = self._get_url(path) refs, server_capabilities = self._discover_references( b"git-upload-pack", url) negotiated_capabilities = self._fetch_capabilities & server_capabilities wants = determine_wants(refs) if wants is not None: wants = [cid for cid in wants if cid != ZERO_SHA] if not wants: return refs if self.dumb: raise NotImplementedError(self.send_pack) req_data = BytesIO() req_proto = Protocol(None, req_data.write) self._handle_upload_pack_head(req_proto, negotiated_capabilities, graph_walker, wants, lambda: False) resp = self._smart_request("git-upload-pack", url, data=req_data.getvalue()) try: resp_proto = Protocol(resp.read, None) self._handle_upload_pack_tail(resp_proto, negotiated_capabilities, graph_walker, pack_data, progress) return refs finally: resp.close()
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) + b" '" + path + b"'" 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), con.can_read)
def _discover_references(self, service, url): assert url[-1] == "/" url = urllib.parse.urljoin(url, "info/refs") headers = {} if self.dumb != False: url += "?service=%s" % service headers["Content-Type"] = "application/x-%s-request" % service req = urllib.request.Request(url, headers=headers) resp = self._perform(req) if resp.getcode() == 404: raise NotGitRepository() if resp.getcode() != 200: raise GitProtocolError("unexpected http response %d" % resp.getcode()) self.dumb = (not resp.info().get_content_type().startswith("application/x-git-")) proto = Protocol(resp.read, None, resp.close) if not self.dumb: # The first line should mention the service pkts = list(proto.read_pkt_seq()) if pkts != [(('# service=%s\n' % service).encode('utf-8'))]: raise GitProtocolError( "unexpected first line %r from smart server" % pkts) return self._read_refs(proto)
def receive_pack(path=".", inf=sys.stdin, outf=sys.stdout): """Receive a pack file after negotiating its contents using smart protocol. :param path: Path to the repository :param inf: Input stream to communicate with client :param outf: Output stream to communicate with client """ backend = FileSystemBackend() def send_fn(data): outf.write(data) outf.flush() proto = Protocol(inf.read, send_fn) handler = ReceivePackHandler(backend, [path], proto) # FIXME: Catch exceptions and write a single-line summary to outf. handler.handle() return 0
def upload_pack(path=".", inf=None, outf=None): """Upload a pack file after negotiating its contents using smart protocol. :param path: Path to the repository :param inf: Input stream to communicate with client :param outf: Output stream to communicate with client """ if outf is None: outf = getattr(sys.stdout, 'buffer', sys.stdout) if inf is None: inf = getattr(sys.stdin, 'buffer', sys.stdin) backend = FileSystemBackend() def send_fn(data): outf.write(data) outf.flush() proto = Protocol(inf.read, send_fn) handler = UploadPackHandler(backend, [path], proto) # FIXME: Catch exceptions and write a single-line summary to outf. handler.handle() return 0
class TCPGitClient(TraditionalGitClient): """A Git Client that works over TCP directly (i.e. git://).""" def __init__(self, host, port=None, *args, **kwargs): if port is None: port = TCP_GIT_PORT self._host = host self._port = port TraditionalGitClient.__init__(self, *args, **kwargs) def _connect(self, cmd, path): sockaddrs = socket.getaddrinfo(self._host, self._port, socket.AF_UNSPEC, socket.SOCK_STREAM) s = None err = socket.error("no address found for %s" % self._host) for (family, socktype, proto, canonname, sockaddr) in sockaddrs: s = socket.socket(family, socktype, proto) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) try: s.connect(sockaddr) break except socket.error, err: if s is not None: s.close() s = None if s is None: raise err # -1 means system default buffering rfile = s.makefile('rb', -1) # 0 means unbuffered wfile = s.makefile('wb', 0) proto = Protocol(rfile.read, wfile.write, report_activity=self._report_activity) if path.startswith("/~"): path = path[1:] proto.send_cmd('git-%s' % cmd, path, 'host=%s' % self._host) return proto, lambda: _fileno_can_read(s)
def setUp(self): TestCase.setUp(self) self.rout = StringIO() self.rin = StringIO() self.proto = Protocol(self.rin.read, self.rout.write)
def _connect(self, service, path): return Protocol(self.read, self.write), self.can_read
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 ProtocolTests(TestCase): def setUp(self): self.rout = StringIO() self.rin = StringIO() self.proto = Protocol(self.rin.read, self.rout.write) def test_write_pkt_line_none(self): self.proto.write_pkt_line(None) self.assertEquals(self.rout.getvalue(), "0000") def test_write_pkt_line(self): self.proto.write_pkt_line("bla") self.assertEquals(self.rout.getvalue(), "0007bla") def test_read_pkt_line(self): self.rin.write("0008cmd ") self.rin.seek(0) self.assertEquals("cmd ", self.proto.read_pkt_line()) def test_read_pkt_seq(self): self.rin.write("0008cmd 0005l0000") self.rin.seek(0) self.assertEquals(["cmd ", "l"], list(self.proto.read_pkt_seq())) def test_read_pkt_line_none(self): self.rin.write("0000") self.rin.seek(0) self.assertEquals(None, self.proto.read_pkt_line()) def test_write_sideband(self): self.proto.write_sideband(3, "bloe") self.assertEquals(self.rout.getvalue(), "0009\x03bloe") def test_send_cmd(self): self.proto.send_cmd("fetch", "a", "b") self.assertEquals(self.rout.getvalue(), "000efetch a\x00b\x00") def test_read_cmd(self): self.rin.write("0012cmd arg1\x00arg2\x00") self.rin.seek(0) self.assertEquals(("cmd", ["arg1", "arg2"]), self.proto.read_cmd()) def test_read_cmd_noend0(self): self.rin.write("0011cmd arg1\x00arg2") self.rin.seek(0) self.assertRaises(AssertionError, self.proto.read_cmd)
def setUp(self): self.rout = StringIO() self.rin = StringIO() self.proto = Protocol(self.rin.read, self.rout.write)
class GitClient(object): """Git smart server client. """ def __init__(self, fileno, read, write): self.proto = Protocol(read, write) self.fileno = fileno def capabilities(self): return "multi_ack side-band-64k thin-pack ofs-delta" 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) if not (ref == "capabilities^{}" and sha == "0" * 40): refs[ref] = sha return refs, server_capabilities def send_pack(self, path): refs, server_capabilities = self.read_refs() changed_refs = [] # FIXME if not changed_refs: self.proto.write_pkt_line(None) return self.proto.write_pkt_line("%s %s %s\0%s" % (changed_refs[0][0], changed_refs[0][1], changed_refs[0][2], self.capabilities())) want = [] have = [] for changed_ref in changed_refs[:]: self.proto.write_pkt_line("%s %s %s" % changed_refs) want.append(changed_refs[1]) if changed_refs[0] != "0" * 40: have.append(changed_refs[0]) self.proto.write_pkt_line(None) # FIXME: This is implementation specific # shas = generate_pack_contents(want, have, None) # write_pack_data(self.write, shas, len(shas)) 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 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 len(select.select([self.fileno], [], [], 0)[0]) > 0: 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)
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
def __init__(self, fileno, read, write): self.proto = Protocol(read, write) self.fileno = fileno
class GitClient(object): """Git smart server client. """ def __init__(self, fileno, read, write): self.proto = Protocol(read, write) self.fileno = fileno def capabilities(self): return "multi_ack side-band-64k thin-pack ofs-delta" 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) if not (ref == "capabilities^{}" and sha == "0" * 40): refs[ref] = sha return refs, server_capabilities def send_pack(self, path): refs, server_capabilities = self.read_refs() changed_refs = [] # FIXME if not changed_refs: self.proto.write_pkt_line(None) return self.proto.write_pkt_line("%s %s %s\0%s" % (changed_refs[0][0], changed_refs[0][1], changed_refs[0][2], self.capabilities())) want = [] have = [] for changed_ref in changed_refs[:]: self.proto.write_pkt_line("%s %s %s" % changed_refs) want.append(changed_refs[1]) if changed_refs[0] != "0"*40: have.append(changed_refs[0]) self.proto.write_pkt_line(None) # FIXME: This is implementation specific # shas = generate_pack_contents(want, have, None) # write_pack_data(self.write, shas, len(shas)) 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 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 len(select.select([self.fileno], [], [], 0)[0]) > 0: 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)
def __init__(self, backend, read, write, args): self.backend = backend self.proto = Protocol(read, write) self.backend.set_args(args)
def __init__(self, backend, read, write): self.backend = backend self.proto = Protocol(read, write)