comparison mercurial/graphmod.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 264a2cbb25d0
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
35 # point. A number prefix means only the last N characters of the current block 35 # point. A number prefix means only the last N characters of the current block
36 # will use that style, the rest will use the PARENT style. Add a - sign 36 # will use that style, the rest will use the PARENT style. Add a - sign
37 # (so making N negative) and all but the first N characters use that style. 37 # (so making N negative) and all but the first N characters use that style.
38 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None} 38 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
39 39
40
40 def dagwalker(repo, revs): 41 def dagwalker(repo, revs):
41 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples 42 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
42 43
43 This generator function walks through revisions (which should be ordered 44 This generator function walks through revisions (which should be ordered
44 from bigger to lower). It returns a tuple for each node. 45 from bigger to lower). It returns a tuple for each node.
55 ctx = repo[rev] 56 ctx = repo[rev]
56 # partition into parents in the rev set and missing parents, then 57 # partition into parents in the rev set and missing parents, then
57 # augment the lists with markers, to inform graph drawing code about 58 # augment the lists with markers, to inform graph drawing code about
58 # what kind of edge to draw between nodes. 59 # what kind of edge to draw between nodes.
59 pset = set(p.rev() for p in ctx.parents() if p.rev() in revs) 60 pset = set(p.rev() for p in ctx.parents() if p.rev() in revs)
60 mpars = [p.rev() for p in ctx.parents() 61 mpars = [
61 if p.rev() != nullrev and p.rev() not in pset] 62 p.rev()
63 for p in ctx.parents()
64 if p.rev() != nullrev and p.rev() not in pset
65 ]
62 parents = [(PARENT, p) for p in sorted(pset)] 66 parents = [(PARENT, p) for p in sorted(pset)]
63 67
64 for mpar in mpars: 68 for mpar in mpars:
65 gp = gpcache.get(mpar) 69 gp = gpcache.get(mpar)
66 if gp is None: 70 if gp is None:
67 # precompute slow query as we know reachableroots() goes 71 # precompute slow query as we know reachableroots() goes
68 # through all revs (issue4782) 72 # through all revs (issue4782)
69 if not isinstance(revs, smartset.baseset): 73 if not isinstance(revs, smartset.baseset):
70 revs = smartset.baseset(revs) 74 revs = smartset.baseset(revs)
71 gp = gpcache[mpar] = sorted(set(dagop.reachableroots( 75 gp = gpcache[mpar] = sorted(
72 repo, revs, [mpar]))) 76 set(dagop.reachableroots(repo, revs, [mpar]))
77 )
73 if not gp: 78 if not gp:
74 parents.append((MISSINGPARENT, mpar)) 79 parents.append((MISSINGPARENT, mpar))
75 pset.add(mpar) 80 pset.add(mpar)
76 else: 81 else:
77 parents.extend((GRANDPARENT, g) for g in gp if g not in pset) 82 parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
78 pset.update(gp) 83 pset.update(gp)
79 84
80 yield (ctx.rev(), CHANGESET, ctx, parents) 85 yield (ctx.rev(), CHANGESET, ctx, parents)
81 86
87
82 def nodes(repo, nodes): 88 def nodes(repo, nodes):
83 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples 89 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
84 90
85 This generator function walks the given nodes. It only returns parents 91 This generator function walks the given nodes. It only returns parents
86 that are in nodes, too. 92 that are in nodes, too.
87 """ 93 """
88 include = set(nodes) 94 include = set(nodes)
89 for node in nodes: 95 for node in nodes:
90 ctx = repo[node] 96 ctx = repo[node]
91 parents = set((PARENT, p.rev()) for p in ctx.parents() 97 parents = set(
92 if p.node() in include) 98 (PARENT, p.rev()) for p in ctx.parents() if p.node() in include
99 )
93 yield (ctx.rev(), CHANGESET, ctx, sorted(parents)) 100 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
101
94 102
95 def colored(dag, repo): 103 def colored(dag, repo):
96 """annotates a DAG with colored edge information 104 """annotates a DAG with colored edge information
97 105
98 For each DAG node this function emits tuples:: 106 For each DAG node this function emits tuples::
119 elif setting == "color" and val.isalnum(): 127 elif setting == "color" and val.isalnum():
120 config.setdefault(branch, {})[setting] = val 128 config.setdefault(branch, {})[setting] = val
121 129
122 if config: 130 if config:
123 getconf = util.lrucachefunc( 131 getconf = util.lrucachefunc(
124 lambda rev: config.get(repo[rev].branch(), {})) 132 lambda rev: config.get(repo[rev].branch(), {})
133 )
125 else: 134 else:
126 getconf = lambda rev: {} 135 getconf = lambda rev: {}
127 136
128 for (cur, type, data, parents) in dag: 137 for (cur, type, data, parents) in dag:
129 138
130 # Compute seen and next 139 # Compute seen and next
131 if cur not in seen: 140 if cur not in seen:
132 seen.append(cur) # new head 141 seen.append(cur) # new head
133 colors[cur] = newcolor 142 colors[cur] = newcolor
134 newcolor += 1 143 newcolor += 1
135 144
136 col = seen.index(cur) 145 col = seen.index(cur)
137 color = colors.pop(cur) 146 color = colors.pop(cur)
138 next = seen[:] 147 next = seen[:]
139 148
140 # Add parents to next 149 # Add parents to next
141 addparents = [p for pt, p in parents if p not in next] 150 addparents = [p for pt, p in parents if p not in next]
142 next[col:col + 1] = addparents 151 next[col : col + 1] = addparents
143 152
144 # Set colors for the parents 153 # Set colors for the parents
145 for i, p in enumerate(addparents): 154 for i, p in enumerate(addparents):
146 if not i: 155 if not i:
147 colors[p] = color 156 colors[p] = color
152 # Add edges to the graph 161 # Add edges to the graph
153 edges = [] 162 edges = []
154 for ecol, eid in enumerate(seen): 163 for ecol, eid in enumerate(seen):
155 if eid in next: 164 if eid in next:
156 bconf = getconf(eid) 165 bconf = getconf(eid)
157 edges.append(( 166 edges.append(
158 ecol, next.index(eid), colors[eid], 167 (
159 bconf.get('width', -1), 168 ecol,
160 bconf.get('color', ''))) 169 next.index(eid),
170 colors[eid],
171 bconf.get('width', -1),
172 bconf.get('color', ''),
173 )
174 )
161 elif eid == cur: 175 elif eid == cur:
162 for ptype, p in parents: 176 for ptype, p in parents:
163 bconf = getconf(p) 177 bconf = getconf(p)
164 edges.append(( 178 edges.append(
165 ecol, next.index(p), color, 179 (
166 bconf.get('width', -1), 180 ecol,
167 bconf.get('color', ''))) 181 next.index(p),
182 color,
183 bconf.get('width', -1),
184 bconf.get('color', ''),
185 )
186 )
168 187
169 # Yield and move on 188 # Yield and move on
170 yield (cur, type, data, (col, color), edges) 189 yield (cur, type, data, (col, color), edges)
171 seen = next 190 seen = next
191
172 192
173 def asciiedges(type, char, state, rev, parents): 193 def asciiedges(type, char, state, rev, parents):
174 """adds edge info to changelog DAG walk suitable for ascii()""" 194 """adds edge info to changelog DAG walk suitable for ascii()"""
175 seen = state['seen'] 195 seen = state['seen']
176 if rev not in seen: 196 if rev not in seen:
190 state['edges'][parent] = state['styles'].get(ptype, '|') 210 state['edges'][parent] = state['styles'].get(ptype, '|')
191 211
192 ncols = len(seen) 212 ncols = len(seen)
193 width = 1 + ncols * 2 213 width = 1 + ncols * 2
194 nextseen = seen[:] 214 nextseen = seen[:]
195 nextseen[nodeidx:nodeidx + 1] = newparents 215 nextseen[nodeidx : nodeidx + 1] = newparents
196 edges = [(nodeidx, nextseen.index(p)) for p in knownparents] 216 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
197 217
198 seen[:] = nextseen 218 seen[:] = nextseen
199 while len(newparents) > 2: 219 while len(newparents) > 2:
200 # ascii() only knows how to add or remove a single column between two 220 # ascii() only knows how to add or remove a single column between two
221 width += 2 241 width += 2
222 # remove current node from edge characters, no longer needed 242 # remove current node from edge characters, no longer needed
223 state['edges'].pop(rev, None) 243 state['edges'].pop(rev, None)
224 yield (type, char, width, (nodeidx, edges, ncols, nmorecols)) 244 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
225 245
246
226 def _fixlongrightedges(edges): 247 def _fixlongrightedges(edges):
227 for (i, (start, end)) in enumerate(edges): 248 for (i, (start, end)) in enumerate(edges):
228 if end > start: 249 if end > start:
229 edges[i] = (start, end + 1) 250 edges[i] = (start, end + 1)
230 251
231 def _getnodelineedgestail( 252
232 echars, idx, pidx, ncols, coldiff, pdiff, fix_tail): 253 def _getnodelineedgestail(echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
233 if fix_tail and coldiff == pdiff and coldiff != 0: 254 if fix_tail and coldiff == pdiff and coldiff != 0:
234 # Still going in the same non-vertical direction. 255 # Still going in the same non-vertical direction.
235 if coldiff == -1: 256 if coldiff == -1:
236 start = max(idx + 1, pidx) 257 start = max(idx + 1, pidx)
237 tail = echars[idx * 2:(start - 1) * 2] 258 tail = echars[idx * 2 : (start - 1) * 2]
238 tail.extend(["/", " "] * (ncols - start)) 259 tail.extend(["/", " "] * (ncols - start))
239 return tail 260 return tail
240 else: 261 else:
241 return ["\\", " "] * (ncols - idx - 1) 262 return ["\\", " "] * (ncols - idx - 1)
242 else: 263 else:
243 remainder = (ncols - idx - 1) 264 remainder = ncols - idx - 1
244 return echars[-(remainder * 2):] if remainder > 0 else [] 265 return echars[-(remainder * 2) :] if remainder > 0 else []
266
245 267
246 def _drawedges(echars, edges, nodeline, interline): 268 def _drawedges(echars, edges, nodeline, interline):
247 for (start, end) in edges: 269 for (start, end) in edges:
248 if start == end + 1: 270 if start == end + 1:
249 interline[2 * end + 1] = "/" 271 interline[2 * end + 1] = "/"
259 (start, end) = (end, start) 281 (start, end) = (end, start)
260 for i in range(2 * start + 1, 2 * end): 282 for i in range(2 * start + 1, 2 * end):
261 if nodeline[i] != "+": 283 if nodeline[i] != "+":
262 nodeline[i] = "-" 284 nodeline[i] = "-"
263 285
286
264 def _getpaddingline(echars, idx, ncols, edges): 287 def _getpaddingline(echars, idx, ncols, edges):
265 # all edges up to the current node 288 # all edges up to the current node
266 line = echars[:idx * 2] 289 line = echars[: idx * 2]
267 # an edge for the current node, if there is one 290 # an edge for the current node, if there is one
268 if (idx, idx - 1) in edges or (idx, idx) in edges: 291 if (idx, idx - 1) in edges or (idx, idx) in edges:
269 # (idx, idx - 1) (idx, idx) 292 # (idx, idx - 1) (idx, idx)
270 # | | | | | | | | 293 # | | | | | | | |
271 # +---o | | o---+ 294 # +---o | | o---+
272 # | | X | | X | | 295 # | | X | | X | |
273 # | |/ / | |/ / 296 # | |/ / | |/ /
274 # | | | | | | 297 # | | | | | |
275 line.extend(echars[idx * 2:(idx + 1) * 2]) 298 line.extend(echars[idx * 2 : (idx + 1) * 2])
276 else: 299 else:
277 line.extend([' ', ' ']) 300 line.extend([' ', ' '])
278 # all edges to the right of the current node 301 # all edges to the right of the current node
279 remainder = ncols - idx - 1 302 remainder = ncols - idx - 1
280 if remainder > 0: 303 if remainder > 0:
281 line.extend(echars[-(remainder * 2):]) 304 line.extend(echars[-(remainder * 2) :])
282 return line 305 return line
306
283 307
284 def _drawendinglines(lines, extra, edgemap, seen, state): 308 def _drawendinglines(lines, extra, edgemap, seen, state):
285 """Draw ending lines for missing parent edges 309 """Draw ending lines for missing parent edges
286 310
287 None indicates an edge that ends at between this node and the next 311 None indicates an edge that ends at between this node and the next
330 remove = [p for p, c in edgemap.items() if c is None] 354 remove = [p for p, c in edgemap.items() if c is None]
331 for parent in remove: 355 for parent in remove:
332 del edgemap[parent] 356 del edgemap[parent]
333 seen.remove(parent) 357 seen.remove(parent)
334 358
359
335 def asciistate(): 360 def asciistate():
336 """returns the initial value for the "state" argument to ascii()""" 361 """returns the initial value for the "state" argument to ascii()"""
337 return { 362 return {
338 'seen': [], 363 'seen': [],
339 'edges': {}, 364 'edges': {},
341 'lastindex': 0, 366 'lastindex': 0,
342 'styles': EDGES.copy(), 367 'styles': EDGES.copy(),
343 'graphshorten': False, 368 'graphshorten': False,
344 } 369 }
345 370
371
346 def outputgraph(ui, graph): 372 def outputgraph(ui, graph):
347 """outputs an ASCII graph of a DAG 373 """outputs an ASCII graph of a DAG
348 374
349 this is a helper function for 'ascii' below. 375 this is a helper function for 'ascii' below.
350 376
356 this function can be monkey-patched by extensions to alter graph display 382 this function can be monkey-patched by extensions to alter graph display
357 without needing to mimic all of the edge-fixup logic in ascii() 383 without needing to mimic all of the edge-fixup logic in ascii()
358 """ 384 """
359 for (ln, logstr) in graph: 385 for (ln, logstr) in graph:
360 ui.write((ln + logstr).rstrip() + "\n") 386 ui.write((ln + logstr).rstrip() + "\n")
387
361 388
362 def ascii(ui, state, type, char, text, coldata): 389 def ascii(ui, state, type, char, text, coldata):
363 """prints an ASCII graph of the DAG 390 """prints an ASCII graph of the DAG
364 391
365 takes the following arguments (one call per node in the graph): 392 takes the following arguments (one call per node in the graph):
402 # | | | | | | | | 429 # | | | | | | | |
403 # | o---+ into | o---+ 430 # | o---+ into | o---+
404 # | / / | | | # <--- padding line 431 # | / / | | | # <--- padding line
405 # o | | | / / 432 # o | | | / /
406 # o | | 433 # o | |
407 add_padding_line = (len(text) > 2 and coldiff == -1 and 434 add_padding_line = (
408 [x for (x, y) in edges if x + 1 < y]) 435 len(text) > 2 and coldiff == -1 and [x for (x, y) in edges if x + 1 < y]
436 )
409 437
410 # fix_nodeline_tail says whether to rewrite 438 # fix_nodeline_tail says whether to rewrite
411 # 439 #
412 # | | o | | | | o | | 440 # | | o | | | | o | |
413 # | | |/ / | | |/ / 441 # | | |/ / | | |/ /
415 # | |/ / | |/ / 443 # | |/ / | |/ /
416 # o | | o | | 444 # o | | o | |
417 fix_nodeline_tail = len(text) <= 2 and not add_padding_line 445 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
418 446
419 # nodeline is the line containing the node character (typically o) 447 # nodeline is the line containing the node character (typically o)
420 nodeline = echars[:idx * 2] 448 nodeline = echars[: idx * 2]
421 nodeline.extend([char, " "]) 449 nodeline.extend([char, " "])
422 450
423 nodeline.extend( 451 nodeline.extend(
424 _getnodelineedgestail( 452 _getnodelineedgestail(
425 echars, idx, state['lastindex'], ncols, coldiff, 453 echars,
426 state['lastcoldiff'], fix_nodeline_tail)) 454 idx,
455 state['lastindex'],
456 ncols,
457 coldiff,
458 state['lastcoldiff'],
459 fix_nodeline_tail,
460 )
461 )
427 462
428 # shift_interline is the line containing the non-vertical 463 # shift_interline is the line containing the non-vertical
429 # edges between this entry and the next 464 # edges between this entry and the next
430 shift_interline = echars[:idx * 2] 465 shift_interline = echars[: idx * 2]
431 for i in pycompat.xrange(2 + coldiff): 466 for i in pycompat.xrange(2 + coldiff):
432 shift_interline.append(' ') 467 shift_interline.append(' ')
433 count = ncols - idx - 1 468 count = ncols - idx - 1
434 if coldiff == -1: 469 if coldiff == -1:
435 for i in pycompat.xrange(count): 470 for i in pycompat.xrange(count):
436 shift_interline.extend(['/', ' ']) 471 shift_interline.extend(['/', ' '])
437 elif coldiff == 0: 472 elif coldiff == 0:
438 shift_interline.extend(echars[(idx + 1) * 2:ncols * 2]) 473 shift_interline.extend(echars[(idx + 1) * 2 : ncols * 2])
439 else: 474 else:
440 for i in pycompat.xrange(count): 475 for i in pycompat.xrange(count):
441 shift_interline.extend(['\\', ' ']) 476 shift_interline.extend(['\\', ' '])
442 477
443 # draw edges from the current node to its parents 478 # draw edges from the current node to its parents
457 else: 492 else:
458 lines.append(shift_interline) 493 lines.append(shift_interline)
459 494
460 # make sure that there are as many graph lines as there are 495 # make sure that there are as many graph lines as there are
461 # log strings 496 # log strings
462 extra_interline = echars[:(ncols + coldiff) * 2] 497 extra_interline = echars[: (ncols + coldiff) * 2]
463 if len(lines) < len(text): 498 if len(lines) < len(text):
464 while len(lines) < len(text): 499 while len(lines) < len(text):
465 lines.append(extra_interline[:]) 500 lines.append(extra_interline[:])
466 501
467 _drawendinglines(lines, extra_interline, edgemap, seen, state) 502 _drawendinglines(lines, extra_interline, edgemap, seen, state)