Mercurial > python-hglib
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 |