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 |
@@ -1,270 +1,432 b'' | |||
|
1 | 1 | # show.py - Extension implementing `hg show` |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | """unified command to show various repository information (EXPERIMENTAL) |
|
9 | 9 | |
|
10 | 10 | This extension provides the :hg:`show` command, which provides a central |
|
11 | 11 | command for displaying commonly-accessed repository data and views of that |
|
12 | 12 | data. |
|
13 | 13 | |
|
14 | 14 | The following config options can influence operation. |
|
15 | 15 | |
|
16 | 16 | ``commands`` |
|
17 | 17 | ------------ |
|
18 | 18 | |
|
19 | 19 | ``show.aliasprefix`` |
|
20 | 20 | List of strings that will register aliases for views. e.g. ``s`` will |
|
21 | 21 | effectively set config options ``alias.s<view> = show <view>`` for all |
|
22 | 22 | views. i.e. `hg swork` would execute `hg show work`. |
|
23 | 23 | |
|
24 | 24 | Aliases that would conflict with existing registrations will not be |
|
25 | 25 | performed. |
|
26 | 26 | """ |
|
27 | 27 | |
|
28 | 28 | from __future__ import absolute_import |
|
29 | 29 | |
|
30 | 30 | from mercurial.i18n import _ |
|
31 | 31 | from mercurial.node import nullrev |
|
32 | 32 | from mercurial import ( |
|
33 | 33 | cmdutil, |
|
34 | 34 | commands, |
|
35 | destutil, | |
|
35 | 36 | error, |
|
36 | 37 | formatter, |
|
37 | 38 | graphmod, |
|
39 | phases, | |
|
38 | 40 | pycompat, |
|
39 | 41 | registrar, |
|
40 | 42 | revset, |
|
41 | 43 | revsetlang, |
|
42 | 44 | ) |
|
43 | 45 | |
|
44 | 46 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
|
45 | 47 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
|
46 | 48 | # be specifying the version(s) of Mercurial they are tested with, or |
|
47 | 49 | # leave the attribute unspecified. |
|
48 | 50 | testedwith = 'ships-with-hg-core' |
|
49 | 51 | |
|
50 | 52 | cmdtable = {} |
|
51 | 53 | command = registrar.command(cmdtable) |
|
52 | 54 | revsetpredicate = registrar.revsetpredicate() |
|
53 | 55 | |
|
54 | 56 | class showcmdfunc(registrar._funcregistrarbase): |
|
55 | 57 | """Register a function to be invoked for an `hg show <thing>`.""" |
|
56 | 58 | |
|
57 | 59 | # Used by _formatdoc(). |
|
58 | 60 | _docformat = '%s -- %s' |
|
59 | 61 | |
|
60 | 62 | def _extrasetup(self, name, func, fmtopic=None, csettopic=None): |
|
61 | 63 | """Called with decorator arguments to register a show view. |
|
62 | 64 | |
|
63 | 65 | ``name`` is the sub-command name. |
|
64 | 66 | |
|
65 | 67 | ``func`` is the function being decorated. |
|
66 | 68 | |
|
67 | 69 | ``fmtopic`` is the topic in the style that will be rendered for |
|
68 | 70 | this view. |
|
69 | 71 | |
|
70 | 72 | ``csettopic`` is the topic in the style to be used for a changeset |
|
71 | 73 | printer. |
|
72 | 74 | |
|
73 | 75 | If ``fmtopic`` is specified, the view function will receive a |
|
74 | 76 | formatter instance. If ``csettopic`` is specified, the view |
|
75 | 77 | function will receive a changeset printer. |
|
76 | 78 | """ |
|
77 | 79 | func._fmtopic = fmtopic |
|
78 | 80 | func._csettopic = csettopic |
|
79 | 81 | |
|
80 | 82 | showview = showcmdfunc() |
|
81 | 83 | |
|
82 | 84 | @command('show', [ |
|
83 | 85 | # TODO: Switch this template flag to use cmdutil.formatteropts if |
|
84 | 86 | # 'hg show' becomes stable before --template/-T is stable. For now, |
|
85 | 87 | # we are putting it here without the '(EXPERIMENTAL)' flag because it |
|
86 | 88 | # is an important part of the 'hg show' user experience and the entire |
|
87 | 89 | # 'hg show' experience is experimental. |
|
88 | 90 | ('T', 'template', '', ('display with template'), _('TEMPLATE')), |
|
89 | 91 | ], _('VIEW')) |
|
90 | 92 | def show(ui, repo, view=None, template=None): |
|
91 | 93 | """show various repository information |
|
92 | 94 | |
|
93 | 95 | A requested view of repository data is displayed. |
|
94 | 96 | |
|
95 | 97 | If no view is requested, the list of available views is shown and the |
|
96 | 98 | command aborts. |
|
97 | 99 | |
|
98 | 100 | .. note:: |
|
99 | 101 | |
|
100 | 102 | There are no backwards compatibility guarantees for the output of this |
|
101 | 103 | command. Output may change in any future Mercurial release. |
|
102 | 104 | |
|
103 | 105 | Consumers wanting stable command output should specify a template via |
|
104 | 106 | ``-T/--template``. |
|
105 | 107 | |
|
106 | 108 | List of available views: |
|
107 | 109 | """ |
|
108 | 110 | if ui.plain() and not template: |
|
109 | 111 | hint = _('invoke with -T/--template to control output format') |
|
110 | 112 | raise error.Abort(_('must specify a template in plain mode'), hint=hint) |
|
111 | 113 | |
|
112 | 114 | views = showview._table |
|
113 | 115 | |
|
114 | 116 | if not view: |
|
115 | 117 | ui.pager('show') |
|
116 | 118 | # TODO consider using formatter here so available views can be |
|
117 | 119 | # rendered to custom format. |
|
118 | 120 | ui.write(_('available views:\n')) |
|
119 | 121 | ui.write('\n') |
|
120 | 122 | |
|
121 | 123 | for name, func in sorted(views.items()): |
|
122 | 124 | ui.write(('%s\n') % func.__doc__) |
|
123 | 125 | |
|
124 | 126 | ui.write('\n') |
|
125 | 127 | raise error.Abort(_('no view requested'), |
|
126 | 128 | hint=_('use "hg show VIEW" to choose a view')) |
|
127 | 129 | |
|
128 | 130 | # TODO use same logic as dispatch to perform prefix matching. |
|
129 | 131 | if view not in views: |
|
130 | 132 | raise error.Abort(_('unknown view: %s') % view, |
|
131 | 133 | hint=_('run "hg show" to see available views')) |
|
132 | 134 | |
|
133 | 135 | template = template or 'show' |
|
134 | 136 | |
|
135 | 137 | fn = views[view] |
|
136 | 138 | ui.pager('show') |
|
137 | 139 | |
|
138 | 140 | if fn._fmtopic: |
|
139 | 141 | fmtopic = 'show%s' % fn._fmtopic |
|
140 | 142 | with ui.formatter(fmtopic, {'template': template}) as fm: |
|
141 | 143 | return fn(ui, repo, fm) |
|
142 | 144 | elif fn._csettopic: |
|
143 | 145 | ref = 'show%s' % fn._csettopic |
|
144 | 146 | spec = formatter.lookuptemplate(ui, ref, template) |
|
145 | 147 | displayer = cmdutil.changeset_templater(ui, repo, spec, buffered=True) |
|
146 | 148 | return fn(ui, repo, displayer) |
|
147 | 149 | else: |
|
148 | 150 | return fn(ui, repo) |
|
149 | 151 | |
|
150 | 152 | @showview('bookmarks', fmtopic='bookmarks') |
|
151 | 153 | def showbookmarks(ui, repo, fm): |
|
152 | 154 | """bookmarks and their associated changeset""" |
|
153 | 155 | marks = repo._bookmarks |
|
154 | 156 | if not len(marks): |
|
155 | 157 | # This is a bit hacky. Ideally, templates would have a way to |
|
156 | 158 | # specify an empty output, but we shouldn't corrupt JSON while |
|
157 | 159 | # waiting for this functionality. |
|
158 | 160 | if not isinstance(fm, formatter.jsonformatter): |
|
159 | 161 | ui.write(_('(no bookmarks set)\n')) |
|
160 | 162 | return |
|
161 | 163 | |
|
162 | 164 | active = repo._activebookmark |
|
163 | 165 | longestname = max(len(b) for b in marks) |
|
164 | 166 | # TODO consider exposing longest shortest(node). |
|
165 | 167 | |
|
166 | 168 | for bm, node in sorted(marks.items()): |
|
167 | 169 | fm.startitem() |
|
168 | 170 | fm.context(ctx=repo[node]) |
|
169 | 171 | fm.write('bookmark', '%s', bm) |
|
170 | 172 | fm.write('node', fm.hexfunc(node), fm.hexfunc(node)) |
|
171 | 173 | fm.data(active=bm == active, |
|
172 | 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 | 336 | @revsetpredicate('_underway([commitage[, headage]])') |
|
175 | 337 | def underwayrevset(repo, subset, x): |
|
176 | 338 | args = revset.getargsdict(x, 'underway', 'commitage headage') |
|
177 | 339 | if 'commitage' not in args: |
|
178 | 340 | args['commitage'] = None |
|
179 | 341 | if 'headage' not in args: |
|
180 | 342 | args['headage'] = None |
|
181 | 343 | |
|
182 | 344 | # We assume callers of this revset add a topographical sort on the |
|
183 | 345 | # result. This means there is no benefit to making the revset lazy |
|
184 | 346 | # since the topographical sort needs to consume all revs. |
|
185 | 347 | # |
|
186 | 348 | # With this in mind, we build up the set manually instead of constructing |
|
187 | 349 | # a complex revset. This enables faster execution. |
|
188 | 350 | |
|
189 | 351 | # Mutable changesets (non-public) are the most important changesets |
|
190 | 352 | # to return. ``not public()`` will also pull in obsolete changesets if |
|
191 | 353 | # there is a non-obsolete changeset with obsolete ancestors. This is |
|
192 | 354 | # why we exclude obsolete changesets from this query. |
|
193 | 355 | rs = 'not public() and not obsolete()' |
|
194 | 356 | rsargs = [] |
|
195 | 357 | if args['commitage']: |
|
196 | 358 | rs += ' and date(%s)' |
|
197 | 359 | rsargs.append(revsetlang.getstring(args['commitage'], |
|
198 | 360 | _('commitage requires a string'))) |
|
199 | 361 | |
|
200 | 362 | mutable = repo.revs(rs, *rsargs) |
|
201 | 363 | relevant = revset.baseset(mutable) |
|
202 | 364 | |
|
203 | 365 | # Add parents of mutable changesets to provide context. |
|
204 | 366 | relevant += repo.revs('parents(%ld)', mutable) |
|
205 | 367 | |
|
206 | 368 | # We also pull in (public) heads if they a) aren't closing a branch |
|
207 | 369 | # b) are recent. |
|
208 | 370 | rs = 'head() and not closed()' |
|
209 | 371 | rsargs = [] |
|
210 | 372 | if args['headage']: |
|
211 | 373 | rs += ' and date(%s)' |
|
212 | 374 | rsargs.append(revsetlang.getstring(args['headage'], |
|
213 | 375 | _('headage requires a string'))) |
|
214 | 376 | |
|
215 | 377 | relevant += repo.revs(rs, *rsargs) |
|
216 | 378 | |
|
217 | 379 | # Add working directory parent. |
|
218 | 380 | wdirrev = repo['.'].rev() |
|
219 | 381 | if wdirrev != nullrev: |
|
220 | 382 | relevant += revset.baseset({wdirrev}) |
|
221 | 383 | |
|
222 | 384 | return subset & relevant |
|
223 | 385 | |
|
224 | 386 | @showview('work', csettopic='work') |
|
225 | 387 | def showwork(ui, repo, displayer): |
|
226 | 388 | """changesets that aren't finished""" |
|
227 | 389 | # TODO support date-based limiting when calling revset. |
|
228 | 390 | revs = repo.revs('sort(_underway(), topo)') |
|
229 | 391 | |
|
230 | 392 | revdag = graphmod.dagwalker(repo, revs) |
|
231 | 393 | |
|
232 | 394 | ui.setconfig('experimental', 'graphshorten', True) |
|
233 | 395 | cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) |
|
234 | 396 | |
|
235 | 397 | def extsetup(ui): |
|
236 | 398 | # Alias `hg <prefix><view>` to `hg show <view>`. |
|
237 | 399 | for prefix in ui.configlist('commands', 'show.aliasprefix'): |
|
238 | 400 | for view in showview._table: |
|
239 | 401 | name = '%s%s' % (prefix, view) |
|
240 | 402 | |
|
241 | 403 | choice, allcommands = cmdutil.findpossible(name, commands.table, |
|
242 | 404 | strict=True) |
|
243 | 405 | |
|
244 | 406 | # This alias is already a command name. Don't set it. |
|
245 | 407 | if name in choice: |
|
246 | 408 | continue |
|
247 | 409 | |
|
248 | 410 | # Same for aliases. |
|
249 | 411 | if ui.config('alias', name): |
|
250 | 412 | continue |
|
251 | 413 | |
|
252 | 414 | ui.setconfig('alias', name, 'show %s' % view, source='show') |
|
253 | 415 | |
|
254 | 416 | # Adjust the docstring of the show command so it shows all registered views. |
|
255 | 417 | # This is a bit hacky because it runs at the end of module load. When moved |
|
256 | 418 | # into core or when another extension wants to provide a view, we'll need |
|
257 | 419 | # to do this more robustly. |
|
258 | 420 | # TODO make this more robust. |
|
259 | 421 | def _updatedocstring(): |
|
260 | 422 | longest = max(map(len, showview._table.keys())) |
|
261 | 423 | entries = [] |
|
262 | 424 | for key in sorted(showview._table.keys()): |
|
263 | 425 | entries.append(pycompat.sysstr(' %s %s' % ( |
|
264 | 426 | key.ljust(longest), showview._table[key]._origdoc))) |
|
265 | 427 | |
|
266 | 428 | cmdtable['show'][0].__doc__ = pycompat.sysstr('%s\n\n%s\n ') % ( |
|
267 | 429 | cmdtable['show'][0].__doc__.rstrip(), |
|
268 | 430 | pycompat.sysstr('\n\n').join(entries)) |
|
269 | 431 | |
|
270 | 432 | _updatedocstring() |
@@ -1,408 +1,414 b'' | |||
|
1 | 1 | # destutil.py - Mercurial utility function for command destination |
|
2 | 2 | # |
|
3 | 3 | # Copyright Matt Mackall <mpm@selenic.com> and other |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | 10 | from .i18n import _ |
|
11 | 11 | from . import ( |
|
12 | 12 | bookmarks, |
|
13 | 13 | error, |
|
14 | 14 | obsutil, |
|
15 | 15 | scmutil, |
|
16 | 16 | ) |
|
17 | 17 | |
|
18 | 18 | def _destupdateobs(repo, clean): |
|
19 | 19 | """decide of an update destination from obsolescence markers""" |
|
20 | 20 | node = None |
|
21 | 21 | wc = repo[None] |
|
22 | 22 | p1 = wc.p1() |
|
23 | 23 | movemark = None |
|
24 | 24 | |
|
25 | 25 | if p1.obsolete() and not p1.children(): |
|
26 | 26 | # allow updating to successors |
|
27 | 27 | successors = obsutil.successorssets(repo, p1.node()) |
|
28 | 28 | |
|
29 | 29 | # behavior of certain cases is as follows, |
|
30 | 30 | # |
|
31 | 31 | # divergent changesets: update to highest rev, similar to what |
|
32 | 32 | # is currently done when there are more than one head |
|
33 | 33 | # (i.e. 'tip') |
|
34 | 34 | # |
|
35 | 35 | # replaced changesets: same as divergent except we know there |
|
36 | 36 | # is no conflict |
|
37 | 37 | # |
|
38 | 38 | # pruned changeset: no update is done; though, we could |
|
39 | 39 | # consider updating to the first non-obsolete parent, |
|
40 | 40 | # similar to what is current done for 'hg prune' |
|
41 | 41 | |
|
42 | 42 | if successors: |
|
43 | 43 | # flatten the list here handles both divergent (len > 1) |
|
44 | 44 | # and the usual case (len = 1) |
|
45 | 45 | successors = [n for sub in successors for n in sub] |
|
46 | 46 | |
|
47 | 47 | # get the max revision for the given successors set, |
|
48 | 48 | # i.e. the 'tip' of a set |
|
49 | 49 | node = repo.revs('max(%ln)', successors).first() |
|
50 | 50 | if bookmarks.isactivewdirparent(repo): |
|
51 | 51 | movemark = repo['.'].node() |
|
52 | 52 | return node, movemark, None |
|
53 | 53 | |
|
54 | 54 | def _destupdatebook(repo, clean): |
|
55 | 55 | """decide on an update destination from active bookmark""" |
|
56 | 56 | # we also move the active bookmark, if any |
|
57 | 57 | activemark = None |
|
58 | 58 | node, movemark = bookmarks.calculateupdate(repo.ui, repo, None) |
|
59 | 59 | if node is not None: |
|
60 | 60 | activemark = node |
|
61 | 61 | return node, movemark, activemark |
|
62 | 62 | |
|
63 | 63 | def _destupdatebranch(repo, clean): |
|
64 | 64 | """decide on an update destination from current branch |
|
65 | 65 | |
|
66 | 66 | This ignores closed branch heads. |
|
67 | 67 | """ |
|
68 | 68 | wc = repo[None] |
|
69 | 69 | movemark = node = None |
|
70 | 70 | currentbranch = wc.branch() |
|
71 | 71 | |
|
72 | 72 | if clean: |
|
73 | 73 | currentbranch = repo['.'].branch() |
|
74 | 74 | |
|
75 | 75 | if currentbranch in repo.branchmap(): |
|
76 | 76 | heads = repo.branchheads(currentbranch) |
|
77 | 77 | if heads: |
|
78 | 78 | node = repo.revs('max(.::(%ln))', heads).first() |
|
79 | 79 | if bookmarks.isactivewdirparent(repo): |
|
80 | 80 | movemark = repo['.'].node() |
|
81 | 81 | elif currentbranch == 'default' and not wc.p1(): |
|
82 | 82 | # "null" parent belongs to "default" branch, but it doesn't exist, so |
|
83 | 83 | # update to the tipmost non-closed branch head |
|
84 | 84 | node = repo.revs('max(head() and not closed())').first() |
|
85 | 85 | else: |
|
86 | 86 | node = repo['.'].node() |
|
87 | 87 | return node, movemark, None |
|
88 | 88 | |
|
89 | 89 | def _destupdatebranchfallback(repo, clean): |
|
90 | 90 | """decide on an update destination from closed heads in current branch""" |
|
91 | 91 | wc = repo[None] |
|
92 | 92 | currentbranch = wc.branch() |
|
93 | 93 | movemark = None |
|
94 | 94 | if currentbranch in repo.branchmap(): |
|
95 | 95 | # here, all descendant branch heads are closed |
|
96 | 96 | heads = repo.branchheads(currentbranch, closed=True) |
|
97 | 97 | assert heads, "any branch has at least one head" |
|
98 | 98 | node = repo.revs('max(.::(%ln))', heads).first() |
|
99 | 99 | assert node is not None, ("any revision has at least " |
|
100 | 100 | "one descendant branch head") |
|
101 | 101 | if bookmarks.isactivewdirparent(repo): |
|
102 | 102 | movemark = repo['.'].node() |
|
103 | 103 | else: |
|
104 | 104 | # here, no "default" branch, and all branches are closed |
|
105 | 105 | node = repo.lookup('tip') |
|
106 | 106 | assert node is not None, "'tip' exists even in empty repository" |
|
107 | 107 | return node, movemark, None |
|
108 | 108 | |
|
109 | 109 | # order in which each step should be evaluated |
|
110 | 110 | # steps are run until one finds a destination |
|
111 | 111 | destupdatesteps = ['evolution', 'bookmark', 'branch', 'branchfallback'] |
|
112 | 112 | # mapping to ease extension overriding steps. |
|
113 | 113 | destupdatestepmap = {'evolution': _destupdateobs, |
|
114 | 114 | 'bookmark': _destupdatebook, |
|
115 | 115 | 'branch': _destupdatebranch, |
|
116 | 116 | 'branchfallback': _destupdatebranchfallback, |
|
117 | 117 | } |
|
118 | 118 | |
|
119 | 119 | def destupdate(repo, clean=False): |
|
120 | 120 | """destination for bare update operation |
|
121 | 121 | |
|
122 | 122 | return (rev, movemark, activemark) |
|
123 | 123 | |
|
124 | 124 | - rev: the revision to update to, |
|
125 | 125 | - movemark: node to move the active bookmark from |
|
126 | 126 | (cf bookmark.calculate update), |
|
127 | 127 | - activemark: a bookmark to activate at the end of the update. |
|
128 | 128 | """ |
|
129 | 129 | node = movemark = activemark = None |
|
130 | 130 | |
|
131 | 131 | for step in destupdatesteps: |
|
132 | 132 | node, movemark, activemark = destupdatestepmap[step](repo, clean) |
|
133 | 133 | if node is not None: |
|
134 | 134 | break |
|
135 | 135 | rev = repo[node].rev() |
|
136 | 136 | |
|
137 | 137 | return rev, movemark, activemark |
|
138 | 138 | |
|
139 | 139 | msgdestmerge = { |
|
140 | 140 | # too many matching divergent bookmark |
|
141 | 141 | 'toomanybookmarks': |
|
142 | 142 | {'merge': |
|
143 | 143 | (_("multiple matching bookmarks to merge -" |
|
144 | 144 | " please merge with an explicit rev or bookmark"), |
|
145 | 145 | _("run 'hg heads' to see all heads")), |
|
146 | 146 | 'rebase': |
|
147 | 147 | (_("multiple matching bookmarks to rebase -" |
|
148 | 148 | " please rebase to an explicit rev or bookmark"), |
|
149 | 149 | _("run 'hg heads' to see all heads")), |
|
150 | 150 | }, |
|
151 | 151 | # no other matching divergent bookmark |
|
152 | 152 | 'nootherbookmarks': |
|
153 | 153 | {'merge': |
|
154 | 154 | (_("no matching bookmark to merge - " |
|
155 | 155 | "please merge with an explicit rev or bookmark"), |
|
156 | 156 | _("run 'hg heads' to see all heads")), |
|
157 | 157 | 'rebase': |
|
158 | 158 | (_("no matching bookmark to rebase - " |
|
159 | 159 | "please rebase to an explicit rev or bookmark"), |
|
160 | 160 | _("run 'hg heads' to see all heads")), |
|
161 | 161 | }, |
|
162 | 162 | # branch have too many unbookmarked heads, no obvious destination |
|
163 | 163 | 'toomanyheads': |
|
164 | 164 | {'merge': |
|
165 | 165 | (_("branch '%s' has %d heads - please merge with an explicit rev"), |
|
166 | 166 | _("run 'hg heads .' to see heads")), |
|
167 | 167 | 'rebase': |
|
168 | 168 | (_("branch '%s' has %d heads - please rebase to an explicit rev"), |
|
169 | 169 | _("run 'hg heads .' to see heads")), |
|
170 | 170 | }, |
|
171 | 171 | # branch have no other unbookmarked heads |
|
172 | 172 | 'bookmarkedheads': |
|
173 | 173 | {'merge': |
|
174 | 174 | (_("heads are bookmarked - please merge with an explicit rev"), |
|
175 | 175 | _("run 'hg heads' to see all heads")), |
|
176 | 176 | 'rebase': |
|
177 | 177 | (_("heads are bookmarked - please rebase to an explicit rev"), |
|
178 | 178 | _("run 'hg heads' to see all heads")), |
|
179 | 179 | }, |
|
180 | 180 | # branch have just a single heads, but there is other branches |
|
181 | 181 | 'nootherbranchheads': |
|
182 | 182 | {'merge': |
|
183 | 183 | (_("branch '%s' has one head - please merge with an explicit rev"), |
|
184 | 184 | _("run 'hg heads' to see all heads")), |
|
185 | 185 | 'rebase': |
|
186 | 186 | (_("branch '%s' has one head - please rebase to an explicit rev"), |
|
187 | 187 | _("run 'hg heads' to see all heads")), |
|
188 | 188 | }, |
|
189 | 189 | # repository have a single head |
|
190 | 190 | 'nootherheads': |
|
191 | 191 | {'merge': |
|
192 | 192 | (_('nothing to merge'), |
|
193 | 193 | None), |
|
194 | 194 | 'rebase': |
|
195 | 195 | (_('nothing to rebase'), |
|
196 | 196 | None), |
|
197 | 197 | }, |
|
198 | 198 | # repository have a single head and we are not on it |
|
199 | 199 | 'nootherheadsbehind': |
|
200 | 200 | {'merge': |
|
201 | 201 | (_('nothing to merge'), |
|
202 | 202 | _("use 'hg update' instead")), |
|
203 | 203 | 'rebase': |
|
204 | 204 | (_('nothing to rebase'), |
|
205 | 205 | _("use 'hg update' instead")), |
|
206 | 206 | }, |
|
207 | 207 | # We are not on a head |
|
208 | 208 | 'notatheads': |
|
209 | 209 | {'merge': |
|
210 | 210 | (_('working directory not at a head revision'), |
|
211 | 211 | _("use 'hg update' or merge with an explicit revision")), |
|
212 | 212 | 'rebase': |
|
213 | 213 | (_('working directory not at a head revision'), |
|
214 | 214 | _("use 'hg update' or rebase to an explicit revision")) |
|
215 | 215 | }, |
|
216 | 216 | 'emptysourceset': |
|
217 | 217 | {'merge': |
|
218 | 218 | (_('source set is empty'), |
|
219 | 219 | None), |
|
220 | 220 | 'rebase': |
|
221 | 221 | (_('source set is empty'), |
|
222 | 222 | None), |
|
223 | 223 | }, |
|
224 | 224 | 'multiplebranchessourceset': |
|
225 | 225 | {'merge': |
|
226 | 226 | (_('source set is rooted in multiple branches'), |
|
227 | 227 | None), |
|
228 | 228 | 'rebase': |
|
229 | 229 | (_('rebaseset is rooted in multiple named branches'), |
|
230 | 230 | _('specify an explicit destination with --dest')), |
|
231 | 231 | }, |
|
232 | 232 | } |
|
233 | 233 | |
|
234 | 234 | def _destmergebook(repo, action='merge', sourceset=None, destspace=None): |
|
235 | 235 | """find merge destination in the active bookmark case""" |
|
236 | 236 | node = None |
|
237 | 237 | bmheads = bookmarks.headsforactive(repo) |
|
238 | 238 | curhead = repo[repo._activebookmark].node() |
|
239 | 239 | if len(bmheads) == 2: |
|
240 | 240 | if curhead == bmheads[0]: |
|
241 | 241 | node = bmheads[1] |
|
242 | 242 | else: |
|
243 | 243 | node = bmheads[0] |
|
244 | 244 | elif len(bmheads) > 2: |
|
245 | 245 | msg, hint = msgdestmerge['toomanybookmarks'][action] |
|
246 | 246 | raise error.ManyMergeDestAbort(msg, hint=hint) |
|
247 | 247 | elif len(bmheads) <= 1: |
|
248 | 248 | msg, hint = msgdestmerge['nootherbookmarks'][action] |
|
249 | 249 | raise error.NoMergeDestAbort(msg, hint=hint) |
|
250 | 250 | assert node is not None |
|
251 | 251 | return node |
|
252 | 252 | |
|
253 | 253 | def _destmergebranch(repo, action='merge', sourceset=None, onheadcheck=True, |
|
254 | 254 | destspace=None): |
|
255 | 255 | """find merge destination based on branch heads""" |
|
256 | 256 | node = None |
|
257 | 257 | |
|
258 | 258 | if sourceset is None: |
|
259 | 259 | sourceset = [repo[repo.dirstate.p1()].rev()] |
|
260 | 260 | branch = repo.dirstate.branch() |
|
261 | 261 | elif not sourceset: |
|
262 | 262 | msg, hint = msgdestmerge['emptysourceset'][action] |
|
263 | 263 | raise error.NoMergeDestAbort(msg, hint=hint) |
|
264 | 264 | else: |
|
265 | 265 | branch = None |
|
266 | 266 | for ctx in repo.set('roots(%ld::%ld)', sourceset, sourceset): |
|
267 | 267 | if branch is not None and ctx.branch() != branch: |
|
268 | 268 | msg, hint = msgdestmerge['multiplebranchessourceset'][action] |
|
269 | 269 | raise error.ManyMergeDestAbort(msg, hint=hint) |
|
270 | 270 | branch = ctx.branch() |
|
271 | 271 | |
|
272 | 272 | bheads = repo.branchheads(branch) |
|
273 | 273 | onhead = repo.revs('%ld and %ln', sourceset, bheads) |
|
274 | 274 | if onheadcheck and not onhead: |
|
275 | 275 | # Case A: working copy if not on a head. (merge only) |
|
276 | 276 | # |
|
277 | 277 | # This is probably a user mistake We bailout pointing at 'hg update' |
|
278 | 278 | if len(repo.heads()) <= 1: |
|
279 | 279 | msg, hint = msgdestmerge['nootherheadsbehind'][action] |
|
280 | 280 | else: |
|
281 | 281 | msg, hint = msgdestmerge['notatheads'][action] |
|
282 | 282 | raise error.Abort(msg, hint=hint) |
|
283 | 283 | # remove heads descendants of source from the set |
|
284 | 284 | bheads = list(repo.revs('%ln - (%ld::)', bheads, sourceset)) |
|
285 | 285 | # filters out bookmarked heads |
|
286 | 286 | nbhs = list(repo.revs('%ld - bookmark()', bheads)) |
|
287 | 287 | |
|
288 | 288 | if destspace is not None: |
|
289 | 289 | # restrict search space |
|
290 | 290 | # used in the 'hg pull --rebase' case, see issue 5214. |
|
291 | 291 | nbhs = list(repo.revs('%ld and %ld', destspace, nbhs)) |
|
292 | 292 | |
|
293 | 293 | if len(nbhs) > 1: |
|
294 | 294 | # Case B: There is more than 1 other anonymous heads |
|
295 | 295 | # |
|
296 | 296 | # This means that there will be more than 1 candidate. This is |
|
297 | 297 | # ambiguous. We abort asking the user to pick as explicit destination |
|
298 | 298 | # instead. |
|
299 | 299 | msg, hint = msgdestmerge['toomanyheads'][action] |
|
300 | 300 | msg %= (branch, len(bheads) + 1) |
|
301 | 301 | raise error.ManyMergeDestAbort(msg, hint=hint) |
|
302 | 302 | elif not nbhs: |
|
303 | 303 | # Case B: There is no other anonymous heads |
|
304 | 304 | # |
|
305 | 305 | # This means that there is no natural candidate to merge with. |
|
306 | 306 | # We abort, with various messages for various cases. |
|
307 | 307 | if bheads: |
|
308 | 308 | msg, hint = msgdestmerge['bookmarkedheads'][action] |
|
309 | 309 | elif len(repo.heads()) > 1: |
|
310 | 310 | msg, hint = msgdestmerge['nootherbranchheads'][action] |
|
311 | 311 | msg %= branch |
|
312 | 312 | elif not onhead: |
|
313 | 313 | # if 'onheadcheck == False' (rebase case), |
|
314 | 314 | # this was not caught in Case A. |
|
315 | 315 | msg, hint = msgdestmerge['nootherheadsbehind'][action] |
|
316 | 316 | else: |
|
317 | 317 | msg, hint = msgdestmerge['nootherheads'][action] |
|
318 | 318 | raise error.NoMergeDestAbort(msg, hint=hint) |
|
319 | 319 | else: |
|
320 | 320 | node = nbhs[0] |
|
321 | 321 | assert node is not None |
|
322 | 322 | return node |
|
323 | 323 | |
|
324 | 324 | def destmerge(repo, action='merge', sourceset=None, onheadcheck=True, |
|
325 | 325 | destspace=None): |
|
326 | 326 | """return the default destination for a merge |
|
327 | 327 | |
|
328 | 328 | (or raise exception about why it can't pick one) |
|
329 | 329 | |
|
330 | 330 | :action: the action being performed, controls emitted error message |
|
331 | 331 | """ |
|
332 | 332 | # destspace is here to work around issues with `hg pull --rebase` see |
|
333 | 333 | # issue5214 for details |
|
334 | 334 | if repo._activebookmark: |
|
335 | 335 | node = _destmergebook(repo, action=action, sourceset=sourceset, |
|
336 | 336 | destspace=destspace) |
|
337 | 337 | else: |
|
338 | 338 | node = _destmergebranch(repo, action=action, sourceset=sourceset, |
|
339 | 339 | onheadcheck=onheadcheck, destspace=destspace) |
|
340 | 340 | return repo[node].rev() |
|
341 | 341 | |
|
342 | 342 | histeditdefaultrevset = 'reverse(only(.) and not public() and not ::merge())' |
|
343 | 343 | |
|
344 | 344 | def desthistedit(ui, repo): |
|
345 | 345 | """Default base revision to edit for `hg histedit`.""" |
|
346 | 346 | default = ui.config('histedit', 'defaultrev', histeditdefaultrevset) |
|
347 | 347 | if default: |
|
348 | 348 | revs = scmutil.revrange(repo, [default]) |
|
349 | 349 | if revs: |
|
350 | 350 | # The revset supplied by the user may not be in ascending order nor |
|
351 | 351 | # take the first revision. So do this manually. |
|
352 | 352 | revs.sort() |
|
353 | 353 | return revs.first() |
|
354 | 354 | |
|
355 | 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 | 363 | def _statusotherbook(ui, repo): |
|
358 | 364 | bmheads = bookmarks.headsforactive(repo) |
|
359 | 365 | curhead = repo[repo._activebookmark].node() |
|
360 | 366 | if repo.revs('%n and parents()', curhead): |
|
361 | 367 | # we are on the active bookmark |
|
362 | 368 | bmheads = [b for b in bmheads if curhead != b] |
|
363 | 369 | if bmheads: |
|
364 | 370 | msg = _('%i other divergent bookmarks for "%s"\n') |
|
365 | 371 | ui.status(msg % (len(bmheads), repo._activebookmark)) |
|
366 | 372 | |
|
367 | 373 | def _statusotherbranchheads(ui, repo): |
|
368 | 374 | currentbranch = repo.dirstate.branch() |
|
369 | 375 | allheads = repo.branchheads(currentbranch, closed=True) |
|
370 | 376 | heads = repo.branchheads(currentbranch) |
|
371 | 377 | if repo.revs('%ln and parents()', allheads): |
|
372 | 378 | # we are on a head, even though it might be closed |
|
373 | 379 | # |
|
374 | 380 | # on closed otherheads |
|
375 | 381 | # ========= ========== |
|
376 | 382 | # o 0 all heads for current branch are closed |
|
377 | 383 | # N only descendant branch heads are closed |
|
378 | 384 | # x 0 there is only one non-closed branch head |
|
379 | 385 | # N there are some non-closed branch heads |
|
380 | 386 | # ========= ========== |
|
381 | 387 | otherheads = repo.revs('%ln - parents()', heads) |
|
382 | 388 | if repo['.'].closesbranch(): |
|
383 | 389 | ui.warn(_('no open descendant heads on branch "%s", ' |
|
384 | 390 | 'updating to a closed head\n') % |
|
385 | 391 | (currentbranch)) |
|
386 | 392 | if otherheads: |
|
387 | 393 | ui.warn(_("(committing will reopen the head, " |
|
388 | 394 | "use 'hg heads .' to see %i other heads)\n") % |
|
389 | 395 | (len(otherheads))) |
|
390 | 396 | else: |
|
391 | 397 | ui.warn(_('(committing will reopen branch "%s")\n') % |
|
392 | 398 | (currentbranch)) |
|
393 | 399 | elif otherheads: |
|
394 | 400 | curhead = repo['.'] |
|
395 | 401 | ui.status(_('updated to "%s: %s"\n') % (curhead, |
|
396 | 402 | curhead.description().split('\n')[0])) |
|
397 | 403 | ui.status(_('%i other heads for branch "%s"\n') % |
|
398 | 404 | (len(otherheads), currentbranch)) |
|
399 | 405 | |
|
400 | 406 | def statusotherdests(ui, repo): |
|
401 | 407 | """Print message about other head""" |
|
402 | 408 | # XXX we should probably include a hint: |
|
403 | 409 | # - about what to do |
|
404 | 410 | # - how to see such heads |
|
405 | 411 | if repo._activebookmark: |
|
406 | 412 | _statusotherbook(ui, repo) |
|
407 | 413 | else: |
|
408 | 414 | _statusotherbranchheads(ui, repo) |
@@ -1,18 +1,19 b'' | |||
|
1 | 1 | # TODO there are a few deficiencies in this file: |
|
2 | 2 | # * The "namespace" of the labels needs to be worked out. We currently |
|
3 | 3 | # piggyback on existing values so color works. |
|
4 | 4 | # * Obsolescence isn't considered for node labels. See _cset_labels in |
|
5 | 5 | # map-cmdline.default. |
|
6 | 6 | showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n' |
|
7 | 7 | |
|
8 | 8 | showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}' |
|
9 | showstack = '{showwork}' | |
|
9 | 10 | |
|
10 | 11 | cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, 5))}' |
|
11 | 12 | |
|
12 | 13 | # Treat branch and tags specially so we don't display "default" or "tip" |
|
13 | 14 | cset_namespace = '{ifeq(namespace, "branches", names_branches, ifeq(namespace, "tags", names_tags, names_others))}' |
|
14 | 15 | names_branches = '{ifeq(branch, "default", "", " ({label('log.{colorname}', branch)})")}' |
|
15 | 16 | names_tags = '{if(names % "{ifeq(name, 'tip', '', name)}", " ({label('log.{colorname}', join(names % "{ifeq(name, 'tip', '', name)}", ' '))})")}' |
|
16 | 17 | names_others = '{if(names, " ({label('log.{colorname}', join(names, ' '))})")}' |
|
17 | 18 | |
|
18 | 19 | cset_shortdesc = '{label("log.description", desc|firstline)}' |
@@ -1,168 +1,171 b'' | |||
|
1 | 1 | $ cat >> $HGRCPATH << EOF |
|
2 | 2 | > [extensions] |
|
3 | 3 | > show = |
|
4 | 4 | > EOF |
|
5 | 5 | |
|
6 | 6 | No arguments shows available views |
|
7 | 7 | |
|
8 | 8 | $ hg init empty |
|
9 | 9 | $ cd empty |
|
10 | 10 | $ hg show |
|
11 | 11 | available views: |
|
12 | 12 | |
|
13 | 13 | bookmarks -- bookmarks and their associated changeset |
|
14 | stack -- current line of work | |
|
14 | 15 | work -- changesets that aren't finished |
|
15 | 16 | |
|
16 | 17 | abort: no view requested |
|
17 | 18 | (use "hg show VIEW" to choose a view) |
|
18 | 19 | [255] |
|
19 | 20 | |
|
20 | 21 | `hg help show` prints available views |
|
21 | 22 | |
|
22 | 23 | $ hg help show |
|
23 | 24 | hg show VIEW |
|
24 | 25 | |
|
25 | 26 | show various repository information |
|
26 | 27 | |
|
27 | 28 | A requested view of repository data is displayed. |
|
28 | 29 | |
|
29 | 30 | If no view is requested, the list of available views is shown and the |
|
30 | 31 | command aborts. |
|
31 | 32 | |
|
32 | 33 | Note: |
|
33 | 34 | There are no backwards compatibility guarantees for the output of this |
|
34 | 35 | command. Output may change in any future Mercurial release. |
|
35 | 36 | |
|
36 | 37 | Consumers wanting stable command output should specify a template via |
|
37 | 38 | "-T/--template". |
|
38 | 39 | |
|
39 | 40 | List of available views: |
|
40 | 41 | |
|
41 | 42 | bookmarks bookmarks and their associated changeset |
|
42 | 43 | |
|
44 | stack current line of work | |
|
45 | ||
|
43 | 46 | work changesets that aren't finished |
|
44 | 47 | |
|
45 | 48 | (use 'hg help -e show' to show help for the show extension) |
|
46 | 49 | |
|
47 | 50 | options: |
|
48 | 51 | |
|
49 | 52 | -T --template TEMPLATE display with template |
|
50 | 53 | |
|
51 | 54 | (some details hidden, use --verbose to show complete help) |
|
52 | 55 | |
|
53 | 56 | Unknown view prints error |
|
54 | 57 | |
|
55 | 58 | $ hg show badview |
|
56 | 59 | abort: unknown view: badview |
|
57 | 60 | (run "hg show" to see available views) |
|
58 | 61 | [255] |
|
59 | 62 | |
|
60 | 63 | HGPLAIN results in abort |
|
61 | 64 | |
|
62 | 65 | $ HGPLAIN=1 hg show bookmarks |
|
63 | 66 | abort: must specify a template in plain mode |
|
64 | 67 | (invoke with -T/--template to control output format) |
|
65 | 68 | [255] |
|
66 | 69 | |
|
67 | 70 | But not if a template is specified |
|
68 | 71 | |
|
69 | 72 | $ HGPLAIN=1 hg show bookmarks -T '{bookmark}\n' |
|
70 | 73 | (no bookmarks set) |
|
71 | 74 | |
|
72 | 75 | $ cd .. |
|
73 | 76 | |
|
74 | 77 | bookmarks view with no bookmarks prints empty message |
|
75 | 78 | |
|
76 | 79 | $ hg init books |
|
77 | 80 | $ cd books |
|
78 | 81 | $ touch f0 |
|
79 | 82 | $ hg -q commit -A -m initial |
|
80 | 83 | |
|
81 | 84 | $ hg show bookmarks |
|
82 | 85 | (no bookmarks set) |
|
83 | 86 | |
|
84 | 87 | bookmarks view shows bookmarks in an aligned table |
|
85 | 88 | |
|
86 | 89 | $ echo book1 > f0 |
|
87 | 90 | $ hg commit -m 'commit for book1' |
|
88 | 91 | $ echo book2 > f0 |
|
89 | 92 | $ hg commit -m 'commit for book2' |
|
90 | 93 | |
|
91 | 94 | $ hg bookmark -r 1 book1 |
|
92 | 95 | $ hg bookmark a-longer-bookmark |
|
93 | 96 | |
|
94 | 97 | $ hg show bookmarks |
|
95 | 98 | * a-longer-bookmark 7b570 |
|
96 | 99 | book1 b757f |
|
97 | 100 | |
|
98 | 101 | A custom bookmarks template works |
|
99 | 102 | |
|
100 | 103 | $ hg show bookmarks -T '{node} {bookmark} {active}\n' |
|
101 | 104 | 7b5709ab64cbc34da9b4367b64afff47f2c4ee83 a-longer-bookmark True |
|
102 | 105 | b757f780b8ffd71267c6ccb32e0882d9d32a8cc0 book1 False |
|
103 | 106 | |
|
104 | 107 | bookmarks JSON works |
|
105 | 108 | |
|
106 | 109 | $ hg show bookmarks -T json |
|
107 | 110 | [ |
|
108 | 111 | { |
|
109 | 112 | "active": true, |
|
110 | 113 | "bookmark": "a-longer-bookmark", |
|
111 | 114 | "longestbookmarklen": 17, |
|
112 | 115 | "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83" |
|
113 | 116 | }, |
|
114 | 117 | { |
|
115 | 118 | "active": false, |
|
116 | 119 | "bookmark": "book1", |
|
117 | 120 | "longestbookmarklen": 17, |
|
118 | 121 | "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0" |
|
119 | 122 | } |
|
120 | 123 | ] |
|
121 | 124 | |
|
122 | 125 | JSON works with no bookmarks |
|
123 | 126 | |
|
124 | 127 | $ hg book -d a-longer-bookmark |
|
125 | 128 | $ hg book -d book1 |
|
126 | 129 | $ hg show bookmarks -T json |
|
127 | 130 | [ |
|
128 | 131 | ] |
|
129 | 132 | |
|
130 | 133 | commands.show.aliasprefix aliases values to `show <view>` |
|
131 | 134 | |
|
132 | 135 | $ hg --config commands.show.aliasprefix=s sbookmarks |
|
133 | 136 | (no bookmarks set) |
|
134 | 137 | |
|
135 | 138 | $ hg --config commands.show.aliasprefix=sh shwork |
|
136 | 139 | @ 7b570 commit for book2 |
|
137 | 140 | o b757f commit for book1 |
|
138 | 141 | o ba592 initial |
|
139 | 142 | |
|
140 | 143 | $ hg --config commands.show.aliasprefix='s sh' swork |
|
141 | 144 | @ 7b570 commit for book2 |
|
142 | 145 | o b757f commit for book1 |
|
143 | 146 | o ba592 initial |
|
144 | 147 | |
|
145 | 148 | $ hg --config commands.show.aliasprefix='s sh' shwork |
|
146 | 149 | @ 7b570 commit for book2 |
|
147 | 150 | o b757f commit for book1 |
|
148 | 151 | o ba592 initial |
|
149 | 152 | |
|
150 | 153 | The aliases don't appear in `hg config` |
|
151 | 154 | |
|
152 | 155 | $ hg --config commands.show.aliasprefix=s config alias |
|
153 | 156 | [1] |
|
154 | 157 | |
|
155 | 158 | Doesn't overwrite existing alias |
|
156 | 159 | |
|
157 | 160 | $ hg --config alias.swork='log -r .' --config commands.show.aliasprefix=s swork |
|
158 | 161 | changeset: 2:7b5709ab64cb |
|
159 | 162 | tag: tip |
|
160 | 163 | user: test |
|
161 | 164 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
162 | 165 | summary: commit for book2 |
|
163 | 166 | |
|
164 | 167 | |
|
165 | 168 | $ hg --config alias.swork='log -r .' --config commands.show.aliasprefix=s config alias |
|
166 | 169 | alias.swork=log -r . |
|
167 | 170 | |
|
168 | 171 | $ cd .. |
General Comments 0
You need to be logged in to leave comments.
Login now