Mercurial > repos > other > linux
comparison prompt.py @ 64:39b07c5f8785
Add Git and Mercurial support to command prompt
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Tue, 25 Apr 2017 11:42:15 +0100 |
parents | |
children | 38843a80f378 |
comparison
equal
deleted
inserted
replaced
63:327c85286b54 | 64:39b07c5f8785 |
---|---|
1 #!/usr/bin/env python | |
2 | |
3 '''get repository information for use in a shell prompt | |
4 | |
5 Take a string, parse any special variables inside, and output the result. | |
6 | |
7 Useful mostly for putting information about the current repository into | |
8 a shell prompt. | |
9 | |
10 | |
11 | |
12 Taken from https://bitbucket.org/sjl/hg-prompt/src @ 5334581 | |
13 ''' | |
14 | |
15 from __future__ import with_statement | |
16 | |
17 import re | |
18 import os | |
19 import subprocess | |
20 from datetime import datetime, timedelta | |
21 from contextlib import closing | |
22 from os import path | |
23 from mercurial import extensions, commands, cmdutil, help | |
24 from mercurial.i18n import _ | |
25 from mercurial.node import hex, short | |
26 | |
27 cmdtable = {} | |
28 command = cmdutil.command(cmdtable) | |
29 | |
30 # `revrange' has been moved into module `scmutil' since v1.9. | |
31 try : | |
32 from mercurial import scmutil | |
33 revrange = scmutil.revrange | |
34 except : | |
35 revrange = cmdutil.revrange | |
36 | |
37 CACHE_PATH = ".hg/prompt/cache" | |
38 CACHE_TIMEOUT = timedelta(minutes=15) | |
39 | |
40 FILTER_ARG = re.compile(r'\|.+\((.*)\)') | |
41 | |
42 | |
43 def _cache_remote(repo, kind): | |
44 cache = path.join(repo.root, CACHE_PATH, kind) | |
45 c_tmp = cache + '.temp' | |
46 | |
47 popenargs = ['hg', kind, '--quiet'] | |
48 remote_path = repo.ui.config('prompt', 'remote') | |
49 if remote_path is not None: | |
50 popenargs.append(remote_path) | |
51 | |
52 null_path = 'NUL:' if subprocess.mswindows else '/dev/null' | |
53 with open(null_path, 'w') as null_fp: | |
54 with open(c_tmp, 'w') as stdout_fp: | |
55 exit_code = subprocess.call(popenargs, stdout=stdout_fp, stderr=null_fp) | |
56 | |
57 if exit_code not in (0, 1): # (changesets_found, changesets_not_found) | |
58 msg = "hg-prompt error: " | |
59 if remote_path: # Failure likely due to bad remote. Is 255 a valid check? | |
60 msg += "Can't access remote '%s'" % remote_path | |
61 else: | |
62 msg += "Error attempting 'hg %s'" % kind | |
63 print msg | |
64 | |
65 os.rename(c_tmp, cache) | |
66 return | |
67 | |
68 | |
69 def _with_groups(groups, out): | |
70 out_groups = [groups[0]] + [groups[-1]] | |
71 | |
72 if any(out_groups) and not all(out_groups): | |
73 print 'Error parsing prompt string. Mismatched braces?' | |
74 | |
75 out = out.replace('%', '%%') | |
76 return ("%s" + out + "%s") % (out_groups[0][:-1] if out_groups[0] else '', | |
77 out_groups[1][1:] if out_groups[1] else '') | |
78 | |
79 def _get_filter(name, g): | |
80 '''Return the filter with the given name, or None if it was not used.''' | |
81 matching_filters = filter(lambda s: s and s.startswith('|%s' % name), g) | |
82 if not matching_filters: | |
83 return None | |
84 | |
85 # Later filters will override earlier ones, for now. | |
86 f = matching_filters[-1] | |
87 | |
88 return f | |
89 | |
90 def _get_filter_arg(f): | |
91 if not f: | |
92 return None | |
93 | |
94 args = FILTER_ARG.match(f).groups() | |
95 if args: | |
96 return args[0] | |
97 else: | |
98 return None | |
99 | |
100 @command('prompt', | |
101 [('', 'angle-brackets', None, 'use angle brackets (<>) for keywords'), | |
102 ('', 'cache-incoming', None, 'used internally by hg-prompt'), | |
103 ('', 'cache-outgoing', None, 'used internally by hg-prompt')], | |
104 'hg prompt STRING') | |
105 def prompt(ui, repo, fs='', **opts): | |
106 '''get repository information for use in a shell prompt | |
107 | |
108 Take a string and output it for use in a shell prompt. You can use | |
109 keywords in curly braces:: | |
110 | |
111 $ hg prompt "currently on {branch}" | |
112 currently on default | |
113 | |
114 You can also use an extended form of any keyword:: | |
115 | |
116 {optional text here{keyword}more optional text} | |
117 | |
118 This will expand the inner {keyword} and output it along with the extra | |
119 text only if the {keyword} expands successfully. This is useful if you | |
120 have a keyword that may not always apply to the current state and you | |
121 have some text that you would like to see only if it is appropriate:: | |
122 | |
123 $ hg prompt "currently at {bookmark}" | |
124 currently at | |
125 $ hg prompt "{currently at {bookmark}}" | |
126 $ hg bookmark my-bookmark | |
127 $ hg prompt "{currently at {bookmark}}" | |
128 currently at my-bookmark | |
129 | |
130 See 'hg help prompt-keywords' for a list of available keywords. | |
131 | |
132 The format string may also be defined in an hgrc file:: | |
133 | |
134 [prompt] | |
135 template = {currently at {bookmark}} | |
136 | |
137 This is used when no format string is passed on the command line. | |
138 ''' | |
139 | |
140 def _basename(m): | |
141 return _with_groups(m.groups(), path.basename(repo.root)) if repo.root else '' | |
142 | |
143 def _bookmark(m): | |
144 try: | |
145 book = extensions.find('bookmarks').current(repo) | |
146 except AttributeError: | |
147 book = getattr(repo, '_bookmarkcurrent', None) | |
148 except KeyError: | |
149 book = getattr(repo, '_bookmarkcurrent', None) | |
150 if book is None: | |
151 book = getattr(repo, '_activebookmark', None) | |
152 if book: | |
153 cur = repo['.'].node() | |
154 if repo._bookmarks[book] == cur: | |
155 return _with_groups(m.groups(), book) | |
156 else: | |
157 return '' | |
158 | |
159 def _branch(m): | |
160 g = m.groups() | |
161 | |
162 branch = repo.dirstate.branch() | |
163 quiet = _get_filter('quiet', g) | |
164 | |
165 out = branch if (not quiet) or (branch != 'default') else '' | |
166 | |
167 return _with_groups(g, out) if out else '' | |
168 | |
169 def _closed(m): | |
170 g = m.groups() | |
171 | |
172 quiet = _get_filter('quiet', g) | |
173 | |
174 p = repo[None].parents()[0] | |
175 pn = p.node() | |
176 branch = repo.dirstate.branch() | |
177 closed = (p.extra().get('close') | |
178 and pn in repo.branchheads(branch, closed=True)) | |
179 out = 'X' if (not quiet) and closed else '' | |
180 | |
181 return _with_groups(g, out) if out else '' | |
182 | |
183 def _count(m): | |
184 g = m.groups() | |
185 query = [g[1][1:]] if g[1] else ['all()'] | |
186 return _with_groups(g, str(len(revrange(repo, query)))) | |
187 | |
188 def _node(m): | |
189 g = m.groups() | |
190 | |
191 parents = repo[None].parents() | |
192 p = 0 if '|merge' not in g else 1 | |
193 p = p if len(parents) > p else None | |
194 | |
195 format = short if '|short' in g else hex | |
196 | |
197 node = format(parents[p].node()) if p is not None else None | |
198 return _with_groups(g, str(node)) if node else '' | |
199 | |
200 def _patch(m): | |
201 g = m.groups() | |
202 | |
203 try: | |
204 extensions.find('mq') | |
205 except KeyError: | |
206 return '' | |
207 | |
208 q = repo.mq | |
209 | |
210 if _get_filter('quiet', g) and not len(q.series): | |
211 return '' | |
212 | |
213 if _get_filter('topindex', g): | |
214 if len(q.applied): | |
215 out = str(len(q.applied) - 1) | |
216 else: | |
217 out = '' | |
218 elif _get_filter('applied', g): | |
219 out = str(len(q.applied)) | |
220 elif _get_filter('unapplied', g): | |
221 out = str(len(q.unapplied(repo))) | |
222 elif _get_filter('count', g): | |
223 out = str(len(q.series)) | |
224 else: | |
225 out = q.applied[-1].name if q.applied else '' | |
226 | |
227 return _with_groups(g, out) if out else '' | |
228 | |
229 def _patches(m): | |
230 g = m.groups() | |
231 | |
232 try: | |
233 extensions.find('mq') | |
234 except KeyError: | |
235 return '' | |
236 | |
237 join_filter = _get_filter('join', g) | |
238 join_filter_arg = _get_filter_arg(join_filter) | |
239 sep = join_filter_arg if join_filter else ' -> ' | |
240 | |
241 patches = repo.mq.series | |
242 applied = [p.name for p in repo.mq.applied] | |
243 unapplied = filter(lambda p: p not in applied, patches) | |
244 | |
245 if _get_filter('hide_applied', g): | |
246 patches = filter(lambda p: p not in applied, patches) | |
247 if _get_filter('hide_unapplied', g): | |
248 patches = filter(lambda p: p not in unapplied, patches) | |
249 | |
250 if _get_filter('reverse', g): | |
251 patches = reversed(patches) | |
252 | |
253 pre_applied_filter = _get_filter('pre_applied', g) | |
254 pre_applied_filter_arg = _get_filter_arg(pre_applied_filter) | |
255 post_applied_filter = _get_filter('post_applied', g) | |
256 post_applied_filter_arg = _get_filter_arg(post_applied_filter) | |
257 | |
258 pre_unapplied_filter = _get_filter('pre_unapplied', g) | |
259 pre_unapplied_filter_arg = _get_filter_arg(pre_unapplied_filter) | |
260 post_unapplied_filter = _get_filter('post_unapplied', g) | |
261 post_unapplied_filter_arg = _get_filter_arg(post_unapplied_filter) | |
262 | |
263 for n, patch in enumerate(patches): | |
264 if patch in applied: | |
265 if pre_applied_filter: | |
266 patches[n] = pre_applied_filter_arg + patches[n] | |
267 if post_applied_filter: | |
268 patches[n] = patches[n] + post_applied_filter_arg | |
269 elif patch in unapplied: | |
270 if pre_unapplied_filter: | |
271 patches[n] = pre_unapplied_filter_arg + patches[n] | |
272 if post_unapplied_filter: | |
273 patches[n] = patches[n] + post_unapplied_filter_arg | |
274 | |
275 return _with_groups(g, sep.join(patches)) if patches else '' | |
276 | |
277 def _queue(m): | |
278 g = m.groups() | |
279 | |
280 try: | |
281 extensions.find('mq') | |
282 except KeyError: | |
283 return '' | |
284 | |
285 q = repo.mq | |
286 | |
287 out = os.path.basename(q.path) | |
288 if out == 'patches' and not os.path.isdir(q.path): | |
289 out = '' | |
290 elif out.startswith('patches-'): | |
291 out = out[8:] | |
292 | |
293 return _with_groups(g, out) if out else '' | |
294 | |
295 def _remote(kind): | |
296 def _r(m): | |
297 g = m.groups() | |
298 | |
299 cache_dir = path.join(repo.root, CACHE_PATH) | |
300 cache = path.join(cache_dir, kind) | |
301 if not path.isdir(cache_dir): | |
302 os.makedirs(cache_dir) | |
303 | |
304 cache_exists = path.isfile(cache) | |
305 | |
306 cache_time = (datetime.fromtimestamp(os.stat(cache).st_mtime) | |
307 if cache_exists else None) | |
308 if not cache_exists or cache_time < datetime.now() - CACHE_TIMEOUT: | |
309 if not cache_exists: | |
310 open(cache, 'w').close() | |
311 subprocess.Popen(['hg', 'prompt', '--cache-%s' % kind]) | |
312 | |
313 if cache_exists: | |
314 with open(cache) as c: | |
315 count = len(c.readlines()) | |
316 if g[1] and count > 0: | |
317 return _with_groups(g, str(count)) | |
318 elif g[2]: | |
319 return _with_groups(g, '0') if not count else '' | |
320 else: | |
321 return _with_groups(g, '') | |
322 else: | |
323 return '' | |
324 return _r | |
325 | |
326 def _rev(m): | |
327 g = m.groups() | |
328 | |
329 parents = repo[None].parents() | |
330 parent = 0 if '|merge' not in g else 1 | |
331 parent = parent if len(parents) > parent else None | |
332 | |
333 rev = parents[parent].rev() if parent is not None else -1 | |
334 return _with_groups(g, str(rev)) if rev >= 0 else '' | |
335 | |
336 def _root(m): | |
337 return _with_groups(m.groups(), repo.root) if repo.root else '' | |
338 | |
339 def _status(m): | |
340 g = m.groups() | |
341 | |
342 st = repo.status(unknown=True)[:5] | |
343 modified = any(st[:4]) | |
344 unknown = len(st[-1]) > 0 | |
345 | |
346 flag = '' | |
347 if '|modified' not in g and '|unknown' not in g: | |
348 flag = '!' if modified else '?' if unknown else '' | |
349 else: | |
350 if '|modified' in g: | |
351 flag += '!' if modified else '' | |
352 if '|unknown' in g: | |
353 flag += '?' if unknown else '' | |
354 | |
355 return _with_groups(g, flag) if flag else '' | |
356 | |
357 def _tags(m): | |
358 g = m.groups() | |
359 | |
360 sep = g[2][1:] if g[2] else ' ' | |
361 tags = repo[None].tags() | |
362 | |
363 quiet = _get_filter('quiet', g) | |
364 if quiet: | |
365 tags = filter(lambda tag: tag != 'tip', tags) | |
366 | |
367 return _with_groups(g, sep.join(tags)) if tags else '' | |
368 | |
369 def _task(m): | |
370 try: | |
371 task = extensions.find('tasks').current(repo) | |
372 return _with_groups(m.groups(), task) if task else '' | |
373 except KeyError: | |
374 return '' | |
375 | |
376 def _tip(m): | |
377 g = m.groups() | |
378 | |
379 format = short if '|short' in g else hex | |
380 | |
381 tip = repo[len(repo) - 1] | |
382 rev = tip.rev() | |
383 tip = format(tip.node()) if '|node' in g else tip.rev() | |
384 | |
385 return _with_groups(g, str(tip)) if rev >= 0 else '' | |
386 | |
387 def _update(m): | |
388 current_rev = repo[None].parents()[0] | |
389 | |
390 # Get the tip of the branch for the current branch | |
391 try: | |
392 heads = repo.branchmap()[current_rev.branch()] | |
393 tip = heads[-1] | |
394 except (KeyError, IndexError): | |
395 # We are in an empty repository. | |
396 | |
397 return '' | |
398 | |
399 for head in reversed(heads): | |
400 if not repo[head].closesbranch(): | |
401 tip = head | |
402 break | |
403 | |
404 return _with_groups(m.groups(), '^') if current_rev.children() else '' | |
405 | |
406 if opts.get("angle_brackets"): | |
407 tag_start = r'\<([^><]*?\<)?' | |
408 tag_end = r'(\>[^><]*?)?>' | |
409 brackets = '<>' | |
410 else: | |
411 tag_start = r'\{([^{}]*?\{)?' | |
412 tag_end = r'(\}[^{}]*?)?\}' | |
413 brackets = '{}' | |
414 | |
415 patterns = { | |
416 'bookmark': _bookmark, | |
417 'branch(\|quiet)?': _branch, | |
418 'closed(\|quiet)?': _closed, | |
419 'count(\|[^%s]*?)?' % brackets[-1]: _count, | |
420 'node(?:' | |
421 '(\|short)' | |
422 '|(\|merge)' | |
423 ')*': _node, | |
424 'patch(?:' | |
425 '(\|topindex)' | |
426 '|(\|applied)' | |
427 '|(\|unapplied)' | |
428 '|(\|count)' | |
429 '|(\|quiet)' | |
430 ')*': _patch, | |
431 'patches(?:' + | |
432 '(\|join\([^%s]*?\))' % brackets[-1] + | |
433 '|(\|reverse)' + | |
434 '|(\|hide_applied)' + | |
435 '|(\|hide_unapplied)' + | |
436 '|(\|pre_applied\([^%s]*?\))' % brackets[-1] + | |
437 '|(\|post_applied\([^%s]*?\))' % brackets[-1] + | |
438 '|(\|pre_unapplied\([^%s]*?\))' % brackets[-1] + | |
439 '|(\|post_unapplied\([^%s]*?\))' % brackets[-1] + | |
440 ')*': _patches, | |
441 'queue': _queue, | |
442 'rev(\|merge)?': _rev, | |
443 'root': _root, | |
444 'root\|basename': _basename, | |
445 'status(?:' | |
446 '(\|modified)' | |
447 '|(\|unknown)' | |
448 ')*': _status, | |
449 'tags(?:' + | |
450 '(\|quiet)' + | |
451 '|(\|[^%s]*?)' % brackets[-1] + | |
452 ')*': _tags, | |
453 'task': _task, | |
454 'tip(?:' | |
455 '(\|node)' | |
456 '|(\|short)' | |
457 ')*': _tip, | |
458 'update': _update, | |
459 | |
460 'incoming(?:' | |
461 '(\|count)' | |
462 '|(\|zero)' | |
463 ')*': _remote('incoming'), | |
464 'outgoing(?:' | |
465 '(\|count)' | |
466 '|(\|zero)' | |
467 ')*': _remote('outgoing') | |
468 } | |
469 | |
470 if opts.get("cache_incoming"): | |
471 _cache_remote(repo, 'incoming') | |
472 | |
473 if opts.get("cache_outgoing"): | |
474 _cache_remote(repo, 'outgoing') | |
475 | |
476 if not fs: | |
477 fs = repo.ui.config("prompt", "template", "") | |
478 | |
479 for tag, repl in patterns.items(): | |
480 fs = re.sub(tag_start + tag + tag_end, repl, fs) | |
481 ui.status(fs) | |
482 | |
483 def _pull_with_cache(orig, ui, repo, *args, **opts): | |
484 """Wrap the pull command to delete the incoming cache as well.""" | |
485 res = orig(ui, repo, *args, **opts) | |
486 cache = path.join(repo.root, CACHE_PATH, 'incoming') | |
487 if path.isfile(cache): | |
488 os.remove(cache) | |
489 return res | |
490 | |
491 def _push_with_cache(orig, ui, repo, *args, **opts): | |
492 """Wrap the push command to delete the outgoing cache as well.""" | |
493 res = orig(ui, repo, *args, **opts) | |
494 cache = path.join(repo.root, CACHE_PATH, 'outgoing') | |
495 if path.isfile(cache): | |
496 os.remove(cache) | |
497 return res | |
498 | |
499 def uisetup(ui): | |
500 extensions.wrapcommand(commands.table, 'pull', _pull_with_cache) | |
501 extensions.wrapcommand(commands.table, 'push', _push_with_cache) | |
502 try: | |
503 extensions.wrapcommand(extensions.find("fetch").cmdtable, 'fetch', _pull_with_cache) | |
504 except KeyError: | |
505 pass | |
506 | |
507 help.helptable += ( | |
508 (['prompt-keywords'], _('Keywords supported by hg-prompt'), | |
509 lambda _: r'''hg-prompt currently supports a number of keywords. | |
510 | |
511 Some keywords support filters. Filters can be chained when it makes | |
512 sense to do so. When in doubt, try it! | |
513 | |
514 bookmark | |
515 Display the current bookmark (requires the bookmarks extension). | |
516 | |
517 branch | |
518 Display the current branch. | |
519 | |
520 |quiet | |
521 Display the current branch only if it is not the default branch. | |
522 | |
523 closed | |
524 Display `X` if working on a closed branch (i.e. committing now would reopen | |
525 the branch). | |
526 | |
527 count | |
528 Display the number of revisions in the given revset (the revset `all()` | |
529 will be used if none is given). | |
530 | |
531 See `hg help revsets` for more information. | |
532 | |
533 |REVSET | |
534 The revset to count. | |
535 | |
536 incoming | |
537 Display nothing, but if the default path contains incoming changesets the | |
538 extra text will be expanded. | |
539 | |
540 For example: `{incoming changes{incoming}}` will expand to | |
541 `incoming changes` if there are changes, otherwise nothing. | |
542 | |
543 Checking for incoming changesets is an expensive operation, so `hg-prompt` | |
544 will cache the results in `.hg/prompt/cache/` and refresh them every 15 | |
545 minutes. | |
546 | |
547 |count | |
548 Display the number of incoming changesets (if greater than 0). | |
549 |zero | |
550 Display 0 if there are no incoming changesets. | |
551 | |
552 node | |
553 Display the (full) changeset hash of the current parent. | |
554 | |
555 |short | |
556 Display the hash as the short, 12-character form. | |
557 | |
558 |merge | |
559 Display the hash of the changeset you're merging with. | |
560 | |
561 outgoing | |
562 Display nothing, but if the current repository contains outgoing | |
563 changesets (to default) the extra text will be expanded. | |
564 | |
565 For example: `{outgoing changes{outgoing}}` will expand to | |
566 `outgoing changes` if there are changes, otherwise nothing. | |
567 | |
568 Checking for outgoing changesets is an expensive operation, so `hg-prompt` | |
569 will cache the results in `.hg/prompt/cache/` and refresh them every 15 | |
570 minutes. | |
571 | |
572 |count | |
573 Display the number of outgoing changesets (if greater than 0). | |
574 |zero | |
575 Display 0 if there are no incoming changesets. | |
576 | |
577 patch | |
578 Display the topmost currently-applied patch (requires the mq | |
579 extension). | |
580 | |
581 |count | |
582 Display the number of patches in the queue. | |
583 | |
584 |topindex | |
585 Display (zero-based) index of the topmost applied patch in the series | |
586 list (as displayed by :hg:`qtop -v`), or the empty string if no patch | |
587 is applied. | |
588 | |
589 |applied | |
590 Display the number of currently applied patches in the queue. | |
591 | |
592 |unapplied | |
593 Display the number of currently unapplied patches in the queue. | |
594 | |
595 |quiet | |
596 Display a number only if there are any patches in the queue. | |
597 | |
598 patches | |
599 Display a list of the current patches in the queue. It will look like | |
600 this: | |
601 | |
602 :::console | |
603 $ hg prompt '{patches}' | |
604 bottom-patch -> middle-patch -> top-patch | |
605 | |
606 |reverse | |
607 Display the patches in reverse order (i.e. topmost first). | |
608 | |
609 |hide_applied | |
610 Do not display applied patches. | |
611 | |
612 |hide_unapplied | |
613 Do not display unapplied patches. | |
614 | |
615 |join(SEP) | |
616 Display SEP between each patch, instead of the default ` -> `. | |
617 | |
618 |pre_applied(STRING) | |
619 Display STRING immediately before each applied patch. Useful for | |
620 adding color codes. | |
621 | |
622 |post_applied(STRING) | |
623 Display STRING immediately after each applied patch. Useful for | |
624 resetting color codes. | |
625 | |
626 |pre_unapplied(STRING) | |
627 Display STRING immediately before each unapplied patch. Useful for | |
628 adding color codes. | |
629 | |
630 |post_unapplied(STRING) | |
631 Display STRING immediately after each unapplied patch. Useful for | |
632 resetting color codes. | |
633 | |
634 queue | |
635 Display the name of the current MQ queue. | |
636 | |
637 rev | |
638 Display the repository-local changeset number of the current parent. | |
639 | |
640 |merge | |
641 Display the repository-local changeset number of the changeset you're | |
642 merging with. | |
643 | |
644 root | |
645 Display the full path to the root of the current repository, without a | |
646 trailing slash. | |
647 | |
648 |basename | |
649 Display the directory name of the root of the current repository. For | |
650 example, if the repository is in `/home/u/myrepo` then this keyword | |
651 would expand to `myrepo`. | |
652 | |
653 status | |
654 Display `!` if the repository has any changed/added/removed files, | |
655 otherwise `?` if it has any untracked (but not ignored) files, otherwise | |
656 nothing. | |
657 | |
658 |modified | |
659 Display `!` if the current repository contains files that have been | |
660 modified, added, removed, or deleted, otherwise nothing. | |
661 | |
662 |unknown | |
663 Display `?` if the current repository contains untracked files, | |
664 otherwise nothing. | |
665 | |
666 tags | |
667 Display the tags of the current parent, separated by a space. | |
668 | |
669 |quiet | |
670 Display the tags of the current parent, excluding the tag "tip". | |
671 | |
672 |SEP | |
673 Display the tags of the current parent, separated by `SEP`. | |
674 | |
675 task | |
676 Display the current task (requires the tasks extension). | |
677 | |
678 tip | |
679 Display the repository-local changeset number of the current tip. | |
680 | |
681 |node | |
682 Display the (full) changeset hash of the current tip. | |
683 | |
684 |short | |
685 Display a short form of the changeset hash of the current tip (must be | |
686 used with the **|node** filter) | |
687 | |
688 update | |
689 Display `^` if the current parent is not the tip of the current branch, | |
690 otherwise nothing. In effect, this lets you see if running `hg update` | |
691 would do something. | |
692 '''), | |
693 ) |