3 bup_python="$(dirname "$0")/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 from __future__ import absolute_import
9 import os, sys, struct, subprocess
11 from bup import options, git, vfs, vint
12 from bup.compat import hexstr
13 from bup.git import MissingObject
14 from bup.helpers import (Conn, debug1, debug2, linereader, lines_until_sentinel,
16 from bup.repo import LocalRepo
20 dumb_server_mode = False
23 def do_help(conn, junk):
24 conn.write('Commands:\n %s\n' % '\n '.join(sorted(commands)))
29 global dumb_server_mode
30 dumb_server_mode = os.path.exists(git.repo('bup-dumb-server'))
31 debug1('bup server: serving in %s mode\n'
32 % (dumb_server_mode and 'dumb' or 'smart'))
35 def _init_session(reinit_with_new_repopath=None):
37 if reinit_with_new_repopath is None and git.repodir:
41 git.check_repo_or_die(reinit_with_new_repopath)
45 # OK. we now know the path is a proper repository. Record this path in the
46 # environment so that subprocesses inherit it and know where to operate.
47 os.environ['BUP_DIR'] = git.repodir
48 debug1('bup server: bupdir is %r\n' % git.repodir)
52 def init_dir(conn, arg):
54 debug1('bup server: bupdir initialized: %r\n' % git.repodir)
59 def set_dir(conn, arg):
64 def list_indexes(conn, junk):
69 for f in os.listdir(git.repo('objects/pack')):
70 if f.endswith('.idx'):
71 conn.write('%s%s\n' % (f, suffix))
75 def send_index(conn, name):
77 assert(name.find('/') < 0)
78 assert(name.endswith('.idx'))
79 idx = git.open_idx(git.repo('objects/pack/%s' % name))
80 conn.write(struct.pack('!I', len(idx.map)))
85 def receive_objects_v2(conn, junk):
94 w = git.PackWriter(objcache_maker=None)
101 raise Exception('object read: expected length header, got EOF\n')
102 n = struct.unpack('!I', ns)[0]
103 #debug2('expecting %d bytes\n' % n)
105 debug1('bup server: received %d object%s.\n'
106 % (w.count, w.count!=1 and "s" or ''))
107 fullpath = w.close(run_midx=not dumb_server_mode)
109 (dir, name) = os.path.split(fullpath)
110 conn.write('%s.idx\n' % name)
113 elif n == 0xffffffff:
114 debug2('bup server: receive-objects suspended.\n')
120 crcr = struct.unpack('!I', conn.read(4))[0]
122 buf = conn.read(n) # object sizes in bup are reasonably small
123 #debug2('read %d bytes\n' % n)
124 _check(w, n, len(buf), 'object read: expected %d bytes, got %d\n')
125 if not dumb_server_mode:
126 oldpack = w.exists(shar, want_source=True)
128 assert(not oldpack == True)
129 assert(oldpack.endswith('.idx'))
130 (dir,name) = os.path.split(oldpack)
131 if not (name in suggested):
132 debug1("bup server: suggesting index %s\n"
133 % git.shorten_hash(name))
134 debug1("bup server: because of object %s\n"
136 conn.write('index %s\n' % name)
139 nw, crc = w._raw_write((buf,), sha=shar)
140 _check(w, crcr, crc, 'object read: expected crc %d, got %d\n')
144 def _check(w, expected, actual, msg):
145 if expected != actual:
147 raise Exception(msg % (expected, actual))
150 def read_ref(conn, refname):
152 r = git.read_ref(refname)
153 conn.write('%s\n' % (r or '').encode('hex'))
157 def update_ref(conn, refname):
159 newval = conn.readline().strip()
160 oldval = conn.readline().strip()
161 git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
167 for blob in git.cp().join(id):
168 conn.write(struct.pack('!I', len(blob)))
170 except KeyError as e:
171 log('server: error: %s\n' % e)
172 conn.write('\0\0\0\0')
175 conn.write('\0\0\0\0')
178 def cat_batch(conn, dummy):
181 # For now, avoid potential deadlock by just reading them all
182 for ref in tuple(lines_until_sentinel(conn, '\n', Exception)):
184 it = cat_pipe.get(ref)
187 conn.write('missing\n')
189 conn.write('%s %s %d\n' % info)
194 def refs(conn, args):
195 limit_to_heads, limit_to_tags = args.split()
196 assert limit_to_heads in ('0', '1')
197 assert limit_to_tags in ('0', '1')
198 limit_to_heads = int(limit_to_heads)
199 limit_to_tags = int(limit_to_tags)
201 patterns = tuple(x[:-1] for x in lines_until_sentinel(conn, '\n', Exception))
202 for name, oid in git.list_refs(patterns=patterns,
203 limit_to_heads=limit_to_heads,
204 limit_to_tags=limit_to_tags):
205 assert '\n' not in name
206 conn.write('%s %s\n' % (oid.encode('hex'), name))
210 def rev_list(conn, _):
212 count = conn.readline()
214 raise Exception('Unexpected EOF while reading rev-list count')
215 count = None if count == '\n' else int(count)
216 fmt = conn.readline()
218 raise Exception('Unexpected EOF while reading rev-list format')
219 fmt = None if fmt == '\n' else fmt[:-1]
220 refs = tuple(x[:-1] for x in lines_until_sentinel(conn, '\n', Exception))
221 args = git.rev_list_invocation(refs, count=count, format=fmt)
222 p = subprocess.Popen(git.rev_list_invocation(refs, count=count, format=fmt),
223 env=git._gitenv(git.repodir),
224 stdout=subprocess.PIPE)
226 out = p.stdout.read(64 * 1024)
231 rv = p.wait() # not fatal
233 msg = 'git rev-list returned error %d' % rv
238 def resolve(conn, args):
240 (flags,) = args.split()
242 want_meta = bool(flags & 1)
243 follow = bool(flags & 2)
244 have_parent = bool(flags & 4)
245 parent = vfs.read_resolution(conn) if have_parent else None
246 path = vint.read_bvec(conn)
248 raise Exception('Empty resolve path')
250 res = list(vfs.resolve(repo, path, parent=parent, want_meta=want_meta,
252 except vfs.IOError as ex:
254 if isinstance(res, vfs.IOError):
255 conn.write(b'\0') # error
256 vfs.write_ioerror(conn, res)
258 conn.write(b'\1') # success
259 vfs.write_resolution(conn, res)
265 o = options.Options(optspec)
266 (opt, flags, extra) = o.parse(sys.argv[1:])
269 o.fatal('no arguments expected')
271 debug2('bup server: reading from stdin.\n')
276 'init-dir': init_dir,
278 'list-indexes': list_indexes,
279 'send-index': send_index,
280 'receive-objects-v2': receive_objects_v2,
281 'read-ref': read_ref,
282 'update-ref': update_ref,
284 'cat': join, # apocryphal alias
285 'cat-batch' : cat_batch,
287 'rev-list': rev_list,
291 # FIXME: this protocol is totally lame and not at all future-proof.
292 # (Especially since we abort completely as soon as *anything* bad happens)
293 conn = Conn(sys.stdin, sys.stdout)
294 lr = linereader(conn)
299 debug1('bup server: command: %r\n' % line)
300 words = line.split(' ', 1)
302 rest = len(words)>1 and words[1] or ''
306 cmd = commands.get(cmd)
310 raise Exception('unknown server command: %r\n' % line)
312 debug1('bup server: done\n')