Mercurial > hg
annotate hgext/graphlog.py @ 6689:d2ac53fe216e
convert: cvsps - User interface to CVS changeset code in cvsps.py
author | Frank Kingswood <frank@kingswood-consulting.co.uk> |
---|---|
date | Sun, 15 Jun 2008 15:59:53 +0100 |
parents | 53465a7464e2 |
children | fb42030d79d6 |
rev | line source |
---|---|
4344 | 1 # ASCII graph log extension for Mercurial |
2 # | |
3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net> | |
4516
96d8a56d4ef9
Removed trailing whitespace and tabs from python files
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4509
diff
changeset
|
4 # |
4344 | 5 # This software may be used and distributed according to the terms of |
6 # the GNU General Public License, incorporated herein by reference. | |
6666
53465a7464e2
convert comments to docstrings in a bunch of extensions
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
6212
diff
changeset
|
7 '''show revision graphs in terminal windows''' |
4344 | 8 |
5938
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
9 import os |
4344 | 10 import sys |
11 from mercurial.cmdutil import revrange, show_changeset | |
6192
cd65a67aff31
Introduce templateopts and logopts to reduce duplicate option definitions.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5968
diff
changeset
|
12 from mercurial.commands import templateopts |
4344 | 13 from mercurial.i18n import _ |
6212 | 14 from mercurial.node import nullrev |
5938
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
15 from mercurial.util import Abort, canonpath |
4344 | 16 |
17 def revision_grapher(repo, start_rev, stop_rev): | |
18 """incremental revision grapher | |
19 | |
20 This generator function walks through the revision history from | |
21 revision start_rev to revision stop_rev (which must be less than | |
22 or equal to start_rev) and for each revision emits tuples with the | |
23 following elements: | |
24 | |
25 - Current revision. | |
26 - Current node. | |
27 - Column of the current node in the set of ongoing edges. | |
28 - Edges; a list of (col, next_col) indicating the edges between | |
29 the current node and its parents. | |
30 - Number of columns (ongoing edges) in the current revision. | |
31 - The difference between the number of columns (ongoing edges) | |
32 in the next revision and the number of columns (ongoing edges) | |
33 in the current revision. That is: -1 means one column removed; | |
34 0 means no columns added or removed; 1 means one column added. | |
35 """ | |
36 | |
37 assert start_rev >= stop_rev | |
38 curr_rev = start_rev | |
39 revs = [] | |
40 while curr_rev >= stop_rev: | |
41 node = repo.changelog.node(curr_rev) | |
42 | |
43 # Compute revs and next_revs. | |
44 if curr_rev not in revs: | |
45 # New head. | |
46 revs.append(curr_rev) | |
47 rev_index = revs.index(curr_rev) | |
48 next_revs = revs[:] | |
49 | |
50 # Add parents to next_revs. | |
51 parents = get_rev_parents(repo, curr_rev) | |
52 parents_to_add = [] | |
53 for parent in parents: | |
54 if parent not in next_revs: | |
55 parents_to_add.append(parent) | |
56 parents_to_add.sort() | |
57 next_revs[rev_index:rev_index + 1] = parents_to_add | |
58 | |
59 edges = [] | |
60 for parent in parents: | |
61 edges.append((rev_index, next_revs.index(parent))) | |
62 | |
63 n_columns_diff = len(next_revs) - len(revs) | |
64 yield (curr_rev, node, rev_index, edges, len(revs), n_columns_diff) | |
65 | |
66 revs = next_revs | |
67 curr_rev -= 1 | |
68 | |
5938
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
69 def filelog_grapher(repo, path, start_rev, stop_rev): |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
70 """incremental file log grapher |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
71 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
72 This generator function walks through the revision history of a |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
73 single file from revision start_rev to revision stop_rev (which must |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
74 be less than or equal to start_rev) and for each revision emits |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
75 tuples with the following elements: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
76 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
77 - Current revision. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
78 - Current node. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
79 - Column of the current node in the set of ongoing edges. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
80 - Edges; a list of (col, next_col) indicating the edges between |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
81 the current node and its parents. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
82 - Number of columns (ongoing edges) in the current revision. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
83 - The difference between the number of columns (ongoing edges) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
84 in the next revision and the number of columns (ongoing edges) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
85 in the current revision. That is: -1 means one column removed; |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
86 0 means no columns added or removed; 1 means one column added. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
87 """ |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
88 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
89 assert start_rev >= stop_rev |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
90 curr_rev = start_rev |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
91 revs = [] |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
92 filerev = repo.file(path).count() - 1 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
93 while filerev >= 0: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
94 fctx = repo.filectx(path, fileid=filerev) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
95 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
96 # Compute revs and next_revs. |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
97 if filerev not in revs: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
98 revs.append(filerev) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
99 rev_index = revs.index(filerev) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
100 next_revs = revs[:] |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
101 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
102 # Add parents to next_revs. |
5968
6dcc190ffc36
graphlog: skip filectx parents in other filelogs
Steve Borho <steve@borho.org>
parents:
5942
diff
changeset
|
103 parents = [f.filerev() for f in fctx.parents() if f.path() == path] |
5938
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
104 parents_to_add = [] |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
105 for parent in parents: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
106 if parent not in next_revs: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
107 parents_to_add.append(parent) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
108 parents_to_add.sort() |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
109 next_revs[rev_index:rev_index + 1] = parents_to_add |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
110 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
111 edges = [] |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
112 for parent in parents: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
113 edges.append((rev_index, next_revs.index(parent))) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
114 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
115 changerev = fctx.linkrev() |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
116 if changerev <= start_rev: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
117 node = repo.changelog.node(changerev) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
118 n_columns_diff = len(next_revs) - len(revs) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
119 yield (changerev, node, rev_index, edges, len(revs), n_columns_diff) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
120 if changerev <= stop_rev: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
121 break |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
122 revs = next_revs |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
123 filerev -= 1 |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
124 |
4344 | 125 def get_rev_parents(repo, rev): |
126 return [x for x in repo.changelog.parentrevs(rev) if x != nullrev] | |
127 | |
128 def fix_long_right_edges(edges): | |
129 for (i, (start, end)) in enumerate(edges): | |
130 if end > start: | |
131 edges[i] = (start, end + 1) | |
132 | |
133 def draw_edges(edges, nodeline, interline): | |
134 for (start, end) in edges: | |
135 if start == end + 1: | |
136 interline[2 * end + 1] = "/" | |
137 elif start == end - 1: | |
138 interline[2 * start + 1] = "\\" | |
139 elif start == end: | |
140 interline[2 * start] = "|" | |
141 else: | |
142 nodeline[2 * end] = "+" | |
143 if start > end: | |
144 (start, end) = (end,start) | |
145 for i in range(2 * start + 1, 2 * end): | |
146 if nodeline[i] != "+": | |
147 nodeline[i] = "-" | |
148 | |
149 def format_line(line, level, logstr): | |
150 text = "%-*s %s" % (2 * level, "".join(line), logstr) | |
151 return "%s\n" % text.rstrip() | |
152 | |
153 def get_nodeline_edges_tail( | |
154 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): | |
155 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: | |
156 # Still going in the same non-vertical direction. | |
157 if n_columns_diff == -1: | |
158 start = max(node_index + 1, p_node_index) | |
159 tail = ["|", " "] * (start - node_index - 1) | |
160 tail.extend(["/", " "] * (n_columns - start)) | |
161 return tail | |
162 else: | |
163 return ["\\", " "] * (n_columns - node_index - 1) | |
164 else: | |
165 return ["|", " "] * (n_columns - node_index - 1) | |
166 | |
167 def get_padding_line(ni, n_columns, edges): | |
168 line = [] | |
169 line.extend(["|", " "] * ni) | |
170 if (ni, ni - 1) in edges or (ni, ni) in edges: | |
171 # (ni, ni - 1) (ni, ni) | |
172 # | | | | | | | | | |
173 # +---o | | o---+ | |
174 # | | c | | c | | | |
175 # | |/ / | |/ / | |
176 # | | | | | | | |
177 c = "|" | |
178 else: | |
179 c = " " | |
180 line.extend([c, " "]) | |
181 line.extend(["|", " "] * (n_columns - ni - 1)) | |
182 return line | |
183 | |
184 def get_limit(limit_opt): | |
185 if limit_opt: | |
186 try: | |
187 limit = int(limit_opt) | |
188 except ValueError: | |
189 raise Abort(_("limit must be a positive integer")) | |
190 if limit <= 0: | |
191 raise Abort(_("limit must be positive")) | |
192 else: | |
193 limit = sys.maxint | |
194 return limit | |
195 | |
196 def get_revs(repo, rev_opt): | |
197 if rev_opt: | |
198 revs = revrange(repo, rev_opt) | |
199 return (max(revs), min(revs)) | |
200 else: | |
201 return (repo.changelog.count() - 1, 0) | |
202 | |
5938
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
203 def graphlog(ui, repo, path=None, **opts): |
4344 | 204 """show revision history alongside an ASCII revision graph |
205 | |
206 Print a revision history alongside a revision graph drawn with | |
207 ASCII characters. | |
208 | |
4583
11cf78983961
Reverted changesets 9d1380e5c8c5 and 1d46169ec197: show @ as glog parent again.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4516
diff
changeset
|
209 Nodes printed as an @ character are parents of the working |
4344 | 210 directory. |
211 """ | |
212 | |
213 limit = get_limit(opts["limit"]) | |
214 (start_rev, stop_rev) = get_revs(repo, opts["rev"]) | |
215 stop_rev = max(stop_rev, start_rev - limit + 1) | |
216 if start_rev == nullrev: | |
217 return | |
218 cs_printer = show_changeset(ui, repo, opts) | |
5938
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
219 if path: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
220 cpath = canonpath(repo.root, os.getcwd(), path) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
221 grapher = filelog_grapher(repo, cpath, start_rev, stop_rev) |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
222 else: |
9ed100559851
graphlog: add filelog revision grapher
Steve Borho <steve@borho.org>
parents:
4735
diff
changeset
|
223 grapher = revision_grapher(repo, start_rev, stop_rev) |
4344 | 224 repo_parents = repo.dirstate.parents() |
225 prev_n_columns_diff = 0 | |
226 prev_node_index = 0 | |
227 | |
228 for (rev, node, node_index, edges, n_columns, n_columns_diff) in grapher: | |
229 # log_strings is the list of all log strings to draw alongside | |
230 # the graph. | |
231 ui.pushbuffer() | |
232 cs_printer.show(rev, node) | |
233 log_strings = ui.popbuffer().split("\n")[:-1] | |
234 | |
235 if n_columns_diff == -1: | |
236 # Transform | |
237 # | |
238 # | | | | | | | |
239 # o | | into o---+ | |
240 # |X / |/ / | |
241 # | | | | | |
242 fix_long_right_edges(edges) | |
243 | |
244 # add_padding_line says whether to rewrite | |
245 # | |
246 # | | | | | | | | | |
247 # | o---+ into | o---+ | |
248 # | / / | | | # <--- padding line | |
249 # o | | | / / | |
250 # o | | | |
4633
ff7253a0d1da
Cleanup of whitespace, indentation and line continuation.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4583
diff
changeset
|
251 add_padding_line = (len(log_strings) > 2 and |
ff7253a0d1da
Cleanup of whitespace, indentation and line continuation.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4583
diff
changeset
|
252 n_columns_diff == -1 and |
ff7253a0d1da
Cleanup of whitespace, indentation and line continuation.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4583
diff
changeset
|
253 [x for (x, y) in edges if x + 1 < y]) |
4344 | 254 |
255 # fix_nodeline_tail says whether to rewrite | |
256 # | |
257 # | | o | | | | o | | | |
258 # | | |/ / | | |/ / | |
259 # | o | | into | o / / # <--- fixed nodeline tail | |
260 # | |/ / | |/ / | |
261 # o | | o | | | |
262 fix_nodeline_tail = len(log_strings) <= 2 and not add_padding_line | |
263 | |
4583
11cf78983961
Reverted changesets 9d1380e5c8c5 and 1d46169ec197: show @ as glog parent again.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4516
diff
changeset
|
264 # nodeline is the line containing the node character (@ or o). |
4344 | 265 nodeline = ["|", " "] * node_index |
266 if node in repo_parents: | |
4583
11cf78983961
Reverted changesets 9d1380e5c8c5 and 1d46169ec197: show @ as glog parent again.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4516
diff
changeset
|
267 node_ch = "@" |
4344 | 268 else: |
269 node_ch = "o" | |
270 nodeline.extend([node_ch, " "]) | |
271 | |
272 nodeline.extend( | |
273 get_nodeline_edges_tail( | |
274 node_index, prev_node_index, n_columns, n_columns_diff, | |
275 prev_n_columns_diff, fix_nodeline_tail)) | |
276 | |
277 # shift_interline is the line containing the non-vertical | |
278 # edges between this entry and the next. | |
279 shift_interline = ["|", " "] * node_index | |
280 if n_columns_diff == -1: | |
281 n_spaces = 1 | |
282 edge_ch = "/" | |
283 elif n_columns_diff == 0: | |
284 n_spaces = 2 | |
285 edge_ch = "|" | |
286 else: | |
287 n_spaces = 3 | |
288 edge_ch = "\\" | |
289 shift_interline.extend(n_spaces * [" "]) | |
290 shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1)) | |
291 | |
292 # Draw edges from the current node to its parents. | |
293 draw_edges(edges, nodeline, shift_interline) | |
294 | |
295 # lines is the list of all graph lines to print. | |
296 lines = [nodeline] | |
297 if add_padding_line: | |
298 lines.append(get_padding_line(node_index, n_columns, edges)) | |
299 lines.append(shift_interline) | |
300 | |
301 # Make sure that there are as many graph lines as there are | |
302 # log strings. | |
303 while len(log_strings) < len(lines): | |
304 log_strings.append("") | |
305 if len(lines) < len(log_strings): | |
306 extra_interline = ["|", " "] * (n_columns + n_columns_diff) | |
307 while len(lines) < len(log_strings): | |
308 lines.append(extra_interline) | |
309 | |
310 # Print lines. | |
311 indentation_level = max(n_columns, n_columns + n_columns_diff) | |
312 for (line, logstr) in zip(lines, log_strings): | |
313 ui.write(format_line(line, indentation_level, logstr)) | |
314 | |
315 # ...and start over. | |
316 prev_node_index = node_index | |
317 prev_n_columns_diff = n_columns_diff | |
318 | |
319 cmdtable = { | |
320 "glog": | |
4730
eadfaa9ec487
Updated command tables in commands.py and hgext extensions.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4633
diff
changeset
|
321 (graphlog, |
eadfaa9ec487
Updated command tables in commands.py and hgext extensions.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4633
diff
changeset
|
322 [('l', 'limit', '', _('limit number of changes displayed')), |
eadfaa9ec487
Updated command tables in commands.py and hgext extensions.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4633
diff
changeset
|
323 ('p', 'patch', False, _('show patch')), |
eadfaa9ec487
Updated command tables in commands.py and hgext extensions.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4633
diff
changeset
|
324 ('r', 'rev', [], _('show the specified revision or range')), |
6192
cd65a67aff31
Introduce templateopts and logopts to reduce duplicate option definitions.
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5968
diff
changeset
|
325 ] + templateopts, |
5942
b75105de8573
glog shows at most one file: correct synopsis
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5940
diff
changeset
|
326 _('hg glog [OPTION]... [FILE]')), |
4344 | 327 } |