##// END OF EJS Templates
graphlog: remove redundant check for empty revs
Yuya Nishihara -
r35560:5e1d4cca default
parent child Browse files
Show More
@@ -1,481 +1,478 b''
1 # Revision graph generator for Mercurial
1 # Revision graph generator for Mercurial
2 #
2 #
3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """supports walking the history as DAGs suitable for graphical output
9 """supports walking the history as DAGs suitable for graphical output
10
10
11 The most basic format we use is that of::
11 The most basic format we use is that of::
12
12
13 (id, type, data, [parentids])
13 (id, type, data, [parentids])
14
14
15 The node and parent ids are arbitrary integers which identify a node in the
15 The node and parent ids are arbitrary integers which identify a node in the
16 context of the graph returned. Type is a constant specifying the node type.
16 context of the graph returned. Type is a constant specifying the node type.
17 Data depends on type.
17 Data depends on type.
18 """
18 """
19
19
20 from __future__ import absolute_import
20 from __future__ import absolute_import
21
21
22 from .node import nullrev
22 from .node import nullrev
23 from . import (
23 from . import (
24 dagop,
24 dagop,
25 smartset,
25 smartset,
26 util,
26 util,
27 )
27 )
28
28
29 CHANGESET = 'C'
29 CHANGESET = 'C'
30 PARENT = 'P'
30 PARENT = 'P'
31 GRANDPARENT = 'G'
31 GRANDPARENT = 'G'
32 MISSINGPARENT = 'M'
32 MISSINGPARENT = 'M'
33 # Style of line to draw. None signals a line that ends and is removed at this
33 # Style of line to draw. None signals a line that ends and is removed at this
34 # point. A number prefix means only the last N characters of the current block
34 # point. A number prefix means only the last N characters of the current block
35 # will use that style, the rest will use the PARENT style. Add a - sign
35 # will use that style, the rest will use the PARENT style. Add a - sign
36 # (so making N negative) and all but the first N characters use that style.
36 # (so making N negative) and all but the first N characters use that style.
37 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
37 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
38
38
39 def dagwalker(repo, revs):
39 def dagwalker(repo, revs):
40 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
40 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
41
41
42 This generator function walks through revisions (which should be ordered
42 This generator function walks through revisions (which should be ordered
43 from bigger to lower). It returns a tuple for each node.
43 from bigger to lower). It returns a tuple for each node.
44
44
45 Each parentinfo entry is a tuple with (edgetype, parentid), where edgetype
45 Each parentinfo entry is a tuple with (edgetype, parentid), where edgetype
46 is one of PARENT, GRANDPARENT or MISSINGPARENT. The node and parent ids
46 is one of PARENT, GRANDPARENT or MISSINGPARENT. The node and parent ids
47 are arbitrary integers which identify a node in the context of the graph
47 are arbitrary integers which identify a node in the context of the graph
48 returned.
48 returned.
49
49
50 """
50 """
51 if not revs:
52 return
53
54 gpcache = {}
51 gpcache = {}
55
52
56 for rev in revs:
53 for rev in revs:
57 ctx = repo[rev]
54 ctx = repo[rev]
58 # partition into parents in the rev set and missing parents, then
55 # partition into parents in the rev set and missing parents, then
59 # augment the lists with markers, to inform graph drawing code about
56 # augment the lists with markers, to inform graph drawing code about
60 # what kind of edge to draw between nodes.
57 # what kind of edge to draw between nodes.
61 pset = set(p.rev() for p in ctx.parents() if p.rev() in revs)
58 pset = set(p.rev() for p in ctx.parents() if p.rev() in revs)
62 mpars = [p.rev() for p in ctx.parents()
59 mpars = [p.rev() for p in ctx.parents()
63 if p.rev() != nullrev and p.rev() not in pset]
60 if p.rev() != nullrev and p.rev() not in pset]
64 parents = [(PARENT, p) for p in sorted(pset)]
61 parents = [(PARENT, p) for p in sorted(pset)]
65
62
66 for mpar in mpars:
63 for mpar in mpars:
67 gp = gpcache.get(mpar)
64 gp = gpcache.get(mpar)
68 if gp is None:
65 if gp is None:
69 # precompute slow query as we know reachableroots() goes
66 # precompute slow query as we know reachableroots() goes
70 # through all revs (issue4782)
67 # through all revs (issue4782)
71 if not isinstance(revs, smartset.baseset):
68 if not isinstance(revs, smartset.baseset):
72 revs = smartset.baseset(revs)
69 revs = smartset.baseset(revs)
73 gp = gpcache[mpar] = sorted(set(dagop.reachableroots(
70 gp = gpcache[mpar] = sorted(set(dagop.reachableroots(
74 repo, revs, [mpar])))
71 repo, revs, [mpar])))
75 if not gp:
72 if not gp:
76 parents.append((MISSINGPARENT, mpar))
73 parents.append((MISSINGPARENT, mpar))
77 pset.add(mpar)
74 pset.add(mpar)
78 else:
75 else:
79 parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
76 parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
80 pset.update(gp)
77 pset.update(gp)
81
78
82 yield (ctx.rev(), CHANGESET, ctx, parents)
79 yield (ctx.rev(), CHANGESET, ctx, parents)
83
80
84 def nodes(repo, nodes):
81 def nodes(repo, nodes):
85 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
82 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
86
83
87 This generator function walks the given nodes. It only returns parents
84 This generator function walks the given nodes. It only returns parents
88 that are in nodes, too.
85 that are in nodes, too.
89 """
86 """
90 include = set(nodes)
87 include = set(nodes)
91 for node in nodes:
88 for node in nodes:
92 ctx = repo[node]
89 ctx = repo[node]
93 parents = set((PARENT, p.rev()) for p in ctx.parents()
90 parents = set((PARENT, p.rev()) for p in ctx.parents()
94 if p.node() in include)
91 if p.node() in include)
95 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
92 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
96
93
97 def colored(dag, repo):
94 def colored(dag, repo):
98 """annotates a DAG with colored edge information
95 """annotates a DAG with colored edge information
99
96
100 For each DAG node this function emits tuples::
97 For each DAG node this function emits tuples::
101
98
102 (id, type, data, (col, color), [(col, nextcol, color)])
99 (id, type, data, (col, color), [(col, nextcol, color)])
103
100
104 with the following new elements:
101 with the following new elements:
105
102
106 - Tuple (col, color) with column and color index for the current node
103 - Tuple (col, color) with column and color index for the current node
107 - A list of tuples indicating the edges between the current node and its
104 - A list of tuples indicating the edges between the current node and its
108 parents.
105 parents.
109 """
106 """
110 seen = []
107 seen = []
111 colors = {}
108 colors = {}
112 newcolor = 1
109 newcolor = 1
113 config = {}
110 config = {}
114
111
115 for key, val in repo.ui.configitems('graph'):
112 for key, val in repo.ui.configitems('graph'):
116 if '.' in key:
113 if '.' in key:
117 branch, setting = key.rsplit('.', 1)
114 branch, setting = key.rsplit('.', 1)
118 # Validation
115 # Validation
119 if setting == "width" and val.isdigit():
116 if setting == "width" and val.isdigit():
120 config.setdefault(branch, {})[setting] = int(val)
117 config.setdefault(branch, {})[setting] = int(val)
121 elif setting == "color" and val.isalnum():
118 elif setting == "color" and val.isalnum():
122 config.setdefault(branch, {})[setting] = val
119 config.setdefault(branch, {})[setting] = val
123
120
124 if config:
121 if config:
125 getconf = util.lrucachefunc(
122 getconf = util.lrucachefunc(
126 lambda rev: config.get(repo[rev].branch(), {}))
123 lambda rev: config.get(repo[rev].branch(), {}))
127 else:
124 else:
128 getconf = lambda rev: {}
125 getconf = lambda rev: {}
129
126
130 for (cur, type, data, parents) in dag:
127 for (cur, type, data, parents) in dag:
131
128
132 # Compute seen and next
129 # Compute seen and next
133 if cur not in seen:
130 if cur not in seen:
134 seen.append(cur) # new head
131 seen.append(cur) # new head
135 colors[cur] = newcolor
132 colors[cur] = newcolor
136 newcolor += 1
133 newcolor += 1
137
134
138 col = seen.index(cur)
135 col = seen.index(cur)
139 color = colors.pop(cur)
136 color = colors.pop(cur)
140 next = seen[:]
137 next = seen[:]
141
138
142 # Add parents to next
139 # Add parents to next
143 addparents = [p for pt, p in parents if p not in next]
140 addparents = [p for pt, p in parents if p not in next]
144 next[col:col + 1] = addparents
141 next[col:col + 1] = addparents
145
142
146 # Set colors for the parents
143 # Set colors for the parents
147 for i, p in enumerate(addparents):
144 for i, p in enumerate(addparents):
148 if not i:
145 if not i:
149 colors[p] = color
146 colors[p] = color
150 else:
147 else:
151 colors[p] = newcolor
148 colors[p] = newcolor
152 newcolor += 1
149 newcolor += 1
153
150
154 # Add edges to the graph
151 # Add edges to the graph
155 edges = []
152 edges = []
156 for ecol, eid in enumerate(seen):
153 for ecol, eid in enumerate(seen):
157 if eid in next:
154 if eid in next:
158 bconf = getconf(eid)
155 bconf = getconf(eid)
159 edges.append((
156 edges.append((
160 ecol, next.index(eid), colors[eid],
157 ecol, next.index(eid), colors[eid],
161 bconf.get('width', -1),
158 bconf.get('width', -1),
162 bconf.get('color', '')))
159 bconf.get('color', '')))
163 elif eid == cur:
160 elif eid == cur:
164 for ptype, p in parents:
161 for ptype, p in parents:
165 bconf = getconf(p)
162 bconf = getconf(p)
166 edges.append((
163 edges.append((
167 ecol, next.index(p), color,
164 ecol, next.index(p), color,
168 bconf.get('width', -1),
165 bconf.get('width', -1),
169 bconf.get('color', '')))
166 bconf.get('color', '')))
170
167
171 # Yield and move on
168 # Yield and move on
172 yield (cur, type, data, (col, color), edges)
169 yield (cur, type, data, (col, color), edges)
173 seen = next
170 seen = next
174
171
175 def asciiedges(type, char, state, rev, parents):
172 def asciiedges(type, char, state, rev, parents):
176 """adds edge info to changelog DAG walk suitable for ascii()"""
173 """adds edge info to changelog DAG walk suitable for ascii()"""
177 seen = state['seen']
174 seen = state['seen']
178 if rev not in seen:
175 if rev not in seen:
179 seen.append(rev)
176 seen.append(rev)
180 nodeidx = seen.index(rev)
177 nodeidx = seen.index(rev)
181
178
182 knownparents = []
179 knownparents = []
183 newparents = []
180 newparents = []
184 for ptype, parent in parents:
181 for ptype, parent in parents:
185 if parent == rev:
182 if parent == rev:
186 # self reference (should only be seen in null rev)
183 # self reference (should only be seen in null rev)
187 continue
184 continue
188 if parent in seen:
185 if parent in seen:
189 knownparents.append(parent)
186 knownparents.append(parent)
190 else:
187 else:
191 newparents.append(parent)
188 newparents.append(parent)
192 state['edges'][parent] = state['styles'].get(ptype, '|')
189 state['edges'][parent] = state['styles'].get(ptype, '|')
193
190
194 ncols = len(seen)
191 ncols = len(seen)
195 width = 1 + ncols * 2
192 width = 1 + ncols * 2
196 nextseen = seen[:]
193 nextseen = seen[:]
197 nextseen[nodeidx:nodeidx + 1] = newparents
194 nextseen[nodeidx:nodeidx + 1] = newparents
198 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
195 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
199
196
200 seen[:] = nextseen
197 seen[:] = nextseen
201 while len(newparents) > 2:
198 while len(newparents) > 2:
202 # ascii() only knows how to add or remove a single column between two
199 # ascii() only knows how to add or remove a single column between two
203 # calls. Nodes with more than two parents break this constraint so we
200 # calls. Nodes with more than two parents break this constraint so we
204 # introduce intermediate expansion lines to grow the active node list
201 # introduce intermediate expansion lines to grow the active node list
205 # slowly.
202 # slowly.
206 edges.append((nodeidx, nodeidx))
203 edges.append((nodeidx, nodeidx))
207 edges.append((nodeidx, nodeidx + 1))
204 edges.append((nodeidx, nodeidx + 1))
208 nmorecols = 1
205 nmorecols = 1
209 width += 2
206 width += 2
210 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
207 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
211 char = '\\'
208 char = '\\'
212 nodeidx += 1
209 nodeidx += 1
213 ncols += 1
210 ncols += 1
214 edges = []
211 edges = []
215 del newparents[0]
212 del newparents[0]
216
213
217 if len(newparents) > 0:
214 if len(newparents) > 0:
218 edges.append((nodeidx, nodeidx))
215 edges.append((nodeidx, nodeidx))
219 if len(newparents) > 1:
216 if len(newparents) > 1:
220 edges.append((nodeidx, nodeidx + 1))
217 edges.append((nodeidx, nodeidx + 1))
221 nmorecols = len(nextseen) - ncols
218 nmorecols = len(nextseen) - ncols
222 if nmorecols > 0:
219 if nmorecols > 0:
223 width += 2
220 width += 2
224 # remove current node from edge characters, no longer needed
221 # remove current node from edge characters, no longer needed
225 state['edges'].pop(rev, None)
222 state['edges'].pop(rev, None)
226 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
223 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
227
224
228 def _fixlongrightedges(edges):
225 def _fixlongrightedges(edges):
229 for (i, (start, end)) in enumerate(edges):
226 for (i, (start, end)) in enumerate(edges):
230 if end > start:
227 if end > start:
231 edges[i] = (start, end + 1)
228 edges[i] = (start, end + 1)
232
229
233 def _getnodelineedgestail(
230 def _getnodelineedgestail(
234 echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
231 echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
235 if fix_tail and coldiff == pdiff and coldiff != 0:
232 if fix_tail and coldiff == pdiff and coldiff != 0:
236 # Still going in the same non-vertical direction.
233 # Still going in the same non-vertical direction.
237 if coldiff == -1:
234 if coldiff == -1:
238 start = max(idx + 1, pidx)
235 start = max(idx + 1, pidx)
239 tail = echars[idx * 2:(start - 1) * 2]
236 tail = echars[idx * 2:(start - 1) * 2]
240 tail.extend(["/", " "] * (ncols - start))
237 tail.extend(["/", " "] * (ncols - start))
241 return tail
238 return tail
242 else:
239 else:
243 return ["\\", " "] * (ncols - idx - 1)
240 return ["\\", " "] * (ncols - idx - 1)
244 else:
241 else:
245 remainder = (ncols - idx - 1)
242 remainder = (ncols - idx - 1)
246 return echars[-(remainder * 2):] if remainder > 0 else []
243 return echars[-(remainder * 2):] if remainder > 0 else []
247
244
248 def _drawedges(echars, edges, nodeline, interline):
245 def _drawedges(echars, edges, nodeline, interline):
249 for (start, end) in edges:
246 for (start, end) in edges:
250 if start == end + 1:
247 if start == end + 1:
251 interline[2 * end + 1] = "/"
248 interline[2 * end + 1] = "/"
252 elif start == end - 1:
249 elif start == end - 1:
253 interline[2 * start + 1] = "\\"
250 interline[2 * start + 1] = "\\"
254 elif start == end:
251 elif start == end:
255 interline[2 * start] = echars[2 * start]
252 interline[2 * start] = echars[2 * start]
256 else:
253 else:
257 if 2 * end >= len(nodeline):
254 if 2 * end >= len(nodeline):
258 continue
255 continue
259 nodeline[2 * end] = "+"
256 nodeline[2 * end] = "+"
260 if start > end:
257 if start > end:
261 (start, end) = (end, start)
258 (start, end) = (end, start)
262 for i in range(2 * start + 1, 2 * end):
259 for i in range(2 * start + 1, 2 * end):
263 if nodeline[i] != "+":
260 if nodeline[i] != "+":
264 nodeline[i] = "-"
261 nodeline[i] = "-"
265
262
266 def _getpaddingline(echars, idx, ncols, edges):
263 def _getpaddingline(echars, idx, ncols, edges):
267 # all edges up to the current node
264 # all edges up to the current node
268 line = echars[:idx * 2]
265 line = echars[:idx * 2]
269 # an edge for the current node, if there is one
266 # an edge for the current node, if there is one
270 if (idx, idx - 1) in edges or (idx, idx) in edges:
267 if (idx, idx - 1) in edges or (idx, idx) in edges:
271 # (idx, idx - 1) (idx, idx)
268 # (idx, idx - 1) (idx, idx)
272 # | | | | | | | |
269 # | | | | | | | |
273 # +---o | | o---+
270 # +---o | | o---+
274 # | | X | | X | |
271 # | | X | | X | |
275 # | |/ / | |/ /
272 # | |/ / | |/ /
276 # | | | | | |
273 # | | | | | |
277 line.extend(echars[idx * 2:(idx + 1) * 2])
274 line.extend(echars[idx * 2:(idx + 1) * 2])
278 else:
275 else:
279 line.extend([' ', ' '])
276 line.extend([' ', ' '])
280 # all edges to the right of the current node
277 # all edges to the right of the current node
281 remainder = ncols - idx - 1
278 remainder = ncols - idx - 1
282 if remainder > 0:
279 if remainder > 0:
283 line.extend(echars[-(remainder * 2):])
280 line.extend(echars[-(remainder * 2):])
284 return line
281 return line
285
282
286 def _drawendinglines(lines, extra, edgemap, seen):
283 def _drawendinglines(lines, extra, edgemap, seen):
287 """Draw ending lines for missing parent edges
284 """Draw ending lines for missing parent edges
288
285
289 None indicates an edge that ends at between this node and the next
286 None indicates an edge that ends at between this node and the next
290 Replace with a short line ending in ~ and add / lines to any edges to
287 Replace with a short line ending in ~ and add / lines to any edges to
291 the right.
288 the right.
292
289
293 """
290 """
294 if None not in edgemap.values():
291 if None not in edgemap.values():
295 return
292 return
296
293
297 # Check for more edges to the right of our ending edges.
294 # Check for more edges to the right of our ending edges.
298 # We need enough space to draw adjustment lines for these.
295 # We need enough space to draw adjustment lines for these.
299 edgechars = extra[::2]
296 edgechars = extra[::2]
300 while edgechars and edgechars[-1] is None:
297 while edgechars and edgechars[-1] is None:
301 edgechars.pop()
298 edgechars.pop()
302 shift_size = max((edgechars.count(None) * 2) - 1, 0)
299 shift_size = max((edgechars.count(None) * 2) - 1, 0)
303 while len(lines) < 3 + shift_size:
300 while len(lines) < 3 + shift_size:
304 lines.append(extra[:])
301 lines.append(extra[:])
305
302
306 if shift_size:
303 if shift_size:
307 empties = []
304 empties = []
308 toshift = []
305 toshift = []
309 first_empty = extra.index(None)
306 first_empty = extra.index(None)
310 for i, c in enumerate(extra[first_empty::2], first_empty // 2):
307 for i, c in enumerate(extra[first_empty::2], first_empty // 2):
311 if c is None:
308 if c is None:
312 empties.append(i * 2)
309 empties.append(i * 2)
313 else:
310 else:
314 toshift.append(i * 2)
311 toshift.append(i * 2)
315 targets = list(range(first_empty, first_empty + len(toshift) * 2, 2))
312 targets = list(range(first_empty, first_empty + len(toshift) * 2, 2))
316 positions = toshift[:]
313 positions = toshift[:]
317 for line in lines[-shift_size:]:
314 for line in lines[-shift_size:]:
318 line[first_empty:] = [' '] * (len(line) - first_empty)
315 line[first_empty:] = [' '] * (len(line) - first_empty)
319 for i in range(len(positions)):
316 for i in range(len(positions)):
320 pos = positions[i] - 1
317 pos = positions[i] - 1
321 positions[i] = max(pos, targets[i])
318 positions[i] = max(pos, targets[i])
322 line[pos] = '/' if pos > targets[i] else extra[toshift[i]]
319 line[pos] = '/' if pos > targets[i] else extra[toshift[i]]
323
320
324 map = {1: '|', 2: '~'}
321 map = {1: '|', 2: '~'}
325 for i, line in enumerate(lines):
322 for i, line in enumerate(lines):
326 if None not in line:
323 if None not in line:
327 continue
324 continue
328 line[:] = [c or map.get(i, ' ') for c in line]
325 line[:] = [c or map.get(i, ' ') for c in line]
329
326
330 # remove edges that ended
327 # remove edges that ended
331 remove = [p for p, c in edgemap.items() if c is None]
328 remove = [p for p, c in edgemap.items() if c is None]
332 for parent in remove:
329 for parent in remove:
333 del edgemap[parent]
330 del edgemap[parent]
334 seen.remove(parent)
331 seen.remove(parent)
335
332
336 def asciistate():
333 def asciistate():
337 """returns the initial value for the "state" argument to ascii()"""
334 """returns the initial value for the "state" argument to ascii()"""
338 return {
335 return {
339 'seen': [],
336 'seen': [],
340 'edges': {},
337 'edges': {},
341 'lastcoldiff': 0,
338 'lastcoldiff': 0,
342 'lastindex': 0,
339 'lastindex': 0,
343 'styles': EDGES.copy(),
340 'styles': EDGES.copy(),
344 'graphshorten': False,
341 'graphshorten': False,
345 }
342 }
346
343
347 def ascii(ui, state, type, char, text, coldata):
344 def ascii(ui, state, type, char, text, coldata):
348 """prints an ASCII graph of the DAG
345 """prints an ASCII graph of the DAG
349
346
350 takes the following arguments (one call per node in the graph):
347 takes the following arguments (one call per node in the graph):
351
348
352 - ui to write to
349 - ui to write to
353 - Somewhere to keep the needed state in (init to asciistate())
350 - Somewhere to keep the needed state in (init to asciistate())
354 - Column of the current node in the set of ongoing edges.
351 - Column of the current node in the set of ongoing edges.
355 - Type indicator of node data, usually 'C' for changesets.
352 - Type indicator of node data, usually 'C' for changesets.
356 - Payload: (char, lines):
353 - Payload: (char, lines):
357 - Character to use as node's symbol.
354 - Character to use as node's symbol.
358 - List of lines to display as the node's text.
355 - List of lines to display as the node's text.
359 - Edges; a list of (col, next_col) indicating the edges between
356 - Edges; a list of (col, next_col) indicating the edges between
360 the current node and its parents.
357 the current node and its parents.
361 - Number of columns (ongoing edges) in the current revision.
358 - Number of columns (ongoing edges) in the current revision.
362 - The difference between the number of columns (ongoing edges)
359 - The difference between the number of columns (ongoing edges)
363 in the next revision and the number of columns (ongoing edges)
360 in the next revision and the number of columns (ongoing edges)
364 in the current revision. That is: -1 means one column removed;
361 in the current revision. That is: -1 means one column removed;
365 0 means no columns added or removed; 1 means one column added.
362 0 means no columns added or removed; 1 means one column added.
366 """
363 """
367 idx, edges, ncols, coldiff = coldata
364 idx, edges, ncols, coldiff = coldata
368 assert -2 < coldiff < 2
365 assert -2 < coldiff < 2
369
366
370 edgemap, seen = state['edges'], state['seen']
367 edgemap, seen = state['edges'], state['seen']
371 # Be tolerant of history issues; make sure we have at least ncols + coldiff
368 # Be tolerant of history issues; make sure we have at least ncols + coldiff
372 # elements to work with. See test-glog.t for broken history test cases.
369 # elements to work with. See test-glog.t for broken history test cases.
373 echars = [c for p in seen for c in (edgemap.get(p, '|'), ' ')]
370 echars = [c for p in seen for c in (edgemap.get(p, '|'), ' ')]
374 echars.extend(('|', ' ') * max(ncols + coldiff - len(seen), 0))
371 echars.extend(('|', ' ') * max(ncols + coldiff - len(seen), 0))
375
372
376 if coldiff == -1:
373 if coldiff == -1:
377 # Transform
374 # Transform
378 #
375 #
379 # | | | | | |
376 # | | | | | |
380 # o | | into o---+
377 # o | | into o---+
381 # |X / |/ /
378 # |X / |/ /
382 # | | | |
379 # | | | |
383 _fixlongrightedges(edges)
380 _fixlongrightedges(edges)
384
381
385 # add_padding_line says whether to rewrite
382 # add_padding_line says whether to rewrite
386 #
383 #
387 # | | | | | | | |
384 # | | | | | | | |
388 # | o---+ into | o---+
385 # | o---+ into | o---+
389 # | / / | | | # <--- padding line
386 # | / / | | | # <--- padding line
390 # o | | | / /
387 # o | | | / /
391 # o | |
388 # o | |
392 add_padding_line = (len(text) > 2 and coldiff == -1 and
389 add_padding_line = (len(text) > 2 and coldiff == -1 and
393 [x for (x, y) in edges if x + 1 < y])
390 [x for (x, y) in edges if x + 1 < y])
394
391
395 # fix_nodeline_tail says whether to rewrite
392 # fix_nodeline_tail says whether to rewrite
396 #
393 #
397 # | | o | | | | o | |
394 # | | o | | | | o | |
398 # | | |/ / | | |/ /
395 # | | |/ / | | |/ /
399 # | o | | into | o / / # <--- fixed nodeline tail
396 # | o | | into | o / / # <--- fixed nodeline tail
400 # | |/ / | |/ /
397 # | |/ / | |/ /
401 # o | | o | |
398 # o | | o | |
402 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
399 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
403
400
404 # nodeline is the line containing the node character (typically o)
401 # nodeline is the line containing the node character (typically o)
405 nodeline = echars[:idx * 2]
402 nodeline = echars[:idx * 2]
406 nodeline.extend([char, " "])
403 nodeline.extend([char, " "])
407
404
408 nodeline.extend(
405 nodeline.extend(
409 _getnodelineedgestail(
406 _getnodelineedgestail(
410 echars, idx, state['lastindex'], ncols, coldiff,
407 echars, idx, state['lastindex'], ncols, coldiff,
411 state['lastcoldiff'], fix_nodeline_tail))
408 state['lastcoldiff'], fix_nodeline_tail))
412
409
413 # shift_interline is the line containing the non-vertical
410 # shift_interline is the line containing the non-vertical
414 # edges between this entry and the next
411 # edges between this entry and the next
415 shift_interline = echars[:idx * 2]
412 shift_interline = echars[:idx * 2]
416 for i in xrange(2 + coldiff):
413 for i in xrange(2 + coldiff):
417 shift_interline.append(' ')
414 shift_interline.append(' ')
418 count = ncols - idx - 1
415 count = ncols - idx - 1
419 if coldiff == -1:
416 if coldiff == -1:
420 for i in xrange(count):
417 for i in xrange(count):
421 shift_interline.extend(['/', ' '])
418 shift_interline.extend(['/', ' '])
422 elif coldiff == 0:
419 elif coldiff == 0:
423 shift_interline.extend(echars[(idx + 1) * 2:ncols * 2])
420 shift_interline.extend(echars[(idx + 1) * 2:ncols * 2])
424 else:
421 else:
425 for i in xrange(count):
422 for i in xrange(count):
426 shift_interline.extend(['\\', ' '])
423 shift_interline.extend(['\\', ' '])
427
424
428 # draw edges from the current node to its parents
425 # draw edges from the current node to its parents
429 _drawedges(echars, edges, nodeline, shift_interline)
426 _drawedges(echars, edges, nodeline, shift_interline)
430
427
431 # lines is the list of all graph lines to print
428 # lines is the list of all graph lines to print
432 lines = [nodeline]
429 lines = [nodeline]
433 if add_padding_line:
430 if add_padding_line:
434 lines.append(_getpaddingline(echars, idx, ncols, edges))
431 lines.append(_getpaddingline(echars, idx, ncols, edges))
435
432
436 # If 'graphshorten' config, only draw shift_interline
433 # If 'graphshorten' config, only draw shift_interline
437 # when there is any non vertical flow in graph.
434 # when there is any non vertical flow in graph.
438 if state['graphshorten']:
435 if state['graphshorten']:
439 if any(c in '\/' for c in shift_interline if c):
436 if any(c in '\/' for c in shift_interline if c):
440 lines.append(shift_interline)
437 lines.append(shift_interline)
441 # Else, no 'graphshorten' config so draw shift_interline.
438 # Else, no 'graphshorten' config so draw shift_interline.
442 else:
439 else:
443 lines.append(shift_interline)
440 lines.append(shift_interline)
444
441
445 # make sure that there are as many graph lines as there are
442 # make sure that there are as many graph lines as there are
446 # log strings
443 # log strings
447 extra_interline = echars[:(ncols + coldiff) * 2]
444 extra_interline = echars[:(ncols + coldiff) * 2]
448 if len(lines) < len(text):
445 if len(lines) < len(text):
449 while len(lines) < len(text):
446 while len(lines) < len(text):
450 lines.append(extra_interline[:])
447 lines.append(extra_interline[:])
451
448
452 _drawendinglines(lines, extra_interline, edgemap, seen)
449 _drawendinglines(lines, extra_interline, edgemap, seen)
453
450
454 while len(text) < len(lines):
451 while len(text) < len(lines):
455 text.append("")
452 text.append("")
456
453
457 if any(len(char) > 1 for char in edgemap.values()):
454 if any(len(char) > 1 for char in edgemap.values()):
458 # limit drawing an edge to the first or last N lines of the current
455 # limit drawing an edge to the first or last N lines of the current
459 # section the rest of the edge is drawn like a parent line.
456 # section the rest of the edge is drawn like a parent line.
460 parent = state['styles'][PARENT][-1]
457 parent = state['styles'][PARENT][-1]
461 def _drawgp(char, i):
458 def _drawgp(char, i):
462 # should a grandparent character be drawn for this line?
459 # should a grandparent character be drawn for this line?
463 if len(char) < 2:
460 if len(char) < 2:
464 return True
461 return True
465 num = int(char[:-1])
462 num = int(char[:-1])
466 # either skip first num lines or take last num lines, based on sign
463 # either skip first num lines or take last num lines, based on sign
467 return -num <= i if num < 0 else (len(lines) - i) <= num
464 return -num <= i if num < 0 else (len(lines) - i) <= num
468 for i, line in enumerate(lines):
465 for i, line in enumerate(lines):
469 line[:] = [c[-1] if _drawgp(c, i) else parent for c in line]
466 line[:] = [c[-1] if _drawgp(c, i) else parent for c in line]
470 edgemap.update(
467 edgemap.update(
471 (e, (c if len(c) < 2 else parent)) for e, c in edgemap.items())
468 (e, (c if len(c) < 2 else parent)) for e, c in edgemap.items())
472
469
473 # print lines
470 # print lines
474 indentation_level = max(ncols, ncols + coldiff)
471 indentation_level = max(ncols, ncols + coldiff)
475 for (line, logstr) in zip(lines, text):
472 for (line, logstr) in zip(lines, text):
476 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
473 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
477 ui.write(ln.rstrip() + '\n')
474 ui.write(ln.rstrip() + '\n')
478
475
479 # ... and start over
476 # ... and start over
480 state['lastcoldiff'] = coldiff
477 state['lastcoldiff'] = coldiff
481 state['lastindex'] = idx
478 state['lastindex'] = idx
General Comments 0
You need to be logged in to leave comments. Login now