4 from subprocess import Popen, PIPE
6 class ClientError(Exception):
10 def __init__(self, remote, create=False):
12 self._indexes_synced = 0
13 rs = remote.split(':', 1)
15 (host, dir) = ('NONE', remote)
16 argv = ['bup', 'server']
19 argv = ['ssh', host, '--', 'bup', 'server']
20 (self.host, self.dir) = (host, dir)
21 self.cachedir = git.repo('index-cache/%s'
22 % re.sub(r'[^@:\w]', '_',
23 "%s:%s" % (host, dir)))
24 self.p = p = Popen(argv, stdin=PIPE, stdout=PIPE)
25 self.conn = conn = Conn(p.stdout, p.stdin)
27 dir = re.sub(r'[\r\n]', ' ', dir)
29 conn.write('init-dir %s\n' % dir)
31 conn.write('set-dir %s\n' % dir)
38 if self.conn and not self._busy:
39 self.conn.write('quit\n')
42 while self.p.stdout.read(65536):
48 raise ClientError('server tunnel returned exit code %d' % rv)
54 raise ClientError('already busy with command %r' % self._busy)
59 def sync_indexes(self):
62 conn.write('list-indexes\n')
63 packdir = git.repo('objects/pack')
67 for line in linereader(conn):
71 assert(line.find('/') < 0)
72 if not os.path.exists(os.path.join(self.cachedir, line)):
76 for f in os.listdir(self.cachedir):
77 if f.endswith('.idx') and not f in all:
78 log('pruning old index: %r\n' % f)
79 os.unlink(os.path.join(self.cachedir, f))
81 # FIXME this should be pipelined: request multiple indexes at a time, or
82 # we waste lots of network turnarounds.
83 for name in needed.keys():
84 log('requesting %r\n' % name)
85 conn.write('send-index %s\n' % name)
86 n = struct.unpack('!I', conn.read(4))[0]
88 log(' expect %d bytes\n' % n)
89 fn = os.path.join(self.cachedir, name)
90 f = open(fn + '.tmp', 'w')
91 for b in chunkyreader(conn, n):
95 os.rename(fn + '.tmp', fn)
97 self._indexes_synced = 1
99 def new_packwriter(self):
100 assert(self._indexes_synced)
102 self._busy = 'receive-objects'
103 self.conn.write('receive-objects\n')
104 objcache = git.MultiPackIndex(self.cachedir)
105 return git.PackWriter_Remote(self.conn, objcache = objcache,
106 onclose = self._not_busy)
108 def read_ref(self, refname):
110 self.conn.write('read-ref %s\n' % refname)
111 r = self.conn.readline().strip()
114 assert(len(r) == 40) # hexified sha
115 return r.decode('hex')
117 return None # nonexistent ref
119 def update_ref(self, refname, newval, oldval):
121 self.conn.write('update-ref %s\n%s\n%s\n'
122 % (refname, newval.encode('hex'),
123 (oldval or '').encode('hex')))
129 self.conn.write('cat %s\n' % re.sub(r'[\n\r]', '_', id))
131 sz = struct.unpack('!I', self.conn.read(4))[0]
133 yield self.conn.read(sz)