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