##// END OF EJS Templates
graphmod/graphlog: extract nodelistwalk
Peter Arrenbrecht -
r8837:d8e3a980 default
parent child Browse files
Show More
@@ -1,382 +1,376 b''
1 # ASCII graph log extension for Mercurial
1 # ASCII graph log extension for Mercurial
2 #
2 #
3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''show revision graphs in terminal windows
8 '''show revision graphs in terminal windows
9
9
10 This extension adds a --graph option to the incoming, outgoing and log
10 This extension adds a --graph option to the incoming, outgoing and log
11 commands. When this options is given, an ASCII representation of the
11 commands. When this options is given, an ASCII representation of the
12 revision graph is also shown.
12 revision graph is also shown.
13 '''
13 '''
14
14
15 import os
15 import os, sys
16 from mercurial.cmdutil import revrange, show_changeset
16 from mercurial.cmdutil import revrange, show_changeset
17 from mercurial.commands import templateopts
17 from mercurial.commands import templateopts
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19 from mercurial.node import nullrev
19 from mercurial.node import nullrev
20 from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions
20 from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions
21 from mercurial import hg, url, util, graphmod
21 from mercurial import hg, url, util, graphmod
22
22
23 def grapher(nodes):
23 def grapher(nodes):
24 """grapher for asciigraph on a list of nodes and their parents
24 """grapher for asciigraph on a list of nodes and their parents
25
25
26 nodes must generate tuples (node, parents, char, lines) where
26 nodes must generate tuples (node, parents, char, lines) where
27 - parents must generate the parents of node, in sorted order,
27 - parents must generate the parents of node, in sorted order,
28 and max length 2,
28 and max length 2,
29 - char is the char to print as the node symbol, and
29 - char is the char to print as the node symbol, and
30 - lines are the lines to display next to the node.
30 - lines are the lines to display next to the node.
31 """
31 """
32 seen = []
32 seen = []
33 for node, parents, char, lines in nodes:
33 for node, parents, char, lines in nodes:
34 if node not in seen:
34 if node not in seen:
35 seen.append(node)
35 seen.append(node)
36 nodeidx = seen.index(node)
36 nodeidx = seen.index(node)
37
37
38 knownparents = []
38 knownparents = []
39 newparents = []
39 newparents = []
40 for parent in parents:
40 for parent in parents:
41 if parent in seen:
41 if parent in seen:
42 knownparents.append(parent)
42 knownparents.append(parent)
43 else:
43 else:
44 newparents.append(parent)
44 newparents.append(parent)
45
45
46 ncols = len(seen)
46 ncols = len(seen)
47 nextseen = seen[:]
47 nextseen = seen[:]
48 nextseen[nodeidx:nodeidx + 1] = newparents
48 nextseen[nodeidx:nodeidx + 1] = newparents
49 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
49 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
50
50
51 if len(newparents) > 0:
51 if len(newparents) > 0:
52 edges.append((nodeidx, nodeidx))
52 edges.append((nodeidx, nodeidx))
53 if len(newparents) > 1:
53 if len(newparents) > 1:
54 edges.append((nodeidx, nodeidx + 1))
54 edges.append((nodeidx, nodeidx + 1))
55 nmorecols = len(nextseen) - ncols
55 nmorecols = len(nextseen) - ncols
56 seen = nextseen
56 seen = nextseen
57 yield (char, lines, nodeidx, edges, ncols, nmorecols)
57 yield (char, lines, nodeidx, edges, ncols, nmorecols)
58
58
59 def fix_long_right_edges(edges):
59 def fix_long_right_edges(edges):
60 for (i, (start, end)) in enumerate(edges):
60 for (i, (start, end)) in enumerate(edges):
61 if end > start:
61 if end > start:
62 edges[i] = (start, end + 1)
62 edges[i] = (start, end + 1)
63
63
64 def get_nodeline_edges_tail(
64 def get_nodeline_edges_tail(
65 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
65 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
66 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
66 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
67 # Still going in the same non-vertical direction.
67 # Still going in the same non-vertical direction.
68 if n_columns_diff == -1:
68 if n_columns_diff == -1:
69 start = max(node_index + 1, p_node_index)
69 start = max(node_index + 1, p_node_index)
70 tail = ["|", " "] * (start - node_index - 1)
70 tail = ["|", " "] * (start - node_index - 1)
71 tail.extend(["/", " "] * (n_columns - start))
71 tail.extend(["/", " "] * (n_columns - start))
72 return tail
72 return tail
73 else:
73 else:
74 return ["\\", " "] * (n_columns - node_index - 1)
74 return ["\\", " "] * (n_columns - node_index - 1)
75 else:
75 else:
76 return ["|", " "] * (n_columns - node_index - 1)
76 return ["|", " "] * (n_columns - node_index - 1)
77
77
78 def draw_edges(edges, nodeline, interline):
78 def draw_edges(edges, nodeline, interline):
79 for (start, end) in edges:
79 for (start, end) in edges:
80 if start == end + 1:
80 if start == end + 1:
81 interline[2 * end + 1] = "/"
81 interline[2 * end + 1] = "/"
82 elif start == end - 1:
82 elif start == end - 1:
83 interline[2 * start + 1] = "\\"
83 interline[2 * start + 1] = "\\"
84 elif start == end:
84 elif start == end:
85 interline[2 * start] = "|"
85 interline[2 * start] = "|"
86 else:
86 else:
87 nodeline[2 * end] = "+"
87 nodeline[2 * end] = "+"
88 if start > end:
88 if start > end:
89 (start, end) = (end,start)
89 (start, end) = (end,start)
90 for i in range(2 * start + 1, 2 * end):
90 for i in range(2 * start + 1, 2 * end):
91 if nodeline[i] != "+":
91 if nodeline[i] != "+":
92 nodeline[i] = "-"
92 nodeline[i] = "-"
93
93
94 def get_padding_line(ni, n_columns, edges):
94 def get_padding_line(ni, n_columns, edges):
95 line = []
95 line = []
96 line.extend(["|", " "] * ni)
96 line.extend(["|", " "] * ni)
97 if (ni, ni - 1) in edges or (ni, ni) in edges:
97 if (ni, ni - 1) in edges or (ni, ni) in edges:
98 # (ni, ni - 1) (ni, ni)
98 # (ni, ni - 1) (ni, ni)
99 # | | | | | | | |
99 # | | | | | | | |
100 # +---o | | o---+
100 # +---o | | o---+
101 # | | c | | c | |
101 # | | c | | c | |
102 # | |/ / | |/ /
102 # | |/ / | |/ /
103 # | | | | | |
103 # | | | | | |
104 c = "|"
104 c = "|"
105 else:
105 else:
106 c = " "
106 c = " "
107 line.extend([c, " "])
107 line.extend([c, " "])
108 line.extend(["|", " "] * (n_columns - ni - 1))
108 line.extend(["|", " "] * (n_columns - ni - 1))
109 return line
109 return line
110
110
111 def ascii(ui, grapher):
111 def ascii(ui, grapher):
112 """prints an ASCII graph of the DAG returned by the grapher
112 """prints an ASCII graph of the DAG returned by the grapher
113
113
114 grapher is a generator that emits tuples with the following elements:
114 grapher is a generator that emits tuples with the following elements:
115
115
116 - Character to use as node's symbol.
116 - Character to use as node's symbol.
117 - List of lines to display as the node's text.
117 - List of lines to display as the node's text.
118 - Column of the current node in the set of ongoing edges.
118 - Column of the current node in the set of ongoing edges.
119 - Edges; a list of (col, next_col) indicating the edges between
119 - Edges; a list of (col, next_col) indicating the edges between
120 the current node and its parents.
120 the current node and its parents.
121 - Number of columns (ongoing edges) in the current revision.
121 - Number of columns (ongoing edges) in the current revision.
122 - The difference between the number of columns (ongoing edges)
122 - The difference between the number of columns (ongoing edges)
123 in the next revision and the number of columns (ongoing edges)
123 in the next revision and the number of columns (ongoing edges)
124 in the current revision. That is: -1 means one column removed;
124 in the current revision. That is: -1 means one column removed;
125 0 means no columns added or removed; 1 means one column added.
125 0 means no columns added or removed; 1 means one column added.
126 """
126 """
127 prev_n_columns_diff = 0
127 prev_n_columns_diff = 0
128 prev_node_index = 0
128 prev_node_index = 0
129 for (node_ch, node_lines, node_index, edges, n_columns, n_columns_diff) in grapher:
129 for (node_ch, node_lines, node_index, edges, n_columns, n_columns_diff) in grapher:
130
130
131 assert -2 < n_columns_diff < 2
131 assert -2 < n_columns_diff < 2
132 if n_columns_diff == -1:
132 if n_columns_diff == -1:
133 # Transform
133 # Transform
134 #
134 #
135 # | | | | | |
135 # | | | | | |
136 # o | | into o---+
136 # o | | into o---+
137 # |X / |/ /
137 # |X / |/ /
138 # | | | |
138 # | | | |
139 fix_long_right_edges(edges)
139 fix_long_right_edges(edges)
140
140
141 # add_padding_line says whether to rewrite
141 # add_padding_line says whether to rewrite
142 #
142 #
143 # | | | | | | | |
143 # | | | | | | | |
144 # | o---+ into | o---+
144 # | o---+ into | o---+
145 # | / / | | | # <--- padding line
145 # | / / | | | # <--- padding line
146 # o | | | / /
146 # o | | | / /
147 # o | |
147 # o | |
148 add_padding_line = (len(node_lines) > 2 and
148 add_padding_line = (len(node_lines) > 2 and
149 n_columns_diff == -1 and
149 n_columns_diff == -1 and
150 [x for (x, y) in edges if x + 1 < y])
150 [x for (x, y) in edges if x + 1 < y])
151
151
152 # fix_nodeline_tail says whether to rewrite
152 # fix_nodeline_tail says whether to rewrite
153 #
153 #
154 # | | o | | | | o | |
154 # | | o | | | | o | |
155 # | | |/ / | | |/ /
155 # | | |/ / | | |/ /
156 # | o | | into | o / / # <--- fixed nodeline tail
156 # | o | | into | o / / # <--- fixed nodeline tail
157 # | |/ / | |/ /
157 # | |/ / | |/ /
158 # o | | o | |
158 # o | | o | |
159 fix_nodeline_tail = len(node_lines) <= 2 and not add_padding_line
159 fix_nodeline_tail = len(node_lines) <= 2 and not add_padding_line
160
160
161 # nodeline is the line containing the node character (typically o)
161 # nodeline is the line containing the node character (typically o)
162 nodeline = ["|", " "] * node_index
162 nodeline = ["|", " "] * node_index
163 nodeline.extend([node_ch, " "])
163 nodeline.extend([node_ch, " "])
164
164
165 nodeline.extend(
165 nodeline.extend(
166 get_nodeline_edges_tail(
166 get_nodeline_edges_tail(
167 node_index, prev_node_index, n_columns, n_columns_diff,
167 node_index, prev_node_index, n_columns, n_columns_diff,
168 prev_n_columns_diff, fix_nodeline_tail))
168 prev_n_columns_diff, fix_nodeline_tail))
169
169
170 # shift_interline is the line containing the non-vertical
170 # shift_interline is the line containing the non-vertical
171 # edges between this entry and the next
171 # edges between this entry and the next
172 shift_interline = ["|", " "] * node_index
172 shift_interline = ["|", " "] * node_index
173 if n_columns_diff == -1:
173 if n_columns_diff == -1:
174 n_spaces = 1
174 n_spaces = 1
175 edge_ch = "/"
175 edge_ch = "/"
176 elif n_columns_diff == 0:
176 elif n_columns_diff == 0:
177 n_spaces = 2
177 n_spaces = 2
178 edge_ch = "|"
178 edge_ch = "|"
179 else:
179 else:
180 n_spaces = 3
180 n_spaces = 3
181 edge_ch = "\\"
181 edge_ch = "\\"
182 shift_interline.extend(n_spaces * [" "])
182 shift_interline.extend(n_spaces * [" "])
183 shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1))
183 shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1))
184
184
185 # draw edges from the current node to its parents
185 # draw edges from the current node to its parents
186 draw_edges(edges, nodeline, shift_interline)
186 draw_edges(edges, nodeline, shift_interline)
187
187
188 # lines is the list of all graph lines to print
188 # lines is the list of all graph lines to print
189 lines = [nodeline]
189 lines = [nodeline]
190 if add_padding_line:
190 if add_padding_line:
191 lines.append(get_padding_line(node_index, n_columns, edges))
191 lines.append(get_padding_line(node_index, n_columns, edges))
192 lines.append(shift_interline)
192 lines.append(shift_interline)
193
193
194 # make sure that there are as many graph lines as there are
194 # make sure that there are as many graph lines as there are
195 # log strings
195 # log strings
196 while len(node_lines) < len(lines):
196 while len(node_lines) < len(lines):
197 node_lines.append("")
197 node_lines.append("")
198 if len(lines) < len(node_lines):
198 if len(lines) < len(node_lines):
199 extra_interline = ["|", " "] * (n_columns + n_columns_diff)
199 extra_interline = ["|", " "] * (n_columns + n_columns_diff)
200 while len(lines) < len(node_lines):
200 while len(lines) < len(node_lines):
201 lines.append(extra_interline)
201 lines.append(extra_interline)
202
202
203 # print lines
203 # print lines
204 indentation_level = max(n_columns, n_columns + n_columns_diff)
204 indentation_level = max(n_columns, n_columns + n_columns_diff)
205 for (line, logstr) in zip(lines, node_lines):
205 for (line, logstr) in zip(lines, node_lines):
206 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
206 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
207 ui.write(ln.rstrip() + '\n')
207 ui.write(ln.rstrip() + '\n')
208
208
209 # ... and start over
209 # ... and start over
210 prev_node_index = node_index
210 prev_node_index = node_index
211 prev_n_columns_diff = n_columns_diff
211 prev_n_columns_diff = n_columns_diff
212
212
213 def get_revs(repo, rev_opt):
213 def get_revs(repo, rev_opt):
214 if rev_opt:
214 if rev_opt:
215 revs = revrange(repo, rev_opt)
215 revs = revrange(repo, rev_opt)
216 return (max(revs), min(revs))
216 return (max(revs), min(revs))
217 else:
217 else:
218 return (len(repo) - 1, 0)
218 return (len(repo) - 1, 0)
219
219
220 def check_unsupported_flags(opts):
220 def check_unsupported_flags(opts):
221 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove",
221 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove",
222 "only_merges", "user", "only_branch", "prune", "newest_first",
222 "only_merges", "user", "only_branch", "prune", "newest_first",
223 "no_merges", "include", "exclude"]:
223 "no_merges", "include", "exclude"]:
224 if op in opts and opts[op]:
224 if op in opts and opts[op]:
225 raise util.Abort(_("--graph option is incompatible with --%s") % op)
225 raise util.Abort(_("--graph option is incompatible with --%s") % op)
226
226
227 def graphlog(ui, repo, path=None, **opts):
227 def graphlog(ui, repo, path=None, **opts):
228 """show revision history alongside an ASCII revision graph
228 """show revision history alongside an ASCII revision graph
229
229
230 Print a revision history alongside a revision graph drawn with
230 Print a revision history alongside a revision graph drawn with
231 ASCII characters.
231 ASCII characters.
232
232
233 Nodes printed as an @ character are parents of the working
233 Nodes printed as an @ character are parents of the working
234 directory.
234 directory.
235 """
235 """
236
236
237 check_unsupported_flags(opts)
237 check_unsupported_flags(opts)
238 limit = cmdutil.loglimit(opts)
238 limit = cmdutil.loglimit(opts)
239 start, stop = get_revs(repo, opts["rev"])
239 start, stop = get_revs(repo, opts["rev"])
240 stop = max(stop, start - limit + 1)
240 stop = max(stop, start - limit + 1)
241 if start == nullrev:
241 if start == nullrev:
242 return
242 return
243
243
244 if path:
244 if path:
245 path = util.canonpath(repo.root, os.getcwd(), path)
245 path = util.canonpath(repo.root, os.getcwd(), path)
246 if path: # could be reset in canonpath
246 if path: # could be reset in canonpath
247 revdag = graphmod.filerevs(repo, path, start, stop)
247 revdag = graphmod.filerevs(repo, path, start, stop)
248 else:
248 else:
249 revdag = graphmod.revisions(repo, start, stop)
249 revdag = graphmod.revisions(repo, start, stop)
250
250
251 graphdag = graphabledag(ui, repo, revdag, opts)
251 graphdag = graphabledag(ui, repo, revdag, opts)
252 ascii(ui, grapher(graphdag))
252 ascii(ui, grapher(graphdag))
253
253
254 def graphrevs(repo, nodes, opts):
254 def graphrevs(repo, nodes, opts):
255 include = set(nodes)
256 limit = cmdutil.loglimit(opts)
255 limit = cmdutil.loglimit(opts)
257 count = 0
256 nodes.reverse()
258 for node in reversed(nodes):
257 if limit < sys.maxint:
259 if count >= limit:
258 nodes = nodes[:limit]
260 break
259 return graphmod.nodes(repo, nodes)
261 ctx = repo[node]
262 parents = [p.rev() for p in ctx.parents() if p.node() in include]
263 parents.sort()
264 yield (ctx, parents)
265 count += 1
266
260
267 def graphabledag(ui, repo, revdag, opts):
261 def graphabledag(ui, repo, revdag, opts):
268 showparents = [ctx.node() for ctx in repo[None].parents()]
262 showparents = [ctx.node() for ctx in repo[None].parents()]
269 displayer = show_changeset(ui, repo, opts, buffered=True)
263 displayer = show_changeset(ui, repo, opts, buffered=True)
270 for (ctx, parents) in revdag:
264 for (ctx, parents) in revdag:
271 displayer.show(ctx)
265 displayer.show(ctx)
272 lines = displayer.hunk.pop(ctx.rev()).split('\n')[:-1]
266 lines = displayer.hunk.pop(ctx.rev()).split('\n')[:-1]
273 char = ctx.node() in showparents and '@' or 'o'
267 char = ctx.node() in showparents and '@' or 'o'
274 yield (ctx.rev(), parents, char, lines)
268 yield (ctx.rev(), parents, char, lines)
275
269
276 def goutgoing(ui, repo, dest=None, **opts):
270 def goutgoing(ui, repo, dest=None, **opts):
277 """show the outgoing changesets alongside an ASCII revision graph
271 """show the outgoing changesets alongside an ASCII revision graph
278
272
279 Print the outgoing changesets alongside a revision graph drawn with
273 Print the outgoing changesets alongside a revision graph drawn with
280 ASCII characters.
274 ASCII characters.
281
275
282 Nodes printed as an @ character are parents of the working
276 Nodes printed as an @ character are parents of the working
283 directory.
277 directory.
284 """
278 """
285
279
286 check_unsupported_flags(opts)
280 check_unsupported_flags(opts)
287 dest, revs, checkout = hg.parseurl(
281 dest, revs, checkout = hg.parseurl(
288 ui.expandpath(dest or 'default-push', dest or 'default'),
282 ui.expandpath(dest or 'default-push', dest or 'default'),
289 opts.get('rev'))
283 opts.get('rev'))
290 if revs:
284 if revs:
291 revs = [repo.lookup(rev) for rev in revs]
285 revs = [repo.lookup(rev) for rev in revs]
292 other = hg.repository(cmdutil.remoteui(ui, opts), dest)
286 other = hg.repository(cmdutil.remoteui(ui, opts), dest)
293 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
287 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
294 o = repo.findoutgoing(other, force=opts.get('force'))
288 o = repo.findoutgoing(other, force=opts.get('force'))
295 if not o:
289 if not o:
296 ui.status(_("no changes found\n"))
290 ui.status(_("no changes found\n"))
297 return
291 return
298
292
299 o = repo.changelog.nodesbetween(o, revs)[0]
293 o = repo.changelog.nodesbetween(o, revs)[0]
300 revdag = graphrevs(repo, o, opts)
294 revdag = graphrevs(repo, o, opts)
301 graphdag = graphabledag(ui, repo, revdag, opts)
295 graphdag = graphabledag(ui, repo, revdag, opts)
302 ascii(ui, grapher(graphdag))
296 ascii(ui, grapher(graphdag))
303
297
304 def gincoming(ui, repo, source="default", **opts):
298 def gincoming(ui, repo, source="default", **opts):
305 """show the incoming changesets alongside an ASCII revision graph
299 """show the incoming changesets alongside an ASCII revision graph
306
300
307 Print the incoming changesets alongside a revision graph drawn with
301 Print the incoming changesets alongside a revision graph drawn with
308 ASCII characters.
302 ASCII characters.
309
303
310 Nodes printed as an @ character are parents of the working
304 Nodes printed as an @ character are parents of the working
311 directory.
305 directory.
312 """
306 """
313
307
314 check_unsupported_flags(opts)
308 check_unsupported_flags(opts)
315 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
309 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
316 other = hg.repository(cmdutil.remoteui(repo, opts), source)
310 other = hg.repository(cmdutil.remoteui(repo, opts), source)
317 ui.status(_('comparing with %s\n') % url.hidepassword(source))
311 ui.status(_('comparing with %s\n') % url.hidepassword(source))
318 if revs:
312 if revs:
319 revs = [other.lookup(rev) for rev in revs]
313 revs = [other.lookup(rev) for rev in revs]
320 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
314 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
321 if not incoming:
315 if not incoming:
322 try:
316 try:
323 os.unlink(opts["bundle"])
317 os.unlink(opts["bundle"])
324 except:
318 except:
325 pass
319 pass
326 ui.status(_("no changes found\n"))
320 ui.status(_("no changes found\n"))
327 return
321 return
328
322
329 cleanup = None
323 cleanup = None
330 try:
324 try:
331
325
332 fname = opts["bundle"]
326 fname = opts["bundle"]
333 if fname or not other.local():
327 if fname or not other.local():
334 # create a bundle (uncompressed if other repo is not local)
328 # create a bundle (uncompressed if other repo is not local)
335 if revs is None:
329 if revs is None:
336 cg = other.changegroup(incoming, "incoming")
330 cg = other.changegroup(incoming, "incoming")
337 else:
331 else:
338 cg = other.changegroupsubset(incoming, revs, 'incoming')
332 cg = other.changegroupsubset(incoming, revs, 'incoming')
339 bundletype = other.local() and "HG10BZ" or "HG10UN"
333 bundletype = other.local() and "HG10BZ" or "HG10UN"
340 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
334 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
341 # keep written bundle?
335 # keep written bundle?
342 if opts["bundle"]:
336 if opts["bundle"]:
343 cleanup = None
337 cleanup = None
344 if not other.local():
338 if not other.local():
345 # use the created uncompressed bundlerepo
339 # use the created uncompressed bundlerepo
346 other = bundlerepo.bundlerepository(ui, repo.root, fname)
340 other = bundlerepo.bundlerepository(ui, repo.root, fname)
347
341
348 chlist = other.changelog.nodesbetween(incoming, revs)[0]
342 chlist = other.changelog.nodesbetween(incoming, revs)[0]
349 revdag = graphrevs(other, chlist, opts)
343 revdag = graphrevs(other, chlist, opts)
350 graphdag = graphabledag(ui, repo, revdag, opts)
344 graphdag = graphabledag(ui, repo, revdag, opts)
351 ascii(ui, grapher(graphdag))
345 ascii(ui, grapher(graphdag))
352
346
353 finally:
347 finally:
354 if hasattr(other, 'close'):
348 if hasattr(other, 'close'):
355 other.close()
349 other.close()
356 if cleanup:
350 if cleanup:
357 os.unlink(cleanup)
351 os.unlink(cleanup)
358
352
359 def uisetup(ui):
353 def uisetup(ui):
360 '''Initialize the extension.'''
354 '''Initialize the extension.'''
361 _wrapcmd(ui, 'log', commands.table, graphlog)
355 _wrapcmd(ui, 'log', commands.table, graphlog)
362 _wrapcmd(ui, 'incoming', commands.table, gincoming)
356 _wrapcmd(ui, 'incoming', commands.table, gincoming)
363 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
357 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
364
358
365 def _wrapcmd(ui, cmd, table, wrapfn):
359 def _wrapcmd(ui, cmd, table, wrapfn):
366 '''wrap the command'''
360 '''wrap the command'''
367 def graph(orig, *args, **kwargs):
361 def graph(orig, *args, **kwargs):
368 if kwargs['graph']:
362 if kwargs['graph']:
369 return wrapfn(*args, **kwargs)
363 return wrapfn(*args, **kwargs)
370 return orig(*args, **kwargs)
364 return orig(*args, **kwargs)
371 entry = extensions.wrapcommand(table, cmd, graph)
365 entry = extensions.wrapcommand(table, cmd, graph)
372 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
366 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
373
367
374 cmdtable = {
368 cmdtable = {
375 "glog":
369 "glog":
376 (graphlog,
370 (graphlog,
377 [('l', 'limit', '', _('limit number of changes displayed')),
371 [('l', 'limit', '', _('limit number of changes displayed')),
378 ('p', 'patch', False, _('show patch')),
372 ('p', 'patch', False, _('show patch')),
379 ('r', 'rev', [], _('show the specified revision or range')),
373 ('r', 'rev', [], _('show the specified revision or range')),
380 ] + templateopts,
374 ] + templateopts,
381 _('hg glog [OPTION]... [FILE]')),
375 _('hg glog [OPTION]... [FILE]')),
382 }
376 }
@@ -1,106 +1,114 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, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from node import nullrev
9 from node import nullrev
10
10
11 def revisions(repo, start, stop):
11 def revisions(repo, start, stop):
12 """cset DAG generator yielding (rev, node, [parents]) tuples
12 """cset DAG generator yielding (rev, node, [parents]) tuples
13
13
14 This generator function walks through the revision history from revision
14 This generator function walks through the revision history from revision
15 start to revision stop (which must be less than or equal to start).
15 start to revision stop (which must be less than or equal to start).
16 """
16 """
17 assert start >= stop
17 assert start >= stop
18 cur = start
18 cur = start
19 while cur >= stop:
19 while cur >= stop:
20 ctx = repo[cur]
20 ctx = repo[cur]
21 parents = [p.rev() for p in ctx.parents() if p.rev() != nullrev]
21 parents = [p.rev() for p in ctx.parents() if p.rev() != nullrev]
22 parents.sort()
22 parents.sort()
23 yield (ctx, parents)
23 yield (ctx, parents)
24 cur -= 1
24 cur -= 1
25
25
26 def filerevs(repo, path, start, stop):
26 def filerevs(repo, path, start, stop):
27 """file cset DAG generator yielding (rev, node, [parents]) tuples
27 """file cset DAG generator yielding (rev, node, [parents]) tuples
28
28
29 This generator function walks through the revision history of a single
29 This generator function walks through the revision history of a single
30 file from revision start to revision stop (which must be less than or
30 file from revision start to revision stop (which must be less than or
31 equal to start).
31 equal to start).
32 """
32 """
33 assert start >= stop
33 assert start >= stop
34 filerev = len(repo.file(path)) - 1
34 filerev = len(repo.file(path)) - 1
35 while filerev >= 0:
35 while filerev >= 0:
36 fctx = repo.filectx(path, fileid=filerev)
36 fctx = repo.filectx(path, fileid=filerev)
37 parents = [f.linkrev() for f in fctx.parents() if f.path() == path]
37 parents = [f.linkrev() for f in fctx.parents() if f.path() == path]
38 parents.sort()
38 parents.sort()
39 if fctx.rev() <= start:
39 if fctx.rev() <= start:
40 yield (fctx, parents)
40 yield (fctx, parents)
41 if fctx.rev() <= stop:
41 if fctx.rev() <= stop:
42 break
42 break
43 filerev -= 1
43 filerev -= 1
44
44
45 def nodes(repo, nodes):
46 include = set(nodes)
47 for node in nodes:
48 ctx = repo[node]
49 parents = [p.rev() for p in ctx.parents() if p.node() in include]
50 parents.sort()
51 yield (ctx, parents)
52
45 def graph(repo, start_rev, stop_rev):
53 def graph(repo, start_rev, stop_rev):
46 """incremental revision grapher
54 """incremental revision grapher
47
55
48 This generator function walks through the revision history from
56 This generator function walks through the revision history from
49 revision start_rev to revision stop_rev (which must be less than
57 revision start_rev to revision stop_rev (which must be less than
50 or equal to start_rev) and for each revision emits tuples with the
58 or equal to start_rev) and for each revision emits tuples with the
51 following elements:
59 following elements:
52
60
53 - Context of the current node
61 - Context of the current node
54 - Tuple (col, color) with column and color index for the current node
62 - Tuple (col, color) with column and color index for the current node
55 - Edges; a list of (col, next_col, color) indicating the edges between
63 - Edges; a list of (col, next_col, color) indicating the edges between
56 the current node and its parents.
64 the current node and its parents.
57 """
65 """
58
66
59 if start_rev == nullrev and not stop_rev:
67 if start_rev == nullrev and not stop_rev:
60 return
68 return
61
69
62 assert start_rev >= stop_rev
70 assert start_rev >= stop_rev
63 assert stop_rev >= 0
71 assert stop_rev >= 0
64 curr_rev = start_rev
72 curr_rev = start_rev
65 revs = []
73 revs = []
66 cl = repo.changelog
74 cl = repo.changelog
67 colors = {}
75 colors = {}
68 new_color = 1
76 new_color = 1
69
77
70 while curr_rev >= stop_rev:
78 while curr_rev >= stop_rev:
71 # Compute revs and next_revs
79 # Compute revs and next_revs
72 if curr_rev not in revs:
80 if curr_rev not in revs:
73 revs.append(curr_rev) # new head
81 revs.append(curr_rev) # new head
74 colors[curr_rev] = new_color
82 colors[curr_rev] = new_color
75 new_color += 1
83 new_color += 1
76
84
77 idx = revs.index(curr_rev)
85 idx = revs.index(curr_rev)
78 color = colors.pop(curr_rev)
86 color = colors.pop(curr_rev)
79 next = revs[:]
87 next = revs[:]
80
88
81 # Add parents to next_revs
89 # Add parents to next_revs
82 parents = [x for x in cl.parentrevs(curr_rev) if x != nullrev]
90 parents = [x for x in cl.parentrevs(curr_rev) if x != nullrev]
83 addparents = [p for p in parents if p not in next]
91 addparents = [p for p in parents if p not in next]
84 next[idx:idx + 1] = addparents
92 next[idx:idx + 1] = addparents
85
93
86 # Set colors for the parents
94 # Set colors for the parents
87 for i, p in enumerate(addparents):
95 for i, p in enumerate(addparents):
88 if not i:
96 if not i:
89 colors[p] = color
97 colors[p] = color
90 else:
98 else:
91 colors[p] = new_color
99 colors[p] = new_color
92 new_color += 1
100 new_color += 1
93
101
94 # Add edges to the graph
102 # Add edges to the graph
95 edges = []
103 edges = []
96 for col, r in enumerate(revs):
104 for col, r in enumerate(revs):
97 if r in next:
105 if r in next:
98 edges.append((col, next.index(r), colors[r]))
106 edges.append((col, next.index(r), colors[r]))
99 elif r == curr_rev:
107 elif r == curr_rev:
100 for p in parents:
108 for p in parents:
101 edges.append((col, next.index(p), colors[p]))
109 edges.append((col, next.index(p), colors[p]))
102
110
103 # Yield and move on
111 # Yield and move on
104 yield (repo[curr_rev], (idx, color), edges)
112 yield (repo[curr_rev], (idx, color), edges)
105 revs = next
113 revs = next
106 curr_rev -= 1
114 curr_rev -= 1
General Comments 0
You need to be logged in to leave comments. Login now