|
@@ -17,6 +17,7 @@
|
|
|
# MA 02110-1301, USA.
|
|
|
|
|
|
import SocketServer
|
|
|
+from dulwich.protocol import Protocol
|
|
|
|
|
|
class Backend(object):
|
|
|
|
|
@@ -55,53 +56,104 @@ class Backend(object):
|
|
|
"""
|
|
|
raise NotImplementedError
|
|
|
|
|
|
+from dulwich.repo import Repo
|
|
|
+from dulwich.pack import PackData, Pack
|
|
|
+import sha, tempfile, os
|
|
|
+from dulwich.pack import write_pack_data
|
|
|
|
|
|
-class Handler(object):
|
|
|
+class GitBackend(Backend):
|
|
|
|
|
|
- def __init__(self, backend, read, write):
|
|
|
- self.backend = backend
|
|
|
- self.read = read
|
|
|
- self.write = write
|
|
|
+ def __init__(self, gitdir=None):
|
|
|
+ self.gitdir = gitdir
|
|
|
|
|
|
- def read_pkt_line(self):
|
|
|
- """
|
|
|
- Reads a 'pkt line' from the remote git process
|
|
|
+ if not self.gitdir:
|
|
|
+ self.gitdir = tempfile.mkdtemp()
|
|
|
+ Repo.create(self.gitdir)
|
|
|
|
|
|
- :return: The next string from the stream
|
|
|
- """
|
|
|
- sizestr = self.read(4)
|
|
|
- if not sizestr:
|
|
|
- return None
|
|
|
- size = int(sizestr, 16)
|
|
|
- if size == 0:
|
|
|
- return None
|
|
|
- return self.read(size-4)
|
|
|
-
|
|
|
- def write_pkt_line(self, line):
|
|
|
- """
|
|
|
- Sends a 'pkt line' to the remote git process
|
|
|
+ self.repo = Repo(self.gitdir)
|
|
|
|
|
|
- :param line: A string containing the data to send
|
|
|
- """
|
|
|
- self.write("%04x%s" % (len(line)+4, line))
|
|
|
+ def get_refs(self):
|
|
|
+ refs = []
|
|
|
+ if self.repo.head():
|
|
|
+ refs.append(('HEAD', self.repo.head()))
|
|
|
+ for ref, sha in self.repo.heads().items():
|
|
|
+ refs.append(('refs/heads/'+ref,sha))
|
|
|
+ return refs
|
|
|
|
|
|
- def write_sideband(self, channel, blob):
|
|
|
- """
|
|
|
- Write data to the sideband (a git multiplexing method)
|
|
|
+ def has_revision(self, sha):
|
|
|
+ return self.repo.get_object(sha) != None
|
|
|
|
|
|
- :param channel: int specifying which channel to write to
|
|
|
- :param blob: a blob of data (as a string) to send on this channel
|
|
|
- """
|
|
|
- # a pktline can be a max of 65535. a sideband line can therefore be
|
|
|
- # 65535-5 = 65530
|
|
|
- # WTF: Why have the len in ASCII, but the channel in binary.
|
|
|
- while blob:
|
|
|
- self.write_pkt_line("%s%s" % (chr(channel), blob[:65530]))
|
|
|
- blob = blob[65530:]
|
|
|
+ def apply_pack(self, refs, read):
|
|
|
+ # store the incoming pack in the repository
|
|
|
+ fd, name = tempfile.mkstemp(suffix='.pack', prefix='', dir=self.repo.pack_dir())
|
|
|
+ os.write(fd, read())
|
|
|
+ os.close(fd)
|
|
|
+
|
|
|
+ # strip '.pack' off our filename
|
|
|
+ basename = name[:-5]
|
|
|
+
|
|
|
+ # generate an index for it
|
|
|
+ pd = PackData(name)
|
|
|
+ pd.create_index_v2(basename+".idx")
|
|
|
+
|
|
|
+ for oldsha, sha, ref in refs:
|
|
|
+ if ref == "0" * 40:
|
|
|
+ self.repo.remove_ref(ref)
|
|
|
+ else:
|
|
|
+ self.repo.set_ref(ref, sha)
|
|
|
+
|
|
|
+ print "pack applied"
|
|
|
+
|
|
|
+ def generate_pack(self, want, have, write, progress):
|
|
|
+ progress("dul-daemon says what\n")
|
|
|
+
|
|
|
+ sha_queue = []
|
|
|
+
|
|
|
+ commits_to_send = want[:]
|
|
|
+ for sha in commits_to_send:
|
|
|
+ if sha in sha_queue:
|
|
|
+ continue
|
|
|
+
|
|
|
+ sha_queue.append((1,sha))
|
|
|
+
|
|
|
+ c = self.repo.commit(sha)
|
|
|
+ for p in c.parents():
|
|
|
+ if not p in commits_to_send:
|
|
|
+ commits_to_send.append(p)
|
|
|
+
|
|
|
+ def parse_tree(tree, sha_queue):
|
|
|
+ for mode, name, x in tree.entries():
|
|
|
+ if not x in sha_queue:
|
|
|
+ try:
|
|
|
+ t = self.repo.get_tree(x)
|
|
|
+ sha_queue.append((2, x))
|
|
|
+ parse_tree(t, sha_queue)
|
|
|
+ except:
|
|
|
+ sha_queue.append((3, x))
|
|
|
+
|
|
|
+ treesha = c.tree()
|
|
|
+ if treesha not in sha_queue:
|
|
|
+ sha_queue.append((2, treesha))
|
|
|
+ t = self.repo.get_tree(treesha)
|
|
|
+ parse_tree(t, sha_queue)
|
|
|
+
|
|
|
+ progress("counting objects: %d\r" % len(sha_queue))
|
|
|
+
|
|
|
+ progress("counting objects: %d, done.\n" % len(sha_queue))
|
|
|
+
|
|
|
+ write_pack_data(write, (self.repo.get_object(sha).as_raw_string() for sha in sha_queue))
|
|
|
+
|
|
|
+ progress("how was that, then?\n")
|
|
|
+
|
|
|
+
|
|
|
+class Handler(object):
|
|
|
+
|
|
|
+ def __init__(self, backend, read, write):
|
|
|
+ self.backend = backend
|
|
|
+ self.proto = Protocol(read, write)
|
|
|
|
|
|
def capabilities(self):
|
|
|
- # FIXME: Capabilities are different for pushing...
|
|
|
- return "multi_ack side-band-64k thin-pack ofs-delta"
|
|
|
+ return " ".join(self.default_capabilities())
|
|
|
|
|
|
def handshake(self, blob):
|
|
|
"""
|
|
@@ -118,30 +170,27 @@ class Handler(object):
|
|
|
|
|
|
return blob
|
|
|
|
|
|
- def handle(self):
|
|
|
- """
|
|
|
- Deal with the request
|
|
|
- """
|
|
|
- raise NotImplementedError
|
|
|
-
|
|
|
|
|
|
class UploadPackHandler(Handler):
|
|
|
|
|
|
+ def default_capabilities(self):
|
|
|
+ return ("multi_ack", "side-band-64k", "thin-pack", "ofs-delta")
|
|
|
+
|
|
|
def handle(self):
|
|
|
refs = self.backend.get_refs()
|
|
|
|
|
|
if refs:
|
|
|
- self.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
|
|
|
+ self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
|
|
|
for i in range(1, len(refs)):
|
|
|
ref = refs[i]
|
|
|
- self.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
|
|
|
+ self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
|
|
|
|
|
|
- # i'm done...
|
|
|
- self.write("0000")
|
|
|
+ # i'm done..
|
|
|
+ self.proto.write("0000")
|
|
|
|
|
|
# Now client will either send "0000", meaning that it doesnt want to pull.
|
|
|
# or it will start sending want want want commands
|
|
|
- want = self.read_pkt_line()
|
|
|
+ want = self.proto.read_pkt_line()
|
|
|
if want == None:
|
|
|
return
|
|
|
|
|
@@ -154,54 +203,57 @@ class UploadPackHandler(Handler):
|
|
|
# FIXME: This check probably isnt needed?
|
|
|
if self.backend.has_revision(want_rev):
|
|
|
want_revs.append(want_rev)
|
|
|
- want = self.read_pkt_line()
|
|
|
+ want = self.proto.read_pkt_line()
|
|
|
|
|
|
# Client will now tell us which commits it already has - if we have them we ACK them
|
|
|
# this allows client to stop looking at that commits parents (main reason why git pull is fast)
|
|
|
last_sha = None
|
|
|
have_revs = []
|
|
|
- have = self.read_pkt_line()
|
|
|
+ have = self.proto.read_pkt_line()
|
|
|
while have and have[:4] == 'have':
|
|
|
have_ref = have[6:46]
|
|
|
- if self.backend.has_revision(hav_rev):
|
|
|
- self.write_pkt_line("ACK %s continue\n" % sha)
|
|
|
- last_sha = sha
|
|
|
- have_revs.append(rev_id)
|
|
|
- have = self.read_pkt_line()
|
|
|
+ if self.backend.has_revision(have_ref):
|
|
|
+ self.proto.write_pkt_line("ACK %s continue\n" % have_ref)
|
|
|
+ last_sha = have_ref
|
|
|
+ have_revs.append(have_ref)
|
|
|
+ have = self.proto.read_pkt_line()
|
|
|
|
|
|
# At some point client will stop sending commits and will tell us it is done
|
|
|
assert(have[:4] == "done")
|
|
|
|
|
|
# Oddness: Git seems to resend the last ACK, without the "continue" statement
|
|
|
if last_sha:
|
|
|
- self.write_pkt_line("ACK %s\n" % last_sha)
|
|
|
+ self.proto.write_pkt_line("ACK %s\n" % last_sha)
|
|
|
|
|
|
# The exchange finishes with a NAK
|
|
|
- self.write_pkt_line("NAK\n")
|
|
|
+ self.proto.write_pkt_line("NAK\n")
|
|
|
|
|
|
- self.backend.generate_pack(want_revs, have_revs, lambda x: self.write_sideband(1, x), lambda x: self.write_sideband(2, x))
|
|
|
+ self.backend.generate_pack(want_revs, have_revs, lambda x: self.proto.write_sideband(1, x), lambda x: self.proto.write_sideband(2, x))
|
|
|
|
|
|
# we are done
|
|
|
- self.write("0000")
|
|
|
+ self.proto.write("0000")
|
|
|
|
|
|
|
|
|
class ReceivePackHandler(Handler):
|
|
|
|
|
|
+ def default_capabilities(self):
|
|
|
+ return ("report-status", "delete-refs")
|
|
|
+
|
|
|
def handle(self):
|
|
|
refs = self.backend.get_refs()
|
|
|
|
|
|
if refs:
|
|
|
- self.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
|
|
|
+ self.proto.write_pkt_line("%s %s\x00%s\n" % (refs[0][1], refs[0][0], self.capabilities()))
|
|
|
for i in range(1, len(refs)):
|
|
|
ref = refs[i]
|
|
|
- self.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
|
|
|
+ self.proto.write_pkt_line("%s %s\n" % (ref[1], ref[0]))
|
|
|
else:
|
|
|
- self.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
|
|
|
+ self.proto.write_pkt_line("0000000000000000000000000000000000000000 capabilities^{} %s" % self.capabilities())
|
|
|
|
|
|
- self.write("0000")
|
|
|
+ self.proto.write("0000")
|
|
|
|
|
|
client_refs = []
|
|
|
- ref = self.read_pkt_line()
|
|
|
+ ref = self.proto.read_pkt_line()
|
|
|
|
|
|
# if ref is none then client doesnt want to send us anything..
|
|
|
if ref is None:
|
|
@@ -212,10 +264,10 @@ class ReceivePackHandler(Handler):
|
|
|
# client will now send us a list of (oldsha, newsha, ref)
|
|
|
while ref:
|
|
|
client_refs.append(ref.split())
|
|
|
- ref = self.read_pkt_line()
|
|
|
+ ref = self.proto.read_pkt_line()
|
|
|
|
|
|
# backend can now deal with this refs and read a pack using self.read
|
|
|
- self.backend.apply_pack(client_refs, self.read)
|
|
|
+ self.backend.apply_pack(client_refs, self.proto.read)
|
|
|
|
|
|
# when we have read all the pack from the client, it assumes everything worked OK
|
|
|
# there is NO ack from the server before it reports victory.
|
|
@@ -231,7 +283,7 @@ class TCPGitRequestHandler(SocketServer.StreamRequestHandler, Handler):
|
|
|
#so we can't call this in a sane place??
|
|
|
Handler.__init__(self, self.server.backend, self.rfile.read, self.wfile.write)
|
|
|
|
|
|
- request = self.read_pkt_line()
|
|
|
+ request = self.proto.read_pkt_line()
|
|
|
|
|
|
# up until the space is the command to run, everything after is parameters
|
|
|
splice_point = request.find(' ')
|
|
@@ -248,7 +300,7 @@ class TCPGitRequestHandler(SocketServer.StreamRequestHandler, Handler):
|
|
|
else:
|
|
|
return
|
|
|
|
|
|
- h = cls(self.backend, self.read, self.write)
|
|
|
+ h = cls(self.backend, self.proto.read, self.proto.write)
|
|
|
h.handle()
|
|
|
|
|
|
|