##// END OF EJS Templates
dagwalker: drop a useless intermediate variable...
Pierre-Yves David -
r23006:bb1bd9ee default
parent child Browse files
Show More
@@ -1,368 +1,367 b''
1 1 # Revision graph generator for Mercurial
2 2 #
3 3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
4 4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """supports walking the history as DAGs suitable for graphical output
10 10
11 11 The most basic format we use is that of::
12 12
13 13 (id, type, data, [parentids])
14 14
15 15 The node and parent ids are arbitrary integers which identify a node in the
16 16 context of the graph returned. Type is a constant specifying the node type.
17 17 Data depends on type.
18 18 """
19 19
20 20 from mercurial.node import nullrev
21 21 import util
22 22
23 23 CHANGESET = 'C'
24 24
25 25 def dagwalker(repo, revs):
26 26 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
27 27
28 28 This generator function walks through revisions (which should be ordered
29 29 from bigger to lower). It returns a tuple for each node. The node and parent
30 30 ids are arbitrary integers which identify a node in the context of the graph
31 31 returned.
32 32 """
33 33 if not revs:
34 34 return
35 35
36 36 cl = repo.changelog
37 37 lowestrev = revs.min()
38 38 gpcache = {}
39 39
40 knownrevs = revs
41 40 for rev in revs:
42 41 ctx = repo[rev]
43 42 parents = sorted(set([p.rev() for p in ctx.parents()
44 if p.rev() in knownrevs]))
43 if p.rev() in revs]))
45 44 mpars = [p.rev() for p in ctx.parents() if
46 45 p.rev() != nullrev and p.rev() not in parents]
47 46
48 47 for mpar in mpars:
49 48 gp = gpcache.get(mpar)
50 49 if gp is None:
51 50 gp = gpcache[mpar] = grandparent(cl, lowestrev, revs, mpar)
52 51 if not gp:
53 52 parents.append(mpar)
54 53 else:
55 54 parents.extend(g for g in gp if g not in parents)
56 55
57 56 yield (ctx.rev(), CHANGESET, ctx, parents)
58 57
59 58 def nodes(repo, nodes):
60 59 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
61 60
62 61 This generator function walks the given nodes. It only returns parents
63 62 that are in nodes, too.
64 63 """
65 64 include = set(nodes)
66 65 for node in nodes:
67 66 ctx = repo[node]
68 67 parents = set([p.rev() for p in ctx.parents() if p.node() in include])
69 68 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
70 69
71 70 def colored(dag, repo):
72 71 """annotates a DAG with colored edge information
73 72
74 73 For each DAG node this function emits tuples::
75 74
76 75 (id, type, data, (col, color), [(col, nextcol, color)])
77 76
78 77 with the following new elements:
79 78
80 79 - Tuple (col, color) with column and color index for the current node
81 80 - A list of tuples indicating the edges between the current node and its
82 81 parents.
83 82 """
84 83 seen = []
85 84 colors = {}
86 85 newcolor = 1
87 86 config = {}
88 87
89 88 for key, val in repo.ui.configitems('graph'):
90 89 if '.' in key:
91 90 branch, setting = key.rsplit('.', 1)
92 91 # Validation
93 92 if setting == "width" and val.isdigit():
94 93 config.setdefault(branch, {})[setting] = int(val)
95 94 elif setting == "color" and val.isalnum():
96 95 config.setdefault(branch, {})[setting] = val
97 96
98 97 if config:
99 98 getconf = util.lrucachefunc(
100 99 lambda rev: config.get(repo[rev].branch(), {}))
101 100 else:
102 101 getconf = lambda rev: {}
103 102
104 103 for (cur, type, data, parents) in dag:
105 104
106 105 # Compute seen and next
107 106 if cur not in seen:
108 107 seen.append(cur) # new head
109 108 colors[cur] = newcolor
110 109 newcolor += 1
111 110
112 111 col = seen.index(cur)
113 112 color = colors.pop(cur)
114 113 next = seen[:]
115 114
116 115 # Add parents to next
117 116 addparents = [p for p in parents if p not in next]
118 117 next[col:col + 1] = addparents
119 118
120 119 # Set colors for the parents
121 120 for i, p in enumerate(addparents):
122 121 if not i:
123 122 colors[p] = color
124 123 else:
125 124 colors[p] = newcolor
126 125 newcolor += 1
127 126
128 127 # Add edges to the graph
129 128 edges = []
130 129 for ecol, eid in enumerate(seen):
131 130 if eid in next:
132 131 bconf = getconf(eid)
133 132 edges.append((
134 133 ecol, next.index(eid), colors[eid],
135 134 bconf.get('width', -1),
136 135 bconf.get('color', '')))
137 136 elif eid == cur:
138 137 for p in parents:
139 138 bconf = getconf(p)
140 139 edges.append((
141 140 ecol, next.index(p), color,
142 141 bconf.get('width', -1),
143 142 bconf.get('color', '')))
144 143
145 144 # Yield and move on
146 145 yield (cur, type, data, (col, color), edges)
147 146 seen = next
148 147
149 148 def grandparent(cl, lowestrev, roots, head):
150 149 """Return all ancestors of head in roots which revision is
151 150 greater or equal to lowestrev.
152 151 """
153 152 pending = set([head])
154 153 seen = set()
155 154 kept = set()
156 155 llowestrev = max(nullrev, lowestrev)
157 156 while pending:
158 157 r = pending.pop()
159 158 if r >= llowestrev and r not in seen:
160 159 if r in roots:
161 160 kept.add(r)
162 161 else:
163 162 pending.update([p for p in cl.parentrevs(r)])
164 163 seen.add(r)
165 164 return sorted(kept)
166 165
167 166 def asciiedges(type, char, lines, seen, rev, parents):
168 167 """adds edge info to changelog DAG walk suitable for ascii()"""
169 168 if rev not in seen:
170 169 seen.append(rev)
171 170 nodeidx = seen.index(rev)
172 171
173 172 knownparents = []
174 173 newparents = []
175 174 for parent in parents:
176 175 if parent in seen:
177 176 knownparents.append(parent)
178 177 else:
179 178 newparents.append(parent)
180 179
181 180 ncols = len(seen)
182 181 nextseen = seen[:]
183 182 nextseen[nodeidx:nodeidx + 1] = newparents
184 183 edges = [(nodeidx, nextseen.index(p)) for p in knownparents if p != nullrev]
185 184
186 185 while len(newparents) > 2:
187 186 # ascii() only knows how to add or remove a single column between two
188 187 # calls. Nodes with more than two parents break this constraint so we
189 188 # introduce intermediate expansion lines to grow the active node list
190 189 # slowly.
191 190 edges.append((nodeidx, nodeidx))
192 191 edges.append((nodeidx, nodeidx + 1))
193 192 nmorecols = 1
194 193 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
195 194 char = '\\'
196 195 lines = []
197 196 nodeidx += 1
198 197 ncols += 1
199 198 edges = []
200 199 del newparents[0]
201 200
202 201 if len(newparents) > 0:
203 202 edges.append((nodeidx, nodeidx))
204 203 if len(newparents) > 1:
205 204 edges.append((nodeidx, nodeidx + 1))
206 205 nmorecols = len(nextseen) - ncols
207 206 seen[:] = nextseen
208 207 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
209 208
210 209 def _fixlongrightedges(edges):
211 210 for (i, (start, end)) in enumerate(edges):
212 211 if end > start:
213 212 edges[i] = (start, end + 1)
214 213
215 214 def _getnodelineedgestail(
216 215 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
217 216 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
218 217 # Still going in the same non-vertical direction.
219 218 if n_columns_diff == -1:
220 219 start = max(node_index + 1, p_node_index)
221 220 tail = ["|", " "] * (start - node_index - 1)
222 221 tail.extend(["/", " "] * (n_columns - start))
223 222 return tail
224 223 else:
225 224 return ["\\", " "] * (n_columns - node_index - 1)
226 225 else:
227 226 return ["|", " "] * (n_columns - node_index - 1)
228 227
229 228 def _drawedges(edges, nodeline, interline):
230 229 for (start, end) in edges:
231 230 if start == end + 1:
232 231 interline[2 * end + 1] = "/"
233 232 elif start == end - 1:
234 233 interline[2 * start + 1] = "\\"
235 234 elif start == end:
236 235 interline[2 * start] = "|"
237 236 else:
238 237 if 2 * end >= len(nodeline):
239 238 continue
240 239 nodeline[2 * end] = "+"
241 240 if start > end:
242 241 (start, end) = (end, start)
243 242 for i in range(2 * start + 1, 2 * end):
244 243 if nodeline[i] != "+":
245 244 nodeline[i] = "-"
246 245
247 246 def _getpaddingline(ni, n_columns, edges):
248 247 line = []
249 248 line.extend(["|", " "] * ni)
250 249 if (ni, ni - 1) in edges or (ni, ni) in edges:
251 250 # (ni, ni - 1) (ni, ni)
252 251 # | | | | | | | |
253 252 # +---o | | o---+
254 253 # | | c | | c | |
255 254 # | |/ / | |/ /
256 255 # | | | | | |
257 256 c = "|"
258 257 else:
259 258 c = " "
260 259 line.extend([c, " "])
261 260 line.extend(["|", " "] * (n_columns - ni - 1))
262 261 return line
263 262
264 263 def asciistate():
265 264 """returns the initial value for the "state" argument to ascii()"""
266 265 return [0, 0]
267 266
268 267 def ascii(ui, state, type, char, text, coldata):
269 268 """prints an ASCII graph of the DAG
270 269
271 270 takes the following arguments (one call per node in the graph):
272 271
273 272 - ui to write to
274 273 - Somewhere to keep the needed state in (init to asciistate())
275 274 - Column of the current node in the set of ongoing edges.
276 275 - Type indicator of node data, usually 'C' for changesets.
277 276 - Payload: (char, lines):
278 277 - Character to use as node's symbol.
279 278 - List of lines to display as the node's text.
280 279 - Edges; a list of (col, next_col) indicating the edges between
281 280 the current node and its parents.
282 281 - Number of columns (ongoing edges) in the current revision.
283 282 - The difference between the number of columns (ongoing edges)
284 283 in the next revision and the number of columns (ongoing edges)
285 284 in the current revision. That is: -1 means one column removed;
286 285 0 means no columns added or removed; 1 means one column added.
287 286 """
288 287
289 288 idx, edges, ncols, coldiff = coldata
290 289 assert -2 < coldiff < 2
291 290 if coldiff == -1:
292 291 # Transform
293 292 #
294 293 # | | | | | |
295 294 # o | | into o---+
296 295 # |X / |/ /
297 296 # | | | |
298 297 _fixlongrightedges(edges)
299 298
300 299 # add_padding_line says whether to rewrite
301 300 #
302 301 # | | | | | | | |
303 302 # | o---+ into | o---+
304 303 # | / / | | | # <--- padding line
305 304 # o | | | / /
306 305 # o | |
307 306 add_padding_line = (len(text) > 2 and coldiff == -1 and
308 307 [x for (x, y) in edges if x + 1 < y])
309 308
310 309 # fix_nodeline_tail says whether to rewrite
311 310 #
312 311 # | | o | | | | o | |
313 312 # | | |/ / | | |/ /
314 313 # | o | | into | o / / # <--- fixed nodeline tail
315 314 # | |/ / | |/ /
316 315 # o | | o | |
317 316 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
318 317
319 318 # nodeline is the line containing the node character (typically o)
320 319 nodeline = ["|", " "] * idx
321 320 nodeline.extend([char, " "])
322 321
323 322 nodeline.extend(
324 323 _getnodelineedgestail(idx, state[1], ncols, coldiff,
325 324 state[0], fix_nodeline_tail))
326 325
327 326 # shift_interline is the line containing the non-vertical
328 327 # edges between this entry and the next
329 328 shift_interline = ["|", " "] * idx
330 329 if coldiff == -1:
331 330 n_spaces = 1
332 331 edge_ch = "/"
333 332 elif coldiff == 0:
334 333 n_spaces = 2
335 334 edge_ch = "|"
336 335 else:
337 336 n_spaces = 3
338 337 edge_ch = "\\"
339 338 shift_interline.extend(n_spaces * [" "])
340 339 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
341 340
342 341 # draw edges from the current node to its parents
343 342 _drawedges(edges, nodeline, shift_interline)
344 343
345 344 # lines is the list of all graph lines to print
346 345 lines = [nodeline]
347 346 if add_padding_line:
348 347 lines.append(_getpaddingline(idx, ncols, edges))
349 348 lines.append(shift_interline)
350 349
351 350 # make sure that there are as many graph lines as there are
352 351 # log strings
353 352 while len(text) < len(lines):
354 353 text.append("")
355 354 if len(lines) < len(text):
356 355 extra_interline = ["|", " "] * (ncols + coldiff)
357 356 while len(lines) < len(text):
358 357 lines.append(extra_interline)
359 358
360 359 # print lines
361 360 indentation_level = max(ncols, ncols + coldiff)
362 361 for (line, logstr) in zip(lines, text):
363 362 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
364 363 ui.write(ln.rstrip() + '\n')
365 364
366 365 # ... and start over
367 366 state[0] = coldiff
368 367 state[1] = idx
General Comments 0
You need to be logged in to leave comments. Login now