Show More
@@ -0,0 +1,220 b'' | |||||
|
1 | $ cat >> $HGRCPATH << EOF | |||
|
2 | > [extensions] | |||
|
3 | > show = | |||
|
4 | > EOF | |||
|
5 | ||||
|
6 | $ hg init repo0 | |||
|
7 | $ cd repo0 | |||
|
8 | ||||
|
9 | Empty repo / no checkout results in error | |||
|
10 | ||||
|
11 | $ hg show stack | |||
|
12 | abort: stack view only available when there is a working directory | |||
|
13 | [255] | |||
|
14 | ||||
|
15 | Stack displays single draft changeset as root revision | |||
|
16 | ||||
|
17 | $ echo 0 > foo | |||
|
18 | $ hg -q commit -A -m 'commit 0' | |||
|
19 | $ hg show stack | |||
|
20 | @ 9f171 commit 0 | |||
|
21 | ||||
|
22 | Stack displays multiple draft changesets | |||
|
23 | ||||
|
24 | $ echo 1 > foo | |||
|
25 | $ hg commit -m 'commit 1' | |||
|
26 | $ echo 2 > foo | |||
|
27 | $ hg commit -m 'commit 2' | |||
|
28 | $ echo 3 > foo | |||
|
29 | $ hg commit -m 'commit 3' | |||
|
30 | $ echo 4 > foo | |||
|
31 | $ hg commit -m 'commit 4' | |||
|
32 | $ hg show stack | |||
|
33 | @ 2737b commit 4 | |||
|
34 | o d1a69 commit 3 | |||
|
35 | o 128c8 commit 2 | |||
|
36 | o 181cc commit 1 | |||
|
37 | o 9f171 commit 0 | |||
|
38 | ||||
|
39 | Public parent of draft base is displayed, separated from stack | |||
|
40 | ||||
|
41 | $ hg phase --public -r 0 | |||
|
42 | $ hg show stack | |||
|
43 | @ 2737b commit 4 | |||
|
44 | o d1a69 commit 3 | |||
|
45 | o 128c8 commit 2 | |||
|
46 | o 181cc commit 1 | |||
|
47 | / (stack base) | |||
|
48 | o 9f171 commit 0 | |||
|
49 | ||||
|
50 | $ hg phase --public -r 1 | |||
|
51 | $ hg show stack | |||
|
52 | @ 2737b commit 4 | |||
|
53 | o d1a69 commit 3 | |||
|
54 | o 128c8 commit 2 | |||
|
55 | / (stack base) | |||
|
56 | o 181cc commit 1 | |||
|
57 | ||||
|
58 | Draft descendants are shown | |||
|
59 | ||||
|
60 | $ hg -q up 2 | |||
|
61 | $ hg show stack | |||
|
62 | o 2737b commit 4 | |||
|
63 | o d1a69 commit 3 | |||
|
64 | @ 128c8 commit 2 | |||
|
65 | / (stack base) | |||
|
66 | o 181cc commit 1 | |||
|
67 | ||||
|
68 | $ hg -q up 3 | |||
|
69 | $ hg show stack | |||
|
70 | o 2737b commit 4 | |||
|
71 | @ d1a69 commit 3 | |||
|
72 | o 128c8 commit 2 | |||
|
73 | / (stack base) | |||
|
74 | o 181cc commit 1 | |||
|
75 | ||||
|
76 | working dir on public changeset should display special message | |||
|
77 | ||||
|
78 | $ hg -q up 1 | |||
|
79 | $ hg show stack | |||
|
80 | (empty stack; working directory is a published changeset) | |||
|
81 | ||||
|
82 | Branch point in descendants displayed at top of graph | |||
|
83 | ||||
|
84 | $ hg -q up 3 | |||
|
85 | $ echo b > foo | |||
|
86 | $ hg commit -m 'commit 5 (new dag branch)' | |||
|
87 | created new head | |||
|
88 | $ hg -q up 2 | |||
|
89 | $ hg show stack | |||
|
90 | \ / (multiple children) | |||
|
91 | | | |||
|
92 | o d1a69 commit 3 | |||
|
93 | @ 128c8 commit 2 | |||
|
94 | / (stack base) | |||
|
95 | o 181cc commit 1 | |||
|
96 | ||||
|
97 | $ cd .. | |||
|
98 | ||||
|
99 | Base is stopped at merges | |||
|
100 | ||||
|
101 | $ hg init merge-base | |||
|
102 | $ cd merge-base | |||
|
103 | $ echo 0 > foo | |||
|
104 | $ hg -q commit -A -m initial | |||
|
105 | $ echo h1 > foo | |||
|
106 | $ hg commit -m 'head 1' | |||
|
107 | $ hg -q up 0 | |||
|
108 | $ echo h2 > foo | |||
|
109 | $ hg -q commit -m 'head 2' | |||
|
110 | $ hg phase --public -r 0:tip | |||
|
111 | $ hg -q up 1 | |||
|
112 | $ hg merge -t :local 2 | |||
|
113 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved | |||
|
114 | (branch merge, don't forget to commit) | |||
|
115 | $ hg commit -m 'merge heads' | |||
|
116 | ||||
|
117 | TODO doesn't yet handle case where wdir is a draft merge | |||
|
118 | ||||
|
119 | $ hg show stack | |||
|
120 | @ 8ee90 merge heads | |||
|
121 | / (stack base) | |||
|
122 | o 59478 head 1 | |||
|
123 | ||||
|
124 | $ echo d1 > foo | |||
|
125 | $ hg commit -m 'draft 1' | |||
|
126 | $ echo d2 > foo | |||
|
127 | $ hg commit -m 'draft 2' | |||
|
128 | ||||
|
129 | $ hg show stack | |||
|
130 | @ 430d5 draft 2 | |||
|
131 | o 787b1 draft 1 | |||
|
132 | / (stack base) | |||
|
133 | o 8ee90 merge heads | |||
|
134 | ||||
|
135 | $ cd .. | |||
|
136 | ||||
|
137 | Now move on to stacks when there are more commits after the base branchpoint | |||
|
138 | ||||
|
139 | $ hg init public-rebase | |||
|
140 | $ cd public-rebase | |||
|
141 | $ echo 0 > foo | |||
|
142 | $ hg -q commit -A -m 'base' | |||
|
143 | $ hg phase --public -r . | |||
|
144 | $ echo d1 > foo | |||
|
145 | $ hg commit -m 'draft 1' | |||
|
146 | $ echo d2 > foo | |||
|
147 | $ hg commit -m 'draft 2' | |||
|
148 | $ hg -q up 0 | |||
|
149 | $ echo 1 > foo | |||
|
150 | $ hg commit -m 'new 1' | |||
|
151 | created new head | |||
|
152 | $ echo 2 > foo | |||
|
153 | $ hg commit -m 'new 2' | |||
|
154 | $ hg -q up 2 | |||
|
155 | ||||
|
156 | Newer draft heads don't impact output | |||
|
157 | ||||
|
158 | $ hg show stack | |||
|
159 | @ eaffc draft 2 | |||
|
160 | o 2b218 draft 1 | |||
|
161 | / (stack base) | |||
|
162 | o b66bb base | |||
|
163 | ||||
|
164 | Newer public heads are rendered | |||
|
165 | ||||
|
166 | $ hg phase --public -r '::tip' | |||
|
167 | ||||
|
168 | $ hg show stack | |||
|
169 | o baa4b new 2 | |||
|
170 | / (2 commits ahead) | |||
|
171 | : | |||
|
172 | : (stack head) | |||
|
173 | : @ eaffc draft 2 | |||
|
174 | : o 2b218 draft 1 | |||
|
175 | :/ (stack base) | |||
|
176 | o b66bb base | |||
|
177 | ||||
|
178 | If rebase is available, we show a hint how to rebase to that head | |||
|
179 | ||||
|
180 | $ hg --config extensions.rebase= show stack | |||
|
181 | o baa4b new 2 | |||
|
182 | / (2 commits ahead; hg rebase --source 2b218 --dest baa4b) | |||
|
183 | : | |||
|
184 | : (stack head) | |||
|
185 | : @ eaffc draft 2 | |||
|
186 | : o 2b218 draft 1 | |||
|
187 | :/ (stack base) | |||
|
188 | o b66bb base | |||
|
189 | ||||
|
190 | Similar tests but for multiple heads | |||
|
191 | ||||
|
192 | $ hg -q up 0 | |||
|
193 | $ echo h2 > foo | |||
|
194 | $ hg -q commit -m 'new head 2' | |||
|
195 | $ hg phase --public -r . | |||
|
196 | $ hg -q up 2 | |||
|
197 | ||||
|
198 | $ hg show stack | |||
|
199 | o baa4b new 2 | |||
|
200 | / (2 commits ahead) | |||
|
201 | : o 9a848 new head 2 | |||
|
202 | :/ (1 commits ahead) | |||
|
203 | : | |||
|
204 | : (stack head) | |||
|
205 | : @ eaffc draft 2 | |||
|
206 | : o 2b218 draft 1 | |||
|
207 | :/ (stack base) | |||
|
208 | o b66bb base | |||
|
209 | ||||
|
210 | $ hg --config extensions.rebase= show stack | |||
|
211 | o baa4b new 2 | |||
|
212 | / (2 commits ahead; hg rebase --source 2b218 --dest baa4b) | |||
|
213 | : o 9a848 new head 2 | |||
|
214 | :/ (1 commits ahead; hg rebase --source 2b218 --dest 9a848) | |||
|
215 | : | |||
|
216 | : (stack head) | |||
|
217 | : @ eaffc draft 2 | |||
|
218 | : o 2b218 draft 1 | |||
|
219 | :/ (stack base) | |||
|
220 | o b66bb base |
@@ -32,9 +32,11 b' from mercurial.node import nullrev' | |||||
32 | from mercurial import ( |
|
32 | from mercurial import ( | |
33 | cmdutil, |
|
33 | cmdutil, | |
34 | commands, |
|
34 | commands, | |
|
35 | destutil, | |||
35 | error, |
|
36 | error, | |
36 | formatter, |
|
37 | formatter, | |
37 | graphmod, |
|
38 | graphmod, | |
|
39 | phases, | |||
38 | pycompat, |
|
40 | pycompat, | |
39 | registrar, |
|
41 | registrar, | |
40 | revset, |
|
42 | revset, | |
@@ -171,6 +173,166 b' def showbookmarks(ui, repo, fm):' | |||||
171 | fm.data(active=bm == active, |
|
173 | fm.data(active=bm == active, | |
172 | longestbookmarklen=longestname) |
|
174 | longestbookmarklen=longestname) | |
173 |
|
175 | |||
|
176 | @showview('stack', csettopic='stack') | |||
|
177 | def showstack(ui, repo, displayer): | |||
|
178 | """current line of work""" | |||
|
179 | wdirctx = repo['.'] | |||
|
180 | if wdirctx.rev() == nullrev: | |||
|
181 | raise error.Abort(_('stack view only available when there is a ' | |||
|
182 | 'working directory')) | |||
|
183 | ||||
|
184 | if wdirctx.phase() == phases.public: | |||
|
185 | ui.write(_('(empty stack; working directory is a published ' | |||
|
186 | 'changeset)\n')) | |||
|
187 | return | |||
|
188 | ||||
|
189 | # TODO extract "find stack" into a function to facilitate | |||
|
190 | # customization and reuse. | |||
|
191 | ||||
|
192 | baserev = destutil.stackbase(ui, repo) | |||
|
193 | basectx = None | |||
|
194 | ||||
|
195 | if baserev is None: | |||
|
196 | baserev = wdirctx.rev() | |||
|
197 | stackrevs = {wdirctx.rev()} | |||
|
198 | else: | |||
|
199 | stackrevs = set(repo.revs('%d::.', baserev)) | |||
|
200 | ||||
|
201 | ctx = repo[baserev] | |||
|
202 | if ctx.p1().rev() != nullrev: | |||
|
203 | basectx = ctx.p1() | |||
|
204 | ||||
|
205 | # And relevant descendants. | |||
|
206 | branchpointattip = False | |||
|
207 | cl = repo.changelog | |||
|
208 | ||||
|
209 | for rev in cl.descendants([wdirctx.rev()]): | |||
|
210 | ctx = repo[rev] | |||
|
211 | ||||
|
212 | # Will only happen if . is public. | |||
|
213 | if ctx.phase() == phases.public: | |||
|
214 | break | |||
|
215 | ||||
|
216 | stackrevs.add(ctx.rev()) | |||
|
217 | ||||
|
218 | if len(ctx.children()) > 1: | |||
|
219 | branchpointattip = True | |||
|
220 | break | |||
|
221 | ||||
|
222 | stackrevs = list(reversed(sorted(stackrevs))) | |||
|
223 | ||||
|
224 | # Find likely target heads for the current stack. These are likely | |||
|
225 | # merge or rebase targets. | |||
|
226 | if basectx: | |||
|
227 | # TODO make this customizable? | |||
|
228 | newheads = set(repo.revs('heads(%d::) - %ld - not public()', | |||
|
229 | basectx.rev(), stackrevs)) | |||
|
230 | else: | |||
|
231 | newheads = set() | |||
|
232 | ||||
|
233 | try: | |||
|
234 | cmdutil.findcmd('rebase', commands.table) | |||
|
235 | haverebase = True | |||
|
236 | except error.UnknownCommand: | |||
|
237 | haverebase = False | |||
|
238 | ||||
|
239 | # TODO use templating. | |||
|
240 | # TODO consider using graphmod. But it may not be necessary given | |||
|
241 | # our simplicity and the customizations required. | |||
|
242 | # TODO use proper graph symbols from graphmod | |||
|
243 | ||||
|
244 | shortesttmpl = formatter.maketemplater(ui, '{shortest(node, 5)}') | |||
|
245 | def shortest(ctx): | |||
|
246 | return shortesttmpl.render({'ctx': ctx, 'node': ctx.hex()}) | |||
|
247 | ||||
|
248 | # We write out new heads to aid in DAG awareness and to help with decision | |||
|
249 | # making on how the stack should be reconciled with commits made since the | |||
|
250 | # branch point. | |||
|
251 | if newheads: | |||
|
252 | # Calculate distance from base so we can render the count and so we can | |||
|
253 | # sort display order by commit distance. | |||
|
254 | revdistance = {} | |||
|
255 | for head in newheads: | |||
|
256 | # There is some redundancy in DAG traversal here and therefore | |||
|
257 | # room to optimize. | |||
|
258 | ancestors = cl.ancestors([head], stoprev=basectx.rev()) | |||
|
259 | revdistance[head] = len(list(ancestors)) | |||
|
260 | ||||
|
261 | sourcectx = repo[stackrevs[-1]] | |||
|
262 | ||||
|
263 | sortedheads = sorted(newheads, key=lambda x: revdistance[x], | |||
|
264 | reverse=True) | |||
|
265 | ||||
|
266 | for i, rev in enumerate(sortedheads): | |||
|
267 | ctx = repo[rev] | |||
|
268 | ||||
|
269 | if i: | |||
|
270 | ui.write(': ') | |||
|
271 | else: | |||
|
272 | ui.write(' ') | |||
|
273 | ||||
|
274 | ui.write(('o ')) | |||
|
275 | displayer.show(ctx) | |||
|
276 | displayer.flush(ctx) | |||
|
277 | ui.write('\n') | |||
|
278 | ||||
|
279 | if i: | |||
|
280 | ui.write(':/') | |||
|
281 | else: | |||
|
282 | ui.write(' /') | |||
|
283 | ||||
|
284 | ui.write(' (') | |||
|
285 | ui.write(_('%d commits ahead') % revdistance[rev], | |||
|
286 | label='stack.commitdistance') | |||
|
287 | ||||
|
288 | if haverebase: | |||
|
289 | # TODO may be able to omit --source in some scenarios | |||
|
290 | ui.write('; ') | |||
|
291 | ui.write(('hg rebase --source %s --dest %s' % ( | |||
|
292 | shortest(sourcectx), shortest(ctx))), | |||
|
293 | label='stack.rebasehint') | |||
|
294 | ||||
|
295 | ui.write(')\n') | |||
|
296 | ||||
|
297 | ui.write(':\n: ') | |||
|
298 | ui.write(_('(stack head)\n'), label='stack.label') | |||
|
299 | ||||
|
300 | if branchpointattip: | |||
|
301 | ui.write(' \\ / ') | |||
|
302 | ui.write(_('(multiple children)\n'), label='stack.label') | |||
|
303 | ui.write(' |\n') | |||
|
304 | ||||
|
305 | for rev in stackrevs: | |||
|
306 | ctx = repo[rev] | |||
|
307 | symbol = '@' if rev == wdirctx.rev() else 'o' | |||
|
308 | ||||
|
309 | if newheads: | |||
|
310 | ui.write(': ') | |||
|
311 | else: | |||
|
312 | ui.write(' ') | |||
|
313 | ||||
|
314 | ui.write(symbol, ' ') | |||
|
315 | displayer.show(ctx) | |||
|
316 | displayer.flush(ctx) | |||
|
317 | ui.write('\n') | |||
|
318 | ||||
|
319 | # TODO display histedit hint? | |||
|
320 | ||||
|
321 | if basectx: | |||
|
322 | # Vertically and horizontally separate stack base from parent | |||
|
323 | # to reinforce stack boundary. | |||
|
324 | if newheads: | |||
|
325 | ui.write(':/ ') | |||
|
326 | else: | |||
|
327 | ui.write(' / ') | |||
|
328 | ||||
|
329 | ui.write(_('(stack base)'), '\n', label='stack.label') | |||
|
330 | ui.write(('o ')) | |||
|
331 | ||||
|
332 | displayer.show(basectx) | |||
|
333 | displayer.flush(basectx) | |||
|
334 | ui.write('\n') | |||
|
335 | ||||
174 | @revsetpredicate('_underway([commitage[, headage]])') |
|
336 | @revsetpredicate('_underway([commitage[, headage]])') | |
175 | def underwayrevset(repo, subset, x): |
|
337 | def underwayrevset(repo, subset, x): | |
176 | args = revset.getargsdict(x, 'underway', 'commitage headage') |
|
338 | args = revset.getargsdict(x, 'underway', 'commitage headage') |
@@ -354,6 +354,12 b' def desthistedit(ui, repo):' | |||||
354 |
|
354 | |||
355 | return None |
|
355 | return None | |
356 |
|
356 | |||
|
357 | def stackbase(ui, repo): | |||
|
358 | # The histedit default base stops at public changesets, branchpoints, | |||
|
359 | # and merges, which is exactly what we want for a stack. | |||
|
360 | revs = scmutil.revrange(repo, [histeditdefaultrevset]) | |||
|
361 | return revs.last() if revs else None | |||
|
362 | ||||
357 | def _statusotherbook(ui, repo): |
|
363 | def _statusotherbook(ui, repo): | |
358 | bmheads = bookmarks.headsforactive(repo) |
|
364 | bmheads = bookmarks.headsforactive(repo) | |
359 | curhead = repo[repo._activebookmark].node() |
|
365 | curhead = repo[repo._activebookmark].node() |
@@ -6,6 +6,7 b'' | |||||
6 | showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n' |
|
6 | showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n' | |
7 |
|
7 | |||
8 | showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}' |
|
8 | showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}' | |
|
9 | showstack = '{showwork}' | |||
9 |
|
10 | |||
10 | cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, 5))}' |
|
11 | cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, 5))}' | |
11 |
|
12 |
@@ -11,6 +11,7 b' No arguments shows available views' | |||||
11 | available views: |
|
11 | available views: | |
12 |
|
12 | |||
13 | bookmarks -- bookmarks and their associated changeset |
|
13 | bookmarks -- bookmarks and their associated changeset | |
|
14 | stack -- current line of work | |||
14 | work -- changesets that aren't finished |
|
15 | work -- changesets that aren't finished | |
15 |
|
16 | |||
16 | abort: no view requested |
|
17 | abort: no view requested | |
@@ -40,6 +41,8 b' No arguments shows available views' | |||||
40 |
|
41 | |||
41 | bookmarks bookmarks and their associated changeset |
|
42 | bookmarks bookmarks and their associated changeset | |
42 |
|
43 | |||
|
44 | stack current line of work | |||
|
45 | ||||
43 | work changesets that aren't finished |
|
46 | work changesets that aren't finished | |
44 |
|
47 | |||
45 | (use 'hg help -e show' to show help for the show extension) |
|
48 | (use 'hg help -e show' to show help for the show extension) |
General Comments 0
You need to be logged in to leave comments.
Login now