##// END OF EJS Templates
show: implement "stack" view...
Gregory Szorc -
r33194:c5a07a3a default
parent child Browse files
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