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 _make_objcache(self):
104 return git.MultiPackIndex(self.cachedir)
106 def new_packwriter(self):
108 self._busy = 'receive-objects'
109 return PackWriter_Remote(self.conn,
110 objcache_maker = self._make_objcache,
111 onclose = self._not_busy)
113 def read_ref(self, refname):
115 self.conn.write('read-ref %s\n' % refname)
116 r = self.conn.readline().strip()
119 assert(len(r) == 40) # hexified sha
120 return r.decode('hex')
122 return None # nonexistent ref
124 def update_ref(self, refname, newval, oldval):
126 self.conn.write('update-ref %s\n%s\n%s\n'
127 % (refname, newval.encode('hex'),
128 (oldval or '').encode('hex')))
134 self.conn.write('cat %s\n' % re.sub(r'[\n\r]', '_', id))
136 sz = struct.unpack('!I', self.conn.read(4))[0]
138 yield self.conn.read(sz)
143 class PackWriter_Remote(git.PackWriter):
144 def __init__(self, conn, objcache_maker=None, onclose=None):
145 git.PackWriter.__init__(self, objcache_maker)
147 self.filename = 'remote socket'
148 self.onclose = onclose
149 self._packopen = False
152 if not self._packopen:
153 self._make_objcache()
154 self.file.write('receive-objects\n')
155 self._packopen = True
158 if self._packopen and self.file:
159 self.file.write('\0\0\0\0')
160 self._packopen = False
161 id = self.file.readline().strip()
172 raise GitError("don't know how to abort remote pack writing")
174 def _raw_write(self, datalist):
176 if not self._packopen:
178 data = ''.join(datalist)
180 self.file.write(struct.pack('!I', len(data)) + data)
181 self.outbytes += len(data)