##// END OF EJS Templates
fix undefined variables, spotted by pylint
Benoit Boissinot -
r11879:4e804302 default
parent child Browse files
Show More
@@ -1,473 +1,474 b''
1 1 # dagparser.py - parser and generator for concise description of DAGs
2 2 #
3 3 # Copyright 2010 Peter Arrenbrecht <peter@arrenbrecht.ch>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re, string
9 9 import util
10 from i18n import _
10 11
11 12 def parsedag(desc):
12 13 '''parses a DAG from a concise textual description; generates events
13 14
14 15 "+n" is a linear run of n nodes based on the current default parent
15 16 "." is a single node based on the current default parent
16 17 "$" resets the default parent to -1 (implied at the start);
17 18 otherwise the default parent is always the last node created
18 19 "<p" sets the default parent to the backref p
19 20 "*p" is a fork at parent p, where p is a backref
20 21 "*p1/p2/.../pn" is a merge of parents p1..pn, where the pi are backrefs
21 22 "/p2/.../pn" is a merge of the preceding node and p2..pn
22 23 ":name" defines a label for the preceding node; labels can be redefined
23 24 "@text" emits an annotation event for text
24 25 "!command" emits an action event for the current node
25 26 "!!my command\n" is like "!", but to the end of the line
26 27 "#...\n" is a comment up to the end of the line
27 28
28 29 Whitespace between the above elements is ignored.
29 30
30 31 A backref is either
31 32 * a number n, which references the node curr-n, where curr is the current
32 33 node, or
33 34 * the name of a label you placed earlier using ":name", or
34 35 * empty to denote the default parent.
35 36
36 37 All string valued-elements are either strictly alphanumeric, or must
37 38 be enclosed in double quotes ("..."), with "\" as escape character.
38 39
39 40 Generates sequence of
40 41
41 42 ('n', (id, [parentids])) for node creation
42 43 ('l', (id, labelname)) for labels on nodes
43 44 ('a', text) for annotations
44 45 ('c', command) for actions (!)
45 46 ('C', command) for line actions (!!)
46 47
47 48 Examples
48 49 --------
49 50
50 51 Example of a complex graph (output not shown for brevity):
51 52
52 53 >>> len(list(parsedag("""
53 54 ...
54 55 ... +3 # 3 nodes in linear run
55 56 ... :forkhere # a label for the last of the 3 nodes from above
56 57 ... +5 # 5 more nodes on one branch
57 58 ... :mergethis # label again
58 59 ... <forkhere # set default parent to labelled fork node
59 60 ... +10 # 10 more nodes on a parallel branch
60 61 ... @stable # following nodes will be annotated as "stable"
61 62 ... +5 # 5 nodes in stable
62 63 ... !addfile # custom command; could trigger new file in next node
63 64 ... +2 # two more nodes
64 65 ... /mergethis # merge last node with labelled node
65 66 ... +4 # 4 more nodes descending from merge node
66 67 ...
67 68 ... """)))
68 69 34
69 70
70 71 Empty list:
71 72
72 73 >>> list(parsedag(""))
73 74 []
74 75
75 76 A simple linear run:
76 77
77 78 >>> list(parsedag("+3"))
78 79 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
79 80
80 81 Some non-standard ways to define such runs:
81 82
82 83 >>> list(parsedag("+1+2"))
83 84 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
84 85
85 86 >>> list(parsedag("+1*1*"))
86 87 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
87 88
88 89 >>> list(parsedag("*"))
89 90 [('n', (0, [-1]))]
90 91
91 92 >>> list(parsedag("..."))
92 93 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
93 94
94 95 A fork and a join, using numeric back references:
95 96
96 97 >>> list(parsedag("+2*2*/2"))
97 98 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
98 99
99 100 >>> list(parsedag("+2<2+1/2"))
100 101 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
101 102
102 103 Placing a label:
103 104
104 105 >>> list(parsedag("+1 :mylabel +1"))
105 106 [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
106 107
107 108 An empty label (silly, really):
108 109
109 110 >>> list(parsedag("+1:+1"))
110 111 [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
111 112
112 113 Fork and join, but with labels instead of numeric back references:
113 114
114 115 >>> list(parsedag("+1:f +1:p2 *f */p2"))
115 116 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
116 117 ('n', (2, [0])), ('n', (3, [2, 1]))]
117 118
118 119 >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
119 120 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
120 121 ('n', (2, [0])), ('n', (3, [2, 1]))]
121 122
122 123 Restarting from the root:
123 124
124 125 >>> list(parsedag("+1 $ +1"))
125 126 [('n', (0, [-1])), ('n', (1, [-1]))]
126 127
127 128 Annotations, which are meant to introduce sticky state for subsequent nodes:
128 129
129 130 >>> list(parsedag("+1 @ann +1"))
130 131 [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
131 132
132 133 >>> list(parsedag('+1 @"my annotation" +1'))
133 134 [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
134 135
135 136 Commands, which are meant to operate on the most recently created node:
136 137
137 138 >>> list(parsedag("+1 !cmd +1"))
138 139 [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
139 140
140 141 >>> list(parsedag('+1 !"my command" +1'))
141 142 [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
142 143
143 144 >>> list(parsedag('+1 !!my command line\\n +1'))
144 145 [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
145 146
146 147 Comments, which extend to the end of the line:
147 148
148 149 >>> list(parsedag('+1 # comment\\n+1'))
149 150 [('n', (0, [-1])), ('n', (1, [0]))]
150 151
151 152 Error:
152 153
153 154 >>> try: list(parsedag('+1 bad'))
154 155 ... except Exception, e: print e
155 156 invalid character in dag description: bad...
156 157
157 158 '''
158 159 if not desc:
159 160 return
160 161
161 162 wordchars = string.ascii_letters + string.digits
162 163
163 164 labels = {}
164 165 p1 = -1
165 166 r = 0
166 167
167 168 def resolve(ref):
168 169 if not ref:
169 170 return p1
170 171 elif ref[0] in string.digits:
171 172 return r - int(ref)
172 173 else:
173 174 return labels[ref]
174 175
175 176 chiter = (c for c in desc)
176 177
177 178 def nextch():
178 179 try:
179 180 return chiter.next()
180 181 except StopIteration:
181 182 return '\0'
182 183
183 184 def nextrun(c, allow):
184 185 s = ''
185 186 while c in allow:
186 187 s += c
187 188 c = nextch()
188 189 return c, s
189 190
190 191 def nextdelimited(c, limit, escape):
191 192 s = ''
192 193 while c != limit:
193 194 if c == escape:
194 195 c = nextch()
195 196 s += c
196 197 c = nextch()
197 198 return nextch(), s
198 199
199 200 def nextstring(c):
200 201 if c == '"':
201 202 return nextdelimited(nextch(), '"', '\\')
202 203 else:
203 204 return nextrun(c, wordchars)
204 205
205 206 c = nextch()
206 207 while c != '\0':
207 208 while c in string.whitespace:
208 209 c = nextch()
209 210 if c == '.':
210 211 yield 'n', (r, [p1])
211 212 p1 = r
212 213 r += 1
213 214 c = nextch()
214 215 elif c == '+':
215 216 c, digs = nextrun(nextch(), string.digits)
216 217 n = int(digs)
217 218 for i in xrange(0, n):
218 219 yield 'n', (r, [p1])
219 220 p1 = r
220 221 r += 1
221 222 elif c == '*' or c == '/':
222 223 if c == '*':
223 224 c = nextch()
224 225 c, pref = nextstring(c)
225 226 prefs = [pref]
226 227 while c == '/':
227 228 c, pref = nextstring(nextch())
228 229 prefs.append(pref)
229 230 ps = [resolve(ref) for ref in prefs]
230 231 yield 'n', (r, ps)
231 232 p1 = r
232 233 r += 1
233 234 elif c == '<':
234 235 c, ref = nextstring(nextch())
235 236 p1 = resolve(ref)
236 237 elif c == ':':
237 238 c, name = nextstring(nextch())
238 239 labels[name] = p1
239 240 yield 'l', (p1, name)
240 241 elif c == '@':
241 242 c, text = nextstring(nextch())
242 243 yield 'a', text
243 244 elif c == '!':
244 245 c = nextch()
245 246 if c == '!':
246 247 cmd = ''
247 248 c = nextch()
248 249 while c not in '\n\r\0':
249 250 cmd += c
250 251 c = nextch()
251 252 yield 'C', cmd
252 253 else:
253 254 c, cmd = nextstring(c)
254 255 yield 'c', cmd
255 256 elif c == '#':
256 257 while c not in '\n\r\0':
257 258 c = nextch()
258 259 elif c == '$':
259 260 p1 = -1
260 261 c = nextch()
261 262 elif c == '\0':
262 263 return # in case it was preceded by whitespace
263 264 else:
264 265 s = ''
265 266 i = 0
266 267 while c != '\0' and i < 10:
267 268 s += c
268 269 i += 1
269 270 c = nextch()
270 271 raise util.Abort("invalid character in dag description: %s..." % s)
271 272
272 273 def dagtextlines(events,
273 274 addspaces=True,
274 275 wraplabels=False,
275 276 wrapannotations=False,
276 277 wrapcommands=False,
277 278 wrapnonlinear=False,
278 279 usedots=False,
279 280 maxlinewidth=70):
280 281 '''generates single lines for dagtext()'''
281 282
282 283 def wrapstring(text):
283 284 if re.match("^[0-9a-z]*$", text):
284 285 return text
285 286 return '"' + text.replace('\\', '\\\\').replace('"', '\"') + '"'
286 287
287 288 def gen():
288 289 labels = {}
289 290 run = 0
290 291 wantr = 0
291 292 needroot = False
292 293 for kind, data in events:
293 294 if kind == 'n':
294 295 r, ps = data
295 296
296 297 # sanity check
297 298 if r != wantr:
298 299 raise util.Abort("Expected id %i, got %i" % (wantr, r))
299 300 if not ps:
300 301 ps = [-1]
301 302 else:
302 303 for p in ps:
303 304 if p >= r:
304 305 raise util.Abort("Parent id %i is larger than "
305 306 "current id %i" % (p, r))
306 307 wantr += 1
307 308
308 309 # new root?
309 310 p1 = r - 1
310 311 if len(ps) == 1 and ps[0] == -1:
311 312 if needroot:
312 313 if run:
313 314 yield '+' + str(run)
314 315 run = 0
315 316 if wrapnonlinear:
316 317 yield '\n'
317 318 yield '$'
318 319 p1 = -1
319 320 else:
320 321 needroot = True
321 322 if len(ps) == 1 and ps[0] == p1:
322 323 if usedots:
323 324 yield "."
324 325 else:
325 326 run += 1
326 327 else:
327 328 if run:
328 329 yield '+' + str(run)
329 330 run = 0
330 331 if wrapnonlinear:
331 332 yield '\n'
332 333 prefs = []
333 334 for p in ps:
334 335 if p == p1:
335 336 prefs.append('')
336 337 elif p in labels:
337 338 prefs.append(labels[p])
338 339 else:
339 340 prefs.append(str(r - p))
340 341 yield '*' + '/'.join(prefs)
341 342 else:
342 343 if run:
343 344 yield '+' + str(run)
344 345 run = 0
345 346 if kind == 'l':
346 347 rid, name = data
347 348 labels[rid] = name
348 349 yield ':' + name
349 350 if wraplabels:
350 351 yield '\n'
351 352 elif kind == 'c':
352 353 yield '!' + wrapstring(data)
353 354 if wrapcommands:
354 355 yield '\n'
355 356 elif kind == 'C':
356 357 yield '!!' + data
357 358 yield '\n'
358 359 elif kind == 'a':
359 360 if wrapannotations:
360 361 yield '\n'
361 362 yield '@' + wrapstring(data)
362 363 elif kind == '#':
363 364 yield '#' + data
364 365 yield '\n'
365 366 else:
366 367 raise util.Abort(_("invalid event type in dag: %s")
367 368 % str((type, data)))
368 369 if run:
369 370 yield '+' + str(run)
370 371
371 372 line = ''
372 373 for part in gen():
373 374 if part == '\n':
374 375 if line:
375 376 yield line
376 377 line = ''
377 378 else:
378 379 if len(line) + len(part) >= maxlinewidth:
379 380 yield line
380 381 line = ''
381 382 elif addspaces and line and part != '.':
382 383 line += ' '
383 384 line += part
384 385 if line:
385 386 yield line
386 387
387 388 def dagtext(dag,
388 389 addspaces=True,
389 390 wraplabels=False,
390 391 wrapannotations=False,
391 392 wrapcommands=False,
392 393 wrapnonlinear=False,
393 394 usedots=False,
394 395 maxlinewidth=70):
395 396 '''generates lines of a textual representation for a dag event stream
396 397
397 398 events should generate what parsedag() does, so:
398 399
399 400 ('n', (id, [parentids])) for node creation
400 401 ('l', (id, labelname)) for labels on nodes
401 402 ('a', text) for annotations
402 403 ('c', text) for commands
403 404 ('C', text) for line commands ('!!')
404 405 ('#', text) for comment lines
405 406
406 407 Parent nodes must come before child nodes.
407 408
408 409 Examples
409 410 --------
410 411
411 412 Linear run:
412 413
413 414 >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
414 415 '+2'
415 416
416 417 Two roots:
417 418
418 419 >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
419 420 '+1 $ +1'
420 421
421 422 Fork and join:
422 423
423 424 >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
424 425 ... ('n', (3, [2, 1]))])
425 426 '+2 *2 */2'
426 427
427 428 Fork and join with labels:
428 429
429 430 >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
430 431 ... ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
431 432 '+1 :f +1 :p2 *f */p2'
432 433
433 434 Annotations:
434 435
435 436 >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
436 437 '+1 @ann +1'
437 438
438 439 >>> dagtext([('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))])
439 440 '+1 @"my annotation" +1'
440 441
441 442 Commands:
442 443
443 444 >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
444 445 '+1 !cmd +1'
445 446
446 447 >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
447 448 '+1 !"my command" +1'
448 449
449 450 >>> dagtext([('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))])
450 451 '+1 !!my command line\\n+1'
451 452
452 453 Comments:
453 454
454 455 >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
455 456 '+1 # comment\\n+1'
456 457
457 458 >>> dagtext([])
458 459 ''
459 460
460 461 Combining parsedag and dagtext:
461 462
462 463 >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
463 464 '+1 :f +1 :p2 *f */p2'
464 465
465 466 '''
466 467 return "\n".join(dagtextlines(dag,
467 468 addspaces,
468 469 wraplabels,
469 470 wrapannotations,
470 471 wrapcommands,
471 472 wrapnonlinear,
472 473 usedots,
473 474 maxlinewidth))
@@ -1,332 +1,332 b''
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 import urllib, tempfile, os
8 import urllib, tempfile, os, sys
9 9 from i18n import _
10 10 from node import bin, hex
11 11 import changegroup as changegroupmod
12 12 import repo, error, encoding, util, store
13 13 import pushkey as pushkey_
14 14
15 15 # list of nodes encoding / decoding
16 16
17 17 def decodelist(l, sep=' '):
18 18 return map(bin, l.split(sep))
19 19
20 20 def encodelist(l, sep=' '):
21 21 return sep.join(map(hex, l))
22 22
23 23 # client side
24 24
25 25 class wirerepository(repo.repository):
26 26 def lookup(self, key):
27 27 self.requirecap('lookup', _('look up remote revision'))
28 28 d = self._call("lookup", key=key)
29 29 success, data = d[:-1].split(" ", 1)
30 30 if int(success):
31 31 return bin(data)
32 32 self._abort(error.RepoError(data))
33 33
34 34 def heads(self):
35 35 d = self._call("heads")
36 36 try:
37 37 return decodelist(d[:-1])
38 38 except:
39 self.abort(error.ResponseError(_("unexpected response:"), d))
39 self._abort(error.ResponseError(_("unexpected response:"), d))
40 40
41 41 def branchmap(self):
42 42 d = self._call("branchmap")
43 43 try:
44 44 branchmap = {}
45 45 for branchpart in d.splitlines():
46 46 branchname, branchheads = branchpart.split(' ', 1)
47 47 branchname = urllib.unquote(branchname)
48 48 # Earlier servers (1.3.x) send branch names in (their) local
49 49 # charset. The best we can do is assume it's identical to our
50 50 # own local charset, in case it's not utf-8.
51 51 try:
52 52 branchname.decode('utf-8')
53 53 except UnicodeDecodeError:
54 54 branchname = encoding.fromlocal(branchname)
55 55 branchheads = decodelist(branchheads)
56 56 branchmap[branchname] = branchheads
57 57 return branchmap
58 58 except TypeError:
59 59 self._abort(error.ResponseError(_("unexpected response:"), d))
60 60
61 61 def branches(self, nodes):
62 62 n = encodelist(nodes)
63 63 d = self._call("branches", nodes=n)
64 64 try:
65 65 br = [tuple(decodelist(b)) for b in d.splitlines()]
66 66 return br
67 67 except:
68 68 self._abort(error.ResponseError(_("unexpected response:"), d))
69 69
70 70 def between(self, pairs):
71 71 batch = 8 # avoid giant requests
72 72 r = []
73 73 for i in xrange(0, len(pairs), batch):
74 74 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
75 75 d = self._call("between", pairs=n)
76 76 try:
77 77 r.extend(l and decodelist(l) or [] for l in d.splitlines())
78 78 except:
79 79 self._abort(error.ResponseError(_("unexpected response:"), d))
80 80 return r
81 81
82 82 def pushkey(self, namespace, key, old, new):
83 83 if not self.capable('pushkey'):
84 84 return False
85 85 d = self._call("pushkey",
86 86 namespace=namespace, key=key, old=old, new=new)
87 87 return bool(int(d))
88 88
89 89 def listkeys(self, namespace):
90 90 if not self.capable('pushkey'):
91 91 return {}
92 92 d = self._call("listkeys", namespace=namespace)
93 93 r = {}
94 94 for l in d.splitlines():
95 95 k, v = l.split('\t')
96 96 r[k.decode('string-escape')] = v.decode('string-escape')
97 97 return r
98 98
99 99 def stream_out(self):
100 100 return self._callstream('stream_out')
101 101
102 102 def changegroup(self, nodes, kind):
103 103 n = encodelist(nodes)
104 104 f = self._callstream("changegroup", roots=n)
105 105 return self._decompress(f)
106 106
107 107 def changegroupsubset(self, bases, heads, kind):
108 108 self.requirecap('changegroupsubset', _('look up remote changes'))
109 109 bases = encodelist(bases)
110 110 heads = encodelist(heads)
111 111 return self._decompress(self._callstream("changegroupsubset",
112 112 bases=bases, heads=heads))
113 113
114 114 def unbundle(self, cg, heads, source):
115 115 '''Send cg (a readable file-like object representing the
116 116 changegroup to push, typically a chunkbuffer object) to the
117 117 remote server as a bundle. Return an integer indicating the
118 118 result of the push (see localrepository.addchangegroup()).'''
119 119
120 120 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
121 121 if ret == "":
122 122 raise error.ResponseError(
123 123 _('push failed:'), output)
124 124 try:
125 125 ret = int(ret)
126 126 except ValueError, err:
127 127 raise error.ResponseError(
128 128 _('push failed (unexpected response):'), ret)
129 129
130 130 for l in output.splitlines(True):
131 131 self.ui.status(_('remote: '), l)
132 132 return ret
133 133
134 134 # server side
135 135
136 136 class streamres(object):
137 137 def __init__(self, gen):
138 138 self.gen = gen
139 139
140 140 class pushres(object):
141 141 def __init__(self, res):
142 142 self.res = res
143 143
144 144 def dispatch(repo, proto, command):
145 145 func, spec = commands[command]
146 146 args = proto.getargs(spec)
147 147 return func(repo, proto, *args)
148 148
149 149 def between(repo, proto, pairs):
150 150 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
151 151 r = []
152 152 for b in repo.between(pairs):
153 153 r.append(encodelist(b) + "\n")
154 154 return "".join(r)
155 155
156 156 def branchmap(repo, proto):
157 157 branchmap = repo.branchmap()
158 158 heads = []
159 159 for branch, nodes in branchmap.iteritems():
160 160 branchname = urllib.quote(branch)
161 161 branchnodes = encodelist(nodes)
162 162 heads.append('%s %s' % (branchname, branchnodes))
163 163 return '\n'.join(heads)
164 164
165 165 def branches(repo, proto, nodes):
166 166 nodes = decodelist(nodes)
167 167 r = []
168 168 for b in repo.branches(nodes):
169 169 r.append(encodelist(b) + "\n")
170 170 return "".join(r)
171 171
172 172 def capabilities(repo, proto):
173 173 caps = 'lookup changegroupsubset branchmap pushkey'.split()
174 174 if _allowstream(repo.ui):
175 175 caps.append('stream=%d' % repo.changelog.version)
176 176 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
177 177 return ' '.join(caps)
178 178
179 179 def changegroup(repo, proto, roots):
180 180 nodes = decodelist(roots)
181 181 cg = repo.changegroup(nodes, 'serve')
182 182 return streamres(proto.groupchunks(cg))
183 183
184 184 def changegroupsubset(repo, proto, bases, heads):
185 185 bases = decodelist(bases)
186 186 heads = decodelist(heads)
187 187 cg = repo.changegroupsubset(bases, heads, 'serve')
188 188 return streamres(proto.groupchunks(cg))
189 189
190 190 def heads(repo, proto):
191 191 h = repo.heads()
192 192 return encodelist(h) + "\n"
193 193
194 194 def hello(repo, proto):
195 195 '''the hello command returns a set of lines describing various
196 196 interesting things about the server, in an RFC822-like format.
197 197 Currently the only one defined is "capabilities", which
198 198 consists of a line in the form:
199 199
200 200 capabilities: space separated list of tokens
201 201 '''
202 202 return "capabilities: %s\n" % (capabilities(repo, proto))
203 203
204 204 def listkeys(repo, proto, namespace):
205 205 d = pushkey_.list(repo, namespace).items()
206 206 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
207 207 v.encode('string-escape')) for k, v in d])
208 208 return t
209 209
210 210 def lookup(repo, proto, key):
211 211 try:
212 212 r = hex(repo.lookup(key))
213 213 success = 1
214 214 except Exception, inst:
215 215 r = str(inst)
216 216 success = 0
217 217 return "%s %s\n" % (success, r)
218 218
219 219 def pushkey(repo, proto, namespace, key, old, new):
220 220 r = pushkey_.push(repo, namespace, key, old, new)
221 221 return '%s\n' % int(r)
222 222
223 223 def _allowstream(ui):
224 224 return ui.configbool('server', 'uncompressed', True, untrusted=True)
225 225
226 226 def stream(repo, proto):
227 227 '''If the server supports streaming clone, it advertises the "stream"
228 228 capability with a value representing the version and flags of the repo
229 229 it is serving. Client checks to see if it understands the format.
230 230
231 231 The format is simple: the server writes out a line with the amount
232 232 of files, then the total amount of bytes to be transfered (separated
233 233 by a space). Then, for each file, the server first writes the filename
234 234 and filesize (separated by the null character), then the file contents.
235 235 '''
236 236
237 237 if not _allowstream(repo.ui):
238 238 return '1\n'
239 239
240 240 entries = []
241 241 total_bytes = 0
242 242 try:
243 243 # get consistent snapshot of repo, lock during scan
244 244 lock = repo.lock()
245 245 try:
246 246 repo.ui.debug('scanning\n')
247 247 for name, ename, size in repo.store.walk():
248 248 entries.append((name, size))
249 249 total_bytes += size
250 250 finally:
251 251 lock.release()
252 252 except error.LockError:
253 253 return '2\n' # error: 2
254 254
255 255 def streamer(repo, entries, total):
256 256 '''stream out all metadata files in repository.'''
257 257 yield '0\n' # success
258 258 repo.ui.debug('%d files, %d bytes to transfer\n' %
259 259 (len(entries), total_bytes))
260 260 yield '%d %d\n' % (len(entries), total_bytes)
261 261 for name, size in entries:
262 262 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
263 263 # partially encode name over the wire for backwards compat
264 264 yield '%s\0%d\n' % (store.encodedir(name), size)
265 265 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
266 266 yield chunk
267 267
268 268 return streamres(streamer(repo, entries, total_bytes))
269 269
270 270 def unbundle(repo, proto, heads):
271 271 their_heads = decodelist(heads)
272 272
273 273 def check_heads():
274 274 heads = repo.heads()
275 275 return their_heads == ['force'] or their_heads == heads
276 276
277 277 # fail early if possible
278 278 if not check_heads():
279 279 return 'unsynced changes'
280 280
281 281 # write bundle data to temporary file because it can be big
282 282 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
283 283 fp = os.fdopen(fd, 'wb+')
284 284 r = 0
285 285 proto.redirect()
286 286 try:
287 287 proto.getfile(fp)
288 288 lock = repo.lock()
289 289 try:
290 290 if not check_heads():
291 291 # someone else committed/pushed/unbundled while we
292 292 # were transferring data
293 293 return 'unsynced changes'
294 294
295 295 # push can proceed
296 296 fp.seek(0)
297 297 header = fp.read(6)
298 298 if header.startswith('HG'):
299 299 if not header.startswith('HG10'):
300 300 raise ValueError('unknown bundle version')
301 301 elif header not in changegroupmod.bundletypes:
302 302 raise ValueError('unknown bundle compression type')
303 303 gen = changegroupmod.unbundle(header, fp)
304 304
305 305 try:
306 306 r = repo.addchangegroup(gen, 'serve', proto._client(),
307 307 lock=lock)
308 308 except util.Abort, inst:
309 309 sys.stderr.write("abort: %s\n" % inst)
310 310 finally:
311 311 lock.release()
312 312 return pushres(r)
313 313
314 314 finally:
315 315 fp.close()
316 316 os.unlink(tempname)
317 317
318 318 commands = {
319 319 'between': (between, 'pairs'),
320 320 'branchmap': (branchmap, ''),
321 321 'branches': (branches, 'nodes'),
322 322 'capabilities': (capabilities, ''),
323 323 'changegroup': (changegroup, 'roots'),
324 324 'changegroupsubset': (changegroupsubset, 'bases heads'),
325 325 'heads': (heads, ''),
326 326 'hello': (hello, ''),
327 327 'listkeys': (listkeys, 'namespace'),
328 328 'lookup': (lookup, 'key'),
329 329 'pushkey': (pushkey, 'namespace key old new'),
330 330 'stream_out': (stream, ''),
331 331 'unbundle': (unbundle, 'heads'),
332 332 }
General Comments 0
You need to be logged in to leave comments. Login now