18 from mercurial import hg, util, graphmod, templatekw, revset |
18 from mercurial import hg, util, graphmod, templatekw, revset |
19 |
19 |
20 cmdtable = {} |
20 cmdtable = {} |
21 command = cmdutil.command(cmdtable) |
21 command = cmdutil.command(cmdtable) |
22 testedwith = 'internal' |
22 testedwith = 'internal' |
23 |
|
24 def asciiedges(type, char, lines, seen, rev, parents): |
|
25 """adds edge info to changelog DAG walk suitable for ascii()""" |
|
26 if rev not in seen: |
|
27 seen.append(rev) |
|
28 nodeidx = seen.index(rev) |
|
29 |
|
30 knownparents = [] |
|
31 newparents = [] |
|
32 for parent in parents: |
|
33 if parent in seen: |
|
34 knownparents.append(parent) |
|
35 else: |
|
36 newparents.append(parent) |
|
37 |
|
38 ncols = len(seen) |
|
39 nextseen = seen[:] |
|
40 nextseen[nodeidx:nodeidx + 1] = newparents |
|
41 edges = [(nodeidx, nextseen.index(p)) for p in knownparents] |
|
42 |
|
43 while len(newparents) > 2: |
|
44 # ascii() only knows how to add or remove a single column between two |
|
45 # calls. Nodes with more than two parents break this constraint so we |
|
46 # introduce intermediate expansion lines to grow the active node list |
|
47 # slowly. |
|
48 edges.append((nodeidx, nodeidx)) |
|
49 edges.append((nodeidx, nodeidx + 1)) |
|
50 nmorecols = 1 |
|
51 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) |
|
52 char = '\\' |
|
53 lines = [] |
|
54 nodeidx += 1 |
|
55 ncols += 1 |
|
56 edges = [] |
|
57 del newparents[0] |
|
58 |
|
59 if len(newparents) > 0: |
|
60 edges.append((nodeidx, nodeidx)) |
|
61 if len(newparents) > 1: |
|
62 edges.append((nodeidx, nodeidx + 1)) |
|
63 nmorecols = len(nextseen) - ncols |
|
64 seen[:] = nextseen |
|
65 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) |
|
66 |
|
67 def _fixlongrightedges(edges): |
|
68 for (i, (start, end)) in enumerate(edges): |
|
69 if end > start: |
|
70 edges[i] = (start, end + 1) |
|
71 |
|
72 def _getnodelineedgestail( |
|
73 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): |
|
74 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: |
|
75 # Still going in the same non-vertical direction. |
|
76 if n_columns_diff == -1: |
|
77 start = max(node_index + 1, p_node_index) |
|
78 tail = ["|", " "] * (start - node_index - 1) |
|
79 tail.extend(["/", " "] * (n_columns - start)) |
|
80 return tail |
|
81 else: |
|
82 return ["\\", " "] * (n_columns - node_index - 1) |
|
83 else: |
|
84 return ["|", " "] * (n_columns - node_index - 1) |
|
85 |
|
86 def _drawedges(edges, nodeline, interline): |
|
87 for (start, end) in edges: |
|
88 if start == end + 1: |
|
89 interline[2 * end + 1] = "/" |
|
90 elif start == end - 1: |
|
91 interline[2 * start + 1] = "\\" |
|
92 elif start == end: |
|
93 interline[2 * start] = "|" |
|
94 else: |
|
95 if 2 * end >= len(nodeline): |
|
96 continue |
|
97 nodeline[2 * end] = "+" |
|
98 if start > end: |
|
99 (start, end) = (end, start) |
|
100 for i in range(2 * start + 1, 2 * end): |
|
101 if nodeline[i] != "+": |
|
102 nodeline[i] = "-" |
|
103 |
|
104 def _getpaddingline(ni, n_columns, edges): |
|
105 line = [] |
|
106 line.extend(["|", " "] * ni) |
|
107 if (ni, ni - 1) in edges or (ni, ni) in edges: |
|
108 # (ni, ni - 1) (ni, ni) |
|
109 # | | | | | | | | |
|
110 # +---o | | o---+ |
|
111 # | | c | | c | | |
|
112 # | |/ / | |/ / |
|
113 # | | | | | | |
|
114 c = "|" |
|
115 else: |
|
116 c = " " |
|
117 line.extend([c, " "]) |
|
118 line.extend(["|", " "] * (n_columns - ni - 1)) |
|
119 return line |
|
120 |
|
121 def asciistate(): |
|
122 """returns the initial value for the "state" argument to ascii()""" |
|
123 return [0, 0] |
|
124 |
|
125 def ascii(ui, state, type, char, text, coldata): |
|
126 """prints an ASCII graph of the DAG |
|
127 |
|
128 takes the following arguments (one call per node in the graph): |
|
129 |
|
130 - ui to write to |
|
131 - Somewhere to keep the needed state in (init to asciistate()) |
|
132 - Column of the current node in the set of ongoing edges. |
|
133 - Type indicator of node data, usually 'C' for changesets. |
|
134 - Payload: (char, lines): |
|
135 - Character to use as node's symbol. |
|
136 - List of lines to display as the node's text. |
|
137 - Edges; a list of (col, next_col) indicating the edges between |
|
138 the current node and its parents. |
|
139 - Number of columns (ongoing edges) in the current revision. |
|
140 - The difference between the number of columns (ongoing edges) |
|
141 in the next revision and the number of columns (ongoing edges) |
|
142 in the current revision. That is: -1 means one column removed; |
|
143 0 means no columns added or removed; 1 means one column added. |
|
144 """ |
|
145 |
|
146 idx, edges, ncols, coldiff = coldata |
|
147 assert -2 < coldiff < 2 |
|
148 if coldiff == -1: |
|
149 # Transform |
|
150 # |
|
151 # | | | | | | |
|
152 # o | | into o---+ |
|
153 # |X / |/ / |
|
154 # | | | | |
|
155 _fixlongrightedges(edges) |
|
156 |
|
157 # add_padding_line says whether to rewrite |
|
158 # |
|
159 # | | | | | | | | |
|
160 # | o---+ into | o---+ |
|
161 # | / / | | | # <--- padding line |
|
162 # o | | | / / |
|
163 # o | | |
|
164 add_padding_line = (len(text) > 2 and coldiff == -1 and |
|
165 [x for (x, y) in edges if x + 1 < y]) |
|
166 |
|
167 # fix_nodeline_tail says whether to rewrite |
|
168 # |
|
169 # | | o | | | | o | | |
|
170 # | | |/ / | | |/ / |
|
171 # | o | | into | o / / # <--- fixed nodeline tail |
|
172 # | |/ / | |/ / |
|
173 # o | | o | | |
|
174 fix_nodeline_tail = len(text) <= 2 and not add_padding_line |
|
175 |
|
176 # nodeline is the line containing the node character (typically o) |
|
177 nodeline = ["|", " "] * idx |
|
178 nodeline.extend([char, " "]) |
|
179 |
|
180 nodeline.extend( |
|
181 _getnodelineedgestail(idx, state[1], ncols, coldiff, |
|
182 state[0], fix_nodeline_tail)) |
|
183 |
|
184 # shift_interline is the line containing the non-vertical |
|
185 # edges between this entry and the next |
|
186 shift_interline = ["|", " "] * idx |
|
187 if coldiff == -1: |
|
188 n_spaces = 1 |
|
189 edge_ch = "/" |
|
190 elif coldiff == 0: |
|
191 n_spaces = 2 |
|
192 edge_ch = "|" |
|
193 else: |
|
194 n_spaces = 3 |
|
195 edge_ch = "\\" |
|
196 shift_interline.extend(n_spaces * [" "]) |
|
197 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1)) |
|
198 |
|
199 # draw edges from the current node to its parents |
|
200 _drawedges(edges, nodeline, shift_interline) |
|
201 |
|
202 # lines is the list of all graph lines to print |
|
203 lines = [nodeline] |
|
204 if add_padding_line: |
|
205 lines.append(_getpaddingline(idx, ncols, edges)) |
|
206 lines.append(shift_interline) |
|
207 |
|
208 # make sure that there are as many graph lines as there are |
|
209 # log strings |
|
210 while len(text) < len(lines): |
|
211 text.append("") |
|
212 if len(lines) < len(text): |
|
213 extra_interline = ["|", " "] * (ncols + coldiff) |
|
214 while len(lines) < len(text): |
|
215 lines.append(extra_interline) |
|
216 |
|
217 # print lines |
|
218 indentation_level = max(ncols, ncols + coldiff) |
|
219 for (line, logstr) in zip(lines, text): |
|
220 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) |
|
221 ui.write(ln.rstrip() + '\n') |
|
222 |
|
223 # ... and start over |
|
224 state[0] = coldiff |
|
225 state[1] = idx |
|
226 |
23 |
227 def _checkunsupportedflags(pats, opts): |
24 def _checkunsupportedflags(pats, opts): |
228 for op in ["newest_first"]: |
25 for op in ["newest_first"]: |
229 if op in opts and opts[op]: |
26 if op in opts and opts[op]: |
230 raise util.Abort(_("-G/--graph option is incompatible with --%s") |
27 raise util.Abort(_("-G/--graph option is incompatible with --%s") |