Mercurial > public > mercurial-scm > hg-stable
annotate hgext/graphlog.py @ 4531:b51a8138292a
Avoid extra filelogs entries.
Right now, there are some situations in which localrepo.filecommit can
create filelog entries even though they're not needed. For example:
- permissions for a file have changed;
- qrefresh can create a filelog entry identical to its parent (see the
added test);
- convert-repo creates extra filelog entries in every merge where the
first parent has added files (for example, changeset ebebe9577a1a of
the kernel repo added extra filelog entries to files in the
arch/blackfin directory, even though the merge should only touch the
drivers/ata directory). This makes "hg log file" in a converted repo
less useful than it could be, since it may mention many merges that
don't actually touch that specific file.
They all come from the same basic problem: localrepo.commit (through
filecommit) creates new filelog entries for all files passed to it
(except for some cases during a merge).
Patch and test case provided by Benoit.
This should fix issue351.
author | Alexis S. L. Carvalho <alexis@cecm.usp.br> |
---|---|
date | Sat, 09 Jun 2007 01:04:28 -0300 |
parents | 96d8a56d4ef9 |
children | 11cf78983961 |
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. | |
7 | |
8 import sys | |
9 from mercurial.cmdutil import revrange, show_changeset | |
10 from mercurial.i18n import _ | |
11 from mercurial.node import nullid, nullrev | |
12 from mercurial.util import Abort | |
13 | |
14 def revision_grapher(repo, start_rev, stop_rev): | |
15 """incremental revision grapher | |
16 | |
17 This generator function walks through the revision history from | |
18 revision start_rev to revision stop_rev (which must be less than | |
19 or equal to start_rev) and for each revision emits tuples with the | |
20 following elements: | |
21 | |
22 - Current revision. | |
23 - Current node. | |
24 - Column of the current node in the set of ongoing edges. | |
25 - Edges; a list of (col, next_col) indicating the edges between | |
26 the current node and its parents. | |
27 - Number of columns (ongoing edges) in the current revision. | |
28 - The difference between the number of columns (ongoing edges) | |
29 in the next revision and the number of columns (ongoing edges) | |
30 in the current revision. That is: -1 means one column removed; | |
31 0 means no columns added or removed; 1 means one column added. | |
32 """ | |
33 | |
34 assert start_rev >= stop_rev | |
35 curr_rev = start_rev | |
36 revs = [] | |
37 while curr_rev >= stop_rev: | |
38 node = repo.changelog.node(curr_rev) | |
39 | |
40 # Compute revs and next_revs. | |
41 if curr_rev not in revs: | |
42 # New head. | |
43 revs.append(curr_rev) | |
44 rev_index = revs.index(curr_rev) | |
45 next_revs = revs[:] | |
46 | |
47 # Add parents to next_revs. | |
48 parents = get_rev_parents(repo, curr_rev) | |
49 parents_to_add = [] | |
50 for parent in parents: | |
51 if parent not in next_revs: | |
52 parents_to_add.append(parent) | |
53 parents_to_add.sort() | |
54 next_revs[rev_index:rev_index + 1] = parents_to_add | |
55 | |
56 edges = [] | |
57 for parent in parents: | |
58 edges.append((rev_index, next_revs.index(parent))) | |
59 | |
60 n_columns_diff = len(next_revs) - len(revs) | |
61 yield (curr_rev, node, rev_index, edges, len(revs), n_columns_diff) | |
62 | |
63 revs = next_revs | |
64 curr_rev -= 1 | |
65 | |
66 def get_rev_parents(repo, rev): | |
67 return [x for x in repo.changelog.parentrevs(rev) if x != nullrev] | |
68 | |
69 def fix_long_right_edges(edges): | |
70 for (i, (start, end)) in enumerate(edges): | |
71 if end > start: | |
72 edges[i] = (start, end + 1) | |
73 | |
74 def draw_edges(edges, nodeline, interline): | |
75 for (start, end) in edges: | |
76 if start == end + 1: | |
77 interline[2 * end + 1] = "/" | |
78 elif start == end - 1: | |
79 interline[2 * start + 1] = "\\" | |
80 elif start == end: | |
81 interline[2 * start] = "|" | |
82 else: | |
83 nodeline[2 * end] = "+" | |
84 if start > end: | |
85 (start, end) = (end,start) | |
86 for i in range(2 * start + 1, 2 * end): | |
87 if nodeline[i] != "+": | |
88 nodeline[i] = "-" | |
89 | |
90 def format_line(line, level, logstr): | |
91 text = "%-*s %s" % (2 * level, "".join(line), logstr) | |
92 return "%s\n" % text.rstrip() | |
93 | |
94 def get_nodeline_edges_tail( | |
95 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): | |
96 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: | |
97 # Still going in the same non-vertical direction. | |
98 if n_columns_diff == -1: | |
99 start = max(node_index + 1, p_node_index) | |
100 tail = ["|", " "] * (start - node_index - 1) | |
101 tail.extend(["/", " "] * (n_columns - start)) | |
102 return tail | |
103 else: | |
104 return ["\\", " "] * (n_columns - node_index - 1) | |
105 else: | |
106 return ["|", " "] * (n_columns - node_index - 1) | |
107 | |
108 def get_padding_line(ni, n_columns, edges): | |
109 line = [] | |
110 line.extend(["|", " "] * ni) | |
111 if (ni, ni - 1) in edges or (ni, ni) in edges: | |
112 # (ni, ni - 1) (ni, ni) | |
113 # | | | | | | | | | |
114 # +---o | | o---+ | |
115 # | | c | | c | | | |
116 # | |/ / | |/ / | |
117 # | | | | | | | |
118 c = "|" | |
119 else: | |
120 c = " " | |
121 line.extend([c, " "]) | |
122 line.extend(["|", " "] * (n_columns - ni - 1)) | |
123 return line | |
124 | |
125 def get_limit(limit_opt): | |
126 if limit_opt: | |
127 try: | |
128 limit = int(limit_opt) | |
129 except ValueError: | |
130 raise Abort(_("limit must be a positive integer")) | |
131 if limit <= 0: | |
132 raise Abort(_("limit must be positive")) | |
133 else: | |
134 limit = sys.maxint | |
135 return limit | |
136 | |
137 def get_revs(repo, rev_opt): | |
138 if rev_opt: | |
139 revs = revrange(repo, rev_opt) | |
140 return (max(revs), min(revs)) | |
141 else: | |
142 return (repo.changelog.count() - 1, 0) | |
143 | |
144 def graphlog(ui, repo, *args, **opts): | |
145 """show revision history alongside an ASCII revision graph | |
146 | |
147 Print a revision history alongside a revision graph drawn with | |
148 ASCII characters. | |
149 | |
4509
9d1380e5c8c5
graphlog: Print . instead of @ for working directory parents
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4344
diff
changeset
|
150 Nodes printed as a . character are parents of the working |
4344 | 151 directory. |
152 """ | |
153 | |
154 limit = get_limit(opts["limit"]) | |
155 (start_rev, stop_rev) = get_revs(repo, opts["rev"]) | |
156 stop_rev = max(stop_rev, start_rev - limit + 1) | |
157 if start_rev == nullrev: | |
158 return | |
159 cs_printer = show_changeset(ui, repo, opts) | |
160 grapher = revision_grapher(repo, start_rev, stop_rev) | |
161 repo_parents = repo.dirstate.parents() | |
162 prev_n_columns_diff = 0 | |
163 prev_node_index = 0 | |
164 | |
165 for (rev, node, node_index, edges, n_columns, n_columns_diff) in grapher: | |
166 # log_strings is the list of all log strings to draw alongside | |
167 # the graph. | |
168 ui.pushbuffer() | |
169 cs_printer.show(rev, node) | |
170 log_strings = ui.popbuffer().split("\n")[:-1] | |
171 | |
172 if n_columns_diff == -1: | |
173 # Transform | |
174 # | |
175 # | | | | | | | |
176 # o | | into o---+ | |
177 # |X / |/ / | |
178 # | | | | | |
179 fix_long_right_edges(edges) | |
180 | |
181 # add_padding_line says whether to rewrite | |
182 # | |
183 # | | | | | | | | | |
184 # | o---+ into | o---+ | |
185 # | / / | | | # <--- padding line | |
186 # o | | | / / | |
187 # o | | | |
188 add_padding_line = \ | |
189 len(log_strings) > 2 and \ | |
190 n_columns_diff == -1 and \ | |
191 [x for (x, y) in edges if x + 1 < y] | |
192 | |
193 # fix_nodeline_tail says whether to rewrite | |
194 # | |
195 # | | o | | | | o | | | |
196 # | | |/ / | | |/ / | |
197 # | o | | into | o / / # <--- fixed nodeline tail | |
198 # | |/ / | |/ / | |
199 # o | | o | | | |
200 fix_nodeline_tail = len(log_strings) <= 2 and not add_padding_line | |
201 | |
4509
9d1380e5c8c5
graphlog: Print . instead of @ for working directory parents
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4344
diff
changeset
|
202 # nodeline is the line containing the node character (. or o). |
4344 | 203 nodeline = ["|", " "] * node_index |
204 if node in repo_parents: | |
4509
9d1380e5c8c5
graphlog: Print . instead of @ for working directory parents
Thomas Arendsen Hein <thomas@intevation.de>
parents:
4344
diff
changeset
|
205 node_ch = "." |
4344 | 206 else: |
207 node_ch = "o" | |
208 nodeline.extend([node_ch, " "]) | |
209 | |
210 nodeline.extend( | |
211 get_nodeline_edges_tail( | |
212 node_index, prev_node_index, n_columns, n_columns_diff, | |
213 prev_n_columns_diff, fix_nodeline_tail)) | |
214 | |
215 # shift_interline is the line containing the non-vertical | |
216 # edges between this entry and the next. | |
217 shift_interline = ["|", " "] * node_index | |
218 if n_columns_diff == -1: | |
219 n_spaces = 1 | |
220 edge_ch = "/" | |
221 elif n_columns_diff == 0: | |
222 n_spaces = 2 | |
223 edge_ch = "|" | |
224 else: | |
225 n_spaces = 3 | |
226 edge_ch = "\\" | |
227 shift_interline.extend(n_spaces * [" "]) | |
228 shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1)) | |
229 | |
230 # Draw edges from the current node to its parents. | |
231 draw_edges(edges, nodeline, shift_interline) | |
232 | |
233 # lines is the list of all graph lines to print. | |
234 lines = [nodeline] | |
235 if add_padding_line: | |
236 lines.append(get_padding_line(node_index, n_columns, edges)) | |
237 lines.append(shift_interline) | |
238 | |
239 # Make sure that there are as many graph lines as there are | |
240 # log strings. | |
241 while len(log_strings) < len(lines): | |
242 log_strings.append("") | |
243 if len(lines) < len(log_strings): | |
244 extra_interline = ["|", " "] * (n_columns + n_columns_diff) | |
245 while len(lines) < len(log_strings): | |
246 lines.append(extra_interline) | |
247 | |
248 # Print lines. | |
249 indentation_level = max(n_columns, n_columns + n_columns_diff) | |
250 for (line, logstr) in zip(lines, log_strings): | |
251 ui.write(format_line(line, indentation_level, logstr)) | |
252 | |
253 # ...and start over. | |
254 prev_node_index = node_index | |
255 prev_n_columns_diff = n_columns_diff | |
256 | |
257 cmdtable = { | |
258 "glog": | |
259 (graphlog, | |
260 [("l", "limit", "", _("limit number of changes displayed")), | |
261 ("p", "patch", False, _("show patch")), | |
262 ("r", "rev", [], _("show the specified revision or range")), | |
263 ("", "style", "", _("display using template map file")), | |
264 ("", "template", "", _("display with template"))], | |
265 "hg glog [OPTIONS]"), | |
266 } |