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