comparison hglib/client.py @ 2:5fa34c3ac9a0

turn hglib into a module and expose open (previously connect) in its __init__.py so common usage will now be: import hglib hglib.open(...) also rename hglib.py to client.py
author Idan Kamara <idankk86@gmail.com>
date Sat, 23 Jul 2011 22:55:36 +0300
parents
children a3a9cf58801f
comparison
equal deleted inserted replaced
1:bbd294291dd8 2:5fa34c3ac9a0
1 import subprocess, os, struct, cStringIO, collections
2 import hglib, error, util
3
4 class hgclient(object):
5 inputfmt = '>I'
6 outputfmt = '>cI'
7 outputfmtsize = struct.calcsize(outputfmt)
8 retfmt = '>i'
9
10 # XXX fix this hack
11 _stylesdir = os.path.join(os.path.dirname(__file__), 'styles')
12 revstyle = ['--style', os.path.join(_stylesdir, 'rev.style')]
13
14 revision = collections.namedtuple('revision', 'rev, node, tags, '
15 'branch, author, desc')
16
17 def __init__(self, path, encoding, configs):
18 args = [hglib.HGPATH, 'serve', '--cmdserver', 'pipe']
19 if path:
20 args += ['-R', path]
21 if configs:
22 args += ['--config'] + configs
23 env = dict(os.environ)
24 if encoding:
25 env['HGENCODING'] = encoding
26
27 self.server = subprocess.Popen(args, stdin=subprocess.PIPE,
28 stdout=subprocess.PIPE, env=env)
29
30 self._readhello()
31 self._config = {}
32
33 def _readhello(self):
34 """ read the hello message the server sends when started """
35 ch, msg = self._readchannel()
36 assert ch == 'o'
37
38 msg = msg.split('\n')
39
40 self.capabilities = msg[0][len('capabilities: '):]
41 if not self.capabilities:
42 raise error.ResponseError("bad hello message: expected 'capabilities: '"
43 ", got %r" % msg[0])
44
45 self.capabilities = set(self.capabilities.split())
46
47 # at the very least the server should be able to run commands
48 assert 'runcommand' in self.capabilities
49
50 self._encoding = msg[1][len('encoding: '):]
51 if not self._encoding:
52 raise error.ResponseError("bad hello message: expected 'encoding: '"
53 ", got %r" % msg[1])
54
55 def _readchannel(self):
56 data = self.server.stdout.read(hgclient.outputfmtsize)
57 if not data:
58 raise error.ServerError()
59 channel, length = struct.unpack(hgclient.outputfmt, data)
60 if channel in 'IL':
61 return channel, length
62 else:
63 return channel, self.server.stdout.read(length)
64
65 def _parserevs(self, splitted):
66 ''' splitted is a list of fields according to our rev.style, where each 6
67 fields compose one revision. '''
68 return [self.revision._make(rev) for rev in util.grouper(6, splitted)]
69
70 def _eatlines(self, s, n):
71 idx = 0
72 for i in xrange(n):
73 idx = s.find('\n', idx) + 1
74
75 return s[idx:]
76
77 def runcommand(self, args, inchannels, outchannels):
78 def writeblock(data):
79 self.server.stdin.write(struct.pack(self.inputfmt, len(data)))
80 self.server.stdin.write(data)
81 self.server.stdin.flush()
82
83 if not self.server:
84 raise ValueError("server not connected")
85
86 self.server.stdin.write('runcommand\n')
87 writeblock('\0'.join(args))
88
89 while True:
90 channel, data = self._readchannel()
91
92 # input channels
93 if channel in inchannels:
94 writeblock(inchannels[channel](data))
95 # output channels
96 elif channel in outchannels:
97 outchannels[channel](data)
98 # result channel, command finished
99 elif channel == 'r':
100 return struct.unpack(hgclient.retfmt, data)[0]
101 # a channel that we don't know and can't ignore
102 elif channel.isupper():
103 raise error.ResponseError("unexpected data on required channel '%s'"
104 % channel)
105 # optional channel
106 else:
107 pass
108
109 def outputruncommand(self, args, inchannels = {}, raiseonerror=True):
110 ''' run the command specified by args, returning (ret, output, error) '''
111 out, err = cStringIO.StringIO(), cStringIO.StringIO()
112 outchannels = {'o' : out.write, 'e' : err.write}
113 ret = self.runcommand(args, inchannels, outchannels)
114 if ret and raiseonerror:
115 raise error.CommandError(args, ret, out.getvalue(), err.getvalue())
116 return ret, out.getvalue(), err.getvalue()
117
118 def close(self):
119 self.server.stdin.close()
120 self.server.wait()
121 ret = self.server.returncode
122 self.server = None
123 return ret
124
125 @property
126 def encoding(self):
127 """ get the servers encoding """
128 if not 'getencoding' in self.capabilities:
129 raise CapabilityError('getencoding')
130
131 if not self._encoding:
132 self.server.stdin.write('getencoding\n')
133 self._encoding = self._readfromchannel('r')
134
135 return self._encoding
136
137 def config(self, refresh=False):
138 if not self._config or refresh:
139 self._config.clear()
140
141 ret, out, err = self.outputruncommand(['showconfig'])
142 if ret:
143 raise error.CommandError(['showconfig'], ret, out, err)
144
145 for entry in cStringIO.StringIO(out):
146 k, v = entry.rstrip().split('=', 1)
147 section, name = k.split('.', 1)
148 self._config.setdefault(section, {})[name] = v
149
150 return self._config
151
152 def status(self):
153 ret, out = self.outputruncommand(['status', '-0'])
154
155 d = dict((c, []) for c in 'MARC!?I')
156
157 for entry in out.split('\0'):
158 if entry:
159 t, f = entry.split(' ', 1)
160 d[t].append(f)
161
162 return d
163
164 def log(self, revrange=None):
165 args = ['log'] + self.revstyle
166 if revrange:
167 args.append('-r')
168 args += revrange
169
170 out = self.outputruncommand(args)[1]
171 out = out.split('\0')[:-1]
172
173 return self._parserevs(out)
174
175 def incoming(self, revrange=None, path=None):
176 args = ['incoming'] + self.revstyle
177 if revrange:
178 args.append('-r')
179 args += revrange
180
181 if path:
182 args += [path]
183
184 ret, out, err = self.outputruncommand(args, raiseonerror=False)
185 if not ret:
186 out = self._eatlines(out, 2).split('\0')[:-1]
187 return self._parserevs(out)
188 elif ret == 1:
189 return []
190 else:
191 raise error.CommandError(args, ret, out, err)
192
193 def outgoing(self, revrange=None, path=None):
194 args = ['outgoing'] + self.revstyle
195 if revrange:
196 args.append('-r')
197 args += revrange
198
199 if path:
200 args += [path]
201
202 ret, out, err = self.outputruncommand(args, raiseonerror=False)
203 if not ret:
204 out = self._eatlines(out, 2).split('\0')[:-1]
205 return self._parserevs(out)
206 elif ret == 1:
207 return []
208 else:
209 raise error.CommandError(args, ret, out, err)
210
211 def commit(self, message, addremove=False):
212 args = ['commit', '-m', message]
213
214 if addremove:
215 args += ['-A']
216
217 self.outputruncommand(args)
218
219 # hope the tip hasn't changed since we committed
220 return self.tip()
221
222 def import_(self, patch):
223 if isinstance(patch, str):
224 fp = open(patch)
225 else:
226 assert hasattr(patch, 'read')
227 assert hasattr(patch, 'readline')
228
229 fp = patch
230
231 try:
232 inchannels = {'I' : fp.read, 'L' : fp.readline}
233 self.outputruncommand(['import', '-'], inchannels)
234 finally:
235 if fp != patch:
236 fp.close()
237
238 def root(self):
239 return self.outputruncommand(['root'])[1].rstrip()
240
241 def clone(self, source='.', dest=None, branch=None, updaterev=None,
242 revrange=None):
243 args = ['clone']
244
245 if branch:
246 args += ['-b', branch]
247 if updaterev:
248 args += ['-u', updaterev]
249 if revrange:
250 args.append('-r')
251 args += revrange
252 args.append(source)
253
254 if dest:
255 args.append(dest)
256
257 self.outputruncommand(args)
258
259 def tip(self):
260 out = self.outputruncommand(['tip'] + self.revstyle)[1]
261 out = out.split('\0')
262
263 return self._parserevs(out)[0]
264
265 def branch(self, name=None):
266 if not name:
267 return self.outputruncommand(['branch'])[1].rstrip()
268
269 def branches(self):
270 out = self.outputruncommand(['branches'])[1]
271 branches = {}
272 for line in out.rstrip().split('\n'):
273 branch, revnode = line.split()
274 branches[branch] = self.log(revrange=[revnode.split(':')[0]])[0]
275
276 return branches
277
278 def paths(self, name=None):
279 if not name:
280 out = self.outputruncommand(['paths'])[1]
281 if not out:
282 return {}
283
284 return dict([s.split(' = ') for s in out.rstrip().split('\n')])
285 else:
286 args = ['paths', name]
287 ret, out, err = self.outputruncommand(args, raiseonerror=False)
288 if ret:
289 raise error.CommandError(args, ret, out, err)
290 return out.rstrip()
291
292 def cat(self, files, rev=None, output=None):
293 args = ['cat']
294 if rev:
295 args += ['-r', rev]
296 if output:
297 args += ['-o', output]
298
299 args += files
300 ret, out, err = self.outputruncommand(args)
301
302 if not output:
303 return out