]> arthur.barton.de Git - bup.git/blobdiff - cmd/server-cmd.py
rev_list: handle multiple results/ref from remote for custom formats
[bup.git] / cmd / server-cmd.py
index 0de6694f5f58d375cabcdc0f2a1d0a399a100aec..6fa9a67a96d8ac68dd7f748315cf6003a4999184 100755 (executable)
@@ -5,15 +5,19 @@ exec "$bup_python" "$0" ${1+"$@"}
 """
 # end of bup preamble
 
-import os, sys, struct
+from __future__ import absolute_import
+import os, sys, struct, subprocess
 
-from bup import options, git
-from bup.helpers import Conn, debug1, debug2, linereader, log
+from bup import options, git, vfs, vint
+from bup.git import MissingObject
+from bup.helpers import (Conn, debug1, debug2, linereader, lines_until_sentinel,
+                         log)
+from bup.repo import LocalRepo
 
 
 suspended_w = None
 dumb_server_mode = False
-
+repo = None
 
 def do_help(conn, junk):
     conn.write('Commands:\n    %s\n' % '\n    '.join(sorted(commands)))
@@ -28,9 +32,15 @@ def _set_mode():
 
 
 def _init_session(reinit_with_new_repopath=None):
+    global repo
     if reinit_with_new_repopath is None and git.repodir:
+        if not repo:
+            repo = LocalRepo()
         return
     git.check_repo_or_die(reinit_with_new_repopath)
+    if repo:
+        repo.close()
+    repo = LocalRepo()
     # OK. we now know the path is a proper repository. Record this path in the
     # environment so that subprocesses inherit it and know where to operate.
     os.environ['BUP_DIR'] = git.repodir
@@ -150,15 +160,10 @@ def update_ref(conn, refname):
     git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
     conn.ok()
 
-
-cat_pipe = None
-def cat(conn, id):
-    global cat_pipe
+def join(conn, id):
     _init_session()
-    if not cat_pipe:
-        cat_pipe = git.CatPipe()
     try:
-        for blob in cat_pipe.join(id):
+        for blob in git.cp().join(id):
             conn.write(struct.pack('!I', len(blob)))
             conn.write(blob)
     except KeyError as e:
@@ -169,6 +174,89 @@ def cat(conn, id):
         conn.write('\0\0\0\0')
         conn.ok()
 
+def cat_batch(conn, dummy):
+    _init_session()
+    cat_pipe = git.cp()
+    # For now, avoid potential deadlock by just reading them all
+    for ref in tuple(lines_until_sentinel(conn, '\n', Exception)):
+        ref = ref[:-1]
+        it = cat_pipe.get(ref)
+        info = next(it)
+        if not info[0]:
+            conn.write('missing\n')
+            continue
+        conn.write('%s %s %d\n' % info)
+        for buf in it:
+            conn.write(buf)
+    conn.ok()
+
+def refs(conn, args):
+    limit_to_heads, limit_to_tags = args.split()
+    assert limit_to_heads in ('0', '1')
+    assert limit_to_tags in ('0', '1')
+    limit_to_heads = int(limit_to_heads)
+    limit_to_tags = int(limit_to_tags)
+    _init_session()
+    patterns = tuple(x[:-1] for x in lines_until_sentinel(conn, '\n', Exception))
+    for name, oid in git.list_refs(patterns=patterns,
+                                   limit_to_heads=limit_to_heads,
+                                   limit_to_tags=limit_to_tags):
+        assert '\n' not in name
+        conn.write('%s %s\n' % (oid.encode('hex'), name))
+    conn.write('\n')
+    conn.ok()
+
+def rev_list(conn, _):
+    _init_session()
+    count = conn.readline()
+    if not count:
+        raise Exception('Unexpected EOF while reading rev-list count')
+    count = None if count == '\n' else int(count)
+    fmt = conn.readline()
+    if not fmt:
+        raise Exception('Unexpected EOF while reading rev-list format')
+    fmt = None if fmt == '\n' else fmt[:-1]
+    refs = tuple(x[:-1] for x in lines_until_sentinel(conn, '\n', Exception))
+    args = git.rev_list_invocation(refs, count=count, format=fmt)
+    p = subprocess.Popen(git.rev_list_invocation(refs, count=count, format=fmt),
+                         preexec_fn=git._gitenv(git.repodir),
+                         stdout=subprocess.PIPE)
+    while True:
+        out = p.stdout.read(64 * 1024)
+        if not out:
+            break
+        conn.write(out)
+    conn.write('\n')
+    rv = p.wait()  # not fatal
+    if rv:
+        msg = 'git rev-list returned error %d' % rv
+        conn.error(msg)
+        raise GitError(msg)
+    conn.ok()
+
+def resolve(conn, args):
+    _init_session()
+    (flags,) = args.split()
+    flags = int(flags)
+    want_meta = bool(flags & 1)
+    follow = bool(flags & 2)
+    have_parent = bool(flags & 4)
+    parent = vfs.read_resolution(conn) if have_parent else None
+    path = vint.read_bvec(conn)
+    if not len(path):
+        raise Exception('Empty resolve path')
+    try:
+        res = list(vfs.resolve(repo, path, parent=parent, want_meta=want_meta,
+                               follow=follow))
+    except vfs.IOError as ex:
+        res = ex
+    if isinstance(res, vfs.IOError):
+        conn.write(b'\0')  # error
+        vfs.write_ioerror(conn, res)
+    else:
+        conn.write(b'\1')  # success
+        vfs.write_resolution(conn, res)
+    conn.ok()
 
 optspec = """
 bup server
@@ -191,7 +279,12 @@ commands = {
     'receive-objects-v2': receive_objects_v2,
     'read-ref': read_ref,
     'update-ref': update_ref,
-    'cat': cat,
+    'join': join,
+    'cat': join,  # apocryphal alias
+    'cat-batch' : cat_batch,
+    'refs': refs,
+    'rev-list': rev_list,
+    'resolve': resolve
 }
 
 # FIXME: this protocol is totally lame and not at all future-proof.