diff --git a/contrib/win32/mercurial.ini b/contrib/win32/mercurial.ini --- a/contrib/win32/mercurial.ini +++ b/contrib/win32/mercurial.ini @@ -1,98 +1,97 @@ ; System-wide Mercurial config file. ; ; !!! Do Not Edit This File !!! ; ; This file will be replaced by the installer on every upgrade. ; Editing this file can cause strange side effects on Vista. ; ; http://bitbucket.org/tortoisehg/stable/issue/135 ; ; To change settings you see in this file, override (or enable) them in ; your user Mercurial.ini file, where USERNAME is your Windows user name: ; ; XP or older - C:\Documents and Settings\USERNAME\Mercurial.ini ; Vista or later - C:\Users\USERNAME\Mercurial.ini [ui] ; editor used to enter commit logs, etc. Most text editors will work. editor = notepad ; show changed files and be a bit more verbose if True ; verbose = True ; colorize commands output ; color = auto ; username data to appear in commits ; it usually takes the form: Joe User ; username = Joe User ; In order to push/pull over ssh you must specify an ssh tool ;ssh = "C:\Progra~1\TortoiseSVN\bin\TortoisePlink.exe" -ssh -2 ;ssh = C:\cygwin\bin\ssh ; ; For more information about mercurial extensions, start here ; https://www.mercurial-scm.org/wiki/UsingExtensions ; ; Extensions shipped with Mercurial ; [extensions] ;acl = ;blackbox = ;bugzilla = ;children = ;churn = ;convert = ;eol = ;extdiff = ;fetch = ;gpg = ;hgk = ;highlight = ;histedit = ;interhg = ;largefiles = ;keyword = ;mq = ;notify = ;pager = ;patchbomb = ;progress = ;purge = ;rebase = ;record = ;relink = ;schemes = ;share = -;shelve = ;transplant = ;win32mbcs = ;zeroconf = ; ; Define external diff commands ; [extdiff] ;cmd.bc3diff = C:\Program Files\Beyond Compare 3\BCompare.exe ;cmd.vdiff = C:\Progra~1\TortoiseSVN\bin\TortoiseMerge.exe ;cmd.vimdiff = gvim.exe ;opts.vimdiff = -f "+next" "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))" [hgk] ; Replace the following with your path to hgk, uncomment it and ; install ActiveTcl (or another win32 port like tclkit) ; path="C:\Program Files\Mercurial\Contrib\hgk.tcl" ; vdiff=vdiff ; ; The git extended diff format can represent binary files, file ; permission changes, and rename information that the normal patch format ; cannot describe. However it is also not compatible with tools which ; expect normal patches. so enable git patches at your own risk. ; [diff] ;git = false ;nodates = false diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -1,6271 +1,6425 @@ # commands.py - command processing for mercurial # # Copyright 2005-2007 Matt Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import import difflib import errno import os import re import sys from .i18n import _ from .node import ( hex, nullid, nullrev, short, wdirhex, wdirrev, ) from . import ( archival, bookmarks, bundle2, changegroup, cmdutil, copies, debugcommands as debugcommandsmod, destutil, dirstateguard, discovery, encoding, error, exchange, extensions, filemerge, formatter, graphmod, hbisect, help, hg, logcmdutil, merge as mergemod, narrowspec, obsolete, obsutil, patch, phases, pycompat, rcutil, registrar, repair, revsetlang, rewriteutil, scmutil, server, + shelve as shelvemod, state as statemod, streamclone, tags as tagsmod, ui as uimod, util, verify as verifymod, wireprotoserver, ) from .utils import ( dateutil, stringutil, ) table = {} table.update(debugcommandsmod.command._table) command = registrar.command(table) INTENT_READONLY = registrar.INTENT_READONLY # common command options globalopts = [ ('R', 'repository', '', _('repository root directory or name of overlay bundle file'), _('REPO')), ('', 'cwd', '', _('change working directory'), _('DIR')), ('y', 'noninteractive', None, _('do not prompt, automatically pick the first choice for all prompts')), ('q', 'quiet', None, _('suppress output')), ('v', 'verbose', None, _('enable additional output')), ('', 'color', '', # i18n: 'always', 'auto', 'never', and 'debug' are keywords # and should not be translated _("when to colorize (boolean, always, auto, never, or debug)"), _('TYPE')), ('', 'config', [], _('set/override config option (use \'section.name=value\')'), _('CONFIG')), ('', 'debug', None, _('enable debugging output')), ('', 'debugger', None, _('start debugger')), ('', 'encoding', encoding.encoding, _('set the charset encoding'), _('ENCODE')), ('', 'encodingmode', encoding.encodingmode, _('set the charset encoding mode'), _('MODE')), ('', 'traceback', None, _('always print a traceback on exception')), ('', 'time', None, _('time how long the command takes')), ('', 'profile', None, _('print command execution profile')), ('', 'version', None, _('output version information and exit')), ('h', 'help', None, _('display help and exit')), ('', 'hidden', False, _('consider hidden changesets')), ('', 'pager', 'auto', _("when to paginate (boolean, always, auto, or never)"), _('TYPE')), ] dryrunopts = cmdutil.dryrunopts remoteopts = cmdutil.remoteopts walkopts = cmdutil.walkopts commitopts = cmdutil.commitopts commitopts2 = cmdutil.commitopts2 formatteropts = cmdutil.formatteropts templateopts = cmdutil.templateopts logopts = cmdutil.logopts diffopts = cmdutil.diffopts diffwsopts = cmdutil.diffwsopts diffopts2 = cmdutil.diffopts2 mergetoolopts = cmdutil.mergetoolopts similarityopts = cmdutil.similarityopts subrepoopts = cmdutil.subrepoopts debugrevlogopts = cmdutil.debugrevlogopts # Commands start here, listed alphabetically @command('add', walkopts + subrepoopts + dryrunopts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, helpbasic=True, inferrepo=True) def add(ui, repo, *pats, **opts): """add the specified files on the next commit Schedule files to be version controlled and added to the repository. The files will be added to the repository at the next commit. To undo an add before that, see :hg:`forget`. If no names are given, add all files to the repository (except files matching ``.hgignore``). .. container:: verbose Examples: - New (unknown) files are added automatically by :hg:`add`:: $ ls foo.c $ hg status ? foo.c $ hg add adding foo.c $ hg status A foo.c - Specific files to be added can be specified:: $ ls bar.c foo.c $ hg status ? bar.c ? foo.c $ hg add bar.c $ hg status A bar.c ? foo.c Returns 0 if all files are successfully added. """ m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts)) uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts) return rejected and 1 or 0 @command('addremove', similarityopts + subrepoopts + walkopts + dryrunopts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, inferrepo=True) def addremove(ui, repo, *pats, **opts): """add all new files, delete all missing files Add all new files and remove all missing files from the repository. Unless names are given, new files are ignored if they match any of the patterns in ``.hgignore``. As with add, these changes take effect at the next commit. Use the -s/--similarity option to detect renamed files. This option takes a percentage between 0 (disabled) and 100 (files must be identical) as its parameter. With a parameter greater than 0, this compares every removed file with every added file and records those similar enough as renames. Detecting renamed files this way can be expensive. After using this option, :hg:`status -C` can be used to check which files were identified as moved or renamed. If not specified, -s/--similarity defaults to 100 and only renames of identical files are detected. .. container:: verbose Examples: - A number of files (bar.c and foo.c) are new, while foobar.c has been removed (without using :hg:`remove`) from the repository:: $ ls bar.c foo.c $ hg status ! foobar.c ? bar.c ? foo.c $ hg addremove adding bar.c adding foo.c removing foobar.c $ hg status A bar.c A foo.c R foobar.c - A file foobar.c was moved to foo.c without using :hg:`rename`. Afterwards, it was edited slightly:: $ ls foo.c $ hg status ! foobar.c ? foo.c $ hg addremove --similarity 90 removing foobar.c adding foo.c recording removal of foobar.c as rename to foo.c (94% similar) $ hg status -C A foo.c foobar.c R foobar.c Returns 0 if all files are successfully added. """ opts = pycompat.byteskwargs(opts) if not opts.get('similarity'): opts['similarity'] = '100' matcher = scmutil.match(repo[None], pats, opts) relative = scmutil.anypats(pats, opts) uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) return scmutil.addremove(repo, matcher, "", uipathfn, opts) @command('annotate|blame', [('r', 'rev', '', _('annotate the specified revision'), _('REV')), ('', 'follow', None, _('follow copies/renames and list the filename (DEPRECATED)')), ('', 'no-follow', None, _("don't follow copies and renames")), ('a', 'text', None, _('treat all files as text')), ('u', 'user', None, _('list the author (long with -v)')), ('f', 'file', None, _('list the filename')), ('d', 'date', None, _('list the date (short with -q)')), ('n', 'number', None, _('list the revision number (default)')), ('c', 'changeset', None, _('list the changeset')), ('l', 'line-number', None, _('show line number at the first appearance')), ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')), ] + diffwsopts + walkopts + formatteropts, _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'), helpcategory=command.CATEGORY_FILE_CONTENTS, helpbasic=True, inferrepo=True) def annotate(ui, repo, *pats, **opts): """show changeset information by line for each file List changes in files, showing the revision id responsible for each line. This command is useful for discovering when a change was made and by whom. If you include --file, --user, or --date, the revision number is suppressed unless you also include --number. Without the -a/--text option, annotate will avoid processing files it detects as binary. With -a, annotate will annotate the file anyway, although the results will probably be neither useful nor desirable. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :lines: List of lines with annotation data. :path: String. Repository-absolute path of the specified file. And each entry of ``{lines}`` provides the following sub-keywords in addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc. :line: String. Line content. :lineno: Integer. Line number at that revision. :path: String. Repository-absolute path of the file at that revision. See :hg:`help templates.operators` for the list expansion syntax. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) if not pats: raise error.Abort(_('at least one filename or pattern is required')) if opts.get('follow'): # --follow is deprecated and now just an alias for -f/--file # to mimic the behavior of Mercurial before version 1.5 opts['file'] = True if (not opts.get('user') and not opts.get('changeset') and not opts.get('date') and not opts.get('file')): opts['number'] = True linenumber = opts.get('line_number') is not None if linenumber and (not opts.get('changeset')) and (not opts.get('number')): raise error.Abort(_('at least one of -n/-c is required for -l')) rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev) ui.pager('annotate') rootfm = ui.formatter('annotate', opts) if ui.debugflag: shorthex = pycompat.identity else: def shorthex(h): return h[:12] if ui.quiet: datefunc = dateutil.shortdate else: datefunc = dateutil.datestr if ctx.rev() is None: if opts.get('changeset'): # omit "+" suffix which is appended to node hex def formatrev(rev): if rev == wdirrev: return '%d' % ctx.p1().rev() else: return '%d' % rev else: def formatrev(rev): if rev == wdirrev: return '%d+' % ctx.p1().rev() else: return '%d ' % rev def formathex(h): if h == wdirhex: return '%s+' % shorthex(hex(ctx.p1().node())) else: return '%s ' % shorthex(h) else: formatrev = b'%d'.__mod__ formathex = shorthex opmap = [ ('user', ' ', lambda x: x.fctx.user(), ui.shortuser), ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev), ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex), ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)), ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr), ('lineno', ':', lambda x: x.lineno, pycompat.bytestr), ] opnamemap = { 'rev': 'number', 'node': 'changeset', 'path': 'file', 'lineno': 'line_number', } if rootfm.isplain(): def makefunc(get, fmt): return lambda x: fmt(get(x)) else: def makefunc(get, fmt): return get datahint = rootfm.datahint() funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap if opts.get(opnamemap.get(fn, fn)) or fn in datahint] funcmap[0] = (funcmap[0][0], '') # no separator in front of first column fields = ' '.join(fn for fn, sep, get, fmt in opmap if opts.get(opnamemap.get(fn, fn)) or fn in datahint) def bad(x, y): raise error.Abort("%s: %s" % (x, y)) m = scmutil.match(ctx, pats, opts, badfn=bad) follow = not opts.get('no_follow') diffopts = patch.difffeatureopts(ui, opts, section='annotate', whitespace=True) skiprevs = opts.get('skip') if skiprevs: skiprevs = scmutil.revrange(repo, skiprevs) uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) for abs in ctx.walk(m): fctx = ctx[abs] rootfm.startitem() rootfm.data(path=abs) if not opts.get('text') and fctx.isbinary(): rootfm.plain(_("%s: binary file\n") % uipathfn(abs)) continue fm = rootfm.nested('lines', tmpl='{rev}: {line}') lines = fctx.annotate(follow=follow, skiprevs=skiprevs, diffopts=diffopts) if not lines: fm.end() continue formats = [] pieces = [] for f, sep in funcmap: l = [f(n) for n in lines] if fm.isplain(): sizes = [encoding.colwidth(x) for x in l] ml = max(sizes) formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes]) else: formats.append(['%s' for x in l]) pieces.append(l) for f, p, n in zip(zip(*formats), zip(*pieces), lines): fm.startitem() fm.context(fctx=n.fctx) fm.write(fields, "".join(f), *p) if n.skip: fmt = "* %s" else: fmt = ": %s" fm.write('line', fmt, n.text) if not lines[-1].text.endswith('\n'): fm.plain('\n') fm.end() rootfm.end() @command('archive', [('', 'no-decode', None, _('do not pass files through decoders')), ('p', 'prefix', '', _('directory prefix for files in archive'), _('PREFIX')), ('r', 'rev', '', _('revision to distribute'), _('REV')), ('t', 'type', '', _('type of distribution to create'), _('TYPE')), ] + subrepoopts + walkopts, _('[OPTION]... DEST'), helpcategory=command.CATEGORY_IMPORT_EXPORT) def archive(ui, repo, dest, **opts): '''create an unversioned archive of a repository revision By default, the revision used is the parent of the working directory; use -r/--rev to specify a different revision. The archive type is automatically detected based on file extension (to override, use -t/--type). .. container:: verbose Examples: - create a zip file containing the 1.0 release:: hg archive -r 1.0 project-1.0.zip - create a tarball excluding .hg files:: hg archive project.tar.gz -X ".hg*" Valid types are: :``files``: a directory full of files (default) :``tar``: tar archive, uncompressed :``tbz2``: tar archive, compressed using bzip2 :``tgz``: tar archive, compressed using gzip :``uzip``: zip archive, uncompressed :``zip``: zip archive, compressed using deflate The exact name of the destination archive or directory is given using a format string; see :hg:`help export` for details. Each member added to an archive file has a directory prefix prepended. Use -p/--prefix to specify a format string for the prefix. The default is the basename of the archive, with suffixes removed. Returns 0 on success. ''' opts = pycompat.byteskwargs(opts) rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev) if not ctx: raise error.Abort(_('no working directory: please specify a revision')) node = ctx.node() dest = cmdutil.makefilename(ctx, dest) if os.path.realpath(dest) == repo.root: raise error.Abort(_('repository root cannot be destination')) kind = opts.get('type') or archival.guesskind(dest) or 'files' prefix = opts.get('prefix') if dest == '-': if kind == 'files': raise error.Abort(_('cannot archive plain files to stdout')) dest = cmdutil.makefileobj(ctx, dest) if not prefix: prefix = os.path.basename(repo.root) + '-%h' prefix = cmdutil.makefilename(ctx, prefix) match = scmutil.match(ctx, [], opts) archival.archive(repo, dest, node, kind, not opts.get('no_decode'), match, prefix, subrepos=opts.get('subrepos')) @command('backout', [('', 'merge', None, _('merge with old dirstate parent after backout')), ('', 'commit', None, _('commit if no conflicts were encountered (DEPRECATED)')), ('', 'no-commit', None, _('do not commit')), ('', 'parent', '', _('parent to choose when backing out merge (DEPRECATED)'), _('REV')), ('r', 'rev', '', _('revision to backout'), _('REV')), ('e', 'edit', False, _('invoke editor on commit messages')), ] + mergetoolopts + walkopts + commitopts + commitopts2, _('[OPTION]... [-r] REV'), helpcategory=command.CATEGORY_CHANGE_MANAGEMENT) def backout(ui, repo, node=None, rev=None, **opts): '''reverse effect of earlier changeset Prepare a new changeset with the effect of REV undone in the current working directory. If no conflicts were encountered, it will be committed immediately. If REV is the parent of the working directory, then this new changeset is committed automatically (unless --no-commit is specified). .. note:: :hg:`backout` cannot be used to fix either an unwanted or incorrect merge. .. container:: verbose Examples: - Reverse the effect of the parent of the working directory. This backout will be committed immediately:: hg backout -r . - Reverse the effect of previous bad revision 23:: hg backout -r 23 - Reverse the effect of previous bad revision 23 and leave changes uncommitted:: hg backout -r 23 --no-commit hg commit -m "Backout revision 23" By default, the pending changeset will have one parent, maintaining a linear history. With --merge, the pending changeset will instead have two parents: the old parent of the working directory and a new child of REV that simply undoes REV. Before version 1.7, the behavior without --merge was equivalent to specifying --merge followed by :hg:`update --clean .` to cancel the merge and leave the child of REV as a head to be merged separately. See :hg:`help dates` for a list of formats valid for -d/--date. See :hg:`help revert` for a way to restore files to the state of another revision. Returns 0 on success, 1 if nothing to backout or there are unresolved files. ''' with repo.wlock(), repo.lock(): return _dobackout(ui, repo, node, rev, **opts) def _dobackout(ui, repo, node=None, rev=None, **opts): opts = pycompat.byteskwargs(opts) if opts.get('commit') and opts.get('no_commit'): raise error.Abort(_("cannot use --commit with --no-commit")) if opts.get('merge') and opts.get('no_commit'): raise error.Abort(_("cannot use --merge with --no-commit")) if rev and node: raise error.Abort(_("please specify just one revision")) if not rev: rev = node if not rev: raise error.Abort(_("please specify a revision to backout")) date = opts.get('date') if date: opts['date'] = dateutil.parsedate(date) cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) node = scmutil.revsingle(repo, rev).node() op1, op2 = repo.dirstate.parents() if not repo.changelog.isancestor(node, op1): raise error.Abort(_('cannot backout change that is not an ancestor')) p1, p2 = repo.changelog.parents(node) if p1 == nullid: raise error.Abort(_('cannot backout a change with no parents')) if p2 != nullid: if not opts.get('parent'): raise error.Abort(_('cannot backout a merge changeset')) p = repo.lookup(opts['parent']) if p not in (p1, p2): raise error.Abort(_('%s is not a parent of %s') % (short(p), short(node))) parent = p else: if opts.get('parent'): raise error.Abort(_('cannot use --parent on non-merge changeset')) parent = p1 # the backout should appear on the same branch branch = repo.dirstate.branch() bheads = repo.branchheads(branch) rctx = scmutil.revsingle(repo, hex(parent)) if not opts.get('merge') and op1 != node: with dirstateguard.dirstateguard(repo, 'backout'): overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'backout'): stats = mergemod.update(repo, parent, branchmerge=True, force=True, ancestor=node, mergeancestor=False) repo.setparents(op1, op2) hg._showstats(repo, stats) if stats.unresolvedcount: repo.ui.status(_("use 'hg resolve' to retry unresolved " "file merges\n")) return 1 else: hg.clean(repo, node, show_stats=False) repo.dirstate.setbranch(branch) cmdutil.revert(ui, repo, rctx, repo.dirstate.parents()) if opts.get('no_commit'): msg = _("changeset %s backed out, " "don't forget to commit.\n") ui.status(msg % short(node)) return 0 def commitfunc(ui, repo, message, match, opts): editform = 'backout' e = cmdutil.getcommiteditor(editform=editform, **pycompat.strkwargs(opts)) if not message: # we don't translate commit messages message = "Backed out changeset %s" % short(node) e = cmdutil.getcommiteditor(edit=True, editform=editform) return repo.commit(message, opts.get('user'), opts.get('date'), match, editor=e) newnode = cmdutil.commit(ui, repo, commitfunc, [], opts) if not newnode: ui.status(_("nothing changed\n")) return 1 cmdutil.commitstatus(repo, newnode, branch, bheads) def nice(node): return '%d:%s' % (repo.changelog.rev(node), short(node)) ui.status(_('changeset %s backs out changeset %s\n') % (nice(repo.changelog.tip()), nice(node))) if opts.get('merge') and op1 != node: hg.clean(repo, op1, show_stats=False) ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip())) overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'backout'): return hg.merge(repo, hex(repo.changelog.tip())) return 0 @command('bisect', [('r', 'reset', False, _('reset bisect state')), ('g', 'good', False, _('mark changeset good')), ('b', 'bad', False, _('mark changeset bad')), ('s', 'skip', False, _('skip testing changeset')), ('e', 'extend', False, _('extend the bisect range')), ('c', 'command', '', _('use command to check changeset state'), _('CMD')), ('U', 'noupdate', False, _('do not update to target'))], _("[-gbsr] [-U] [-c CMD] [REV]"), helpcategory=command.CATEGORY_CHANGE_NAVIGATION) def bisect(ui, repo, rev=None, extra=None, command=None, reset=None, good=None, bad=None, skip=None, extend=None, noupdate=None): """subdivision search of changesets This command helps to find changesets which introduce problems. To use, mark the earliest changeset you know exhibits the problem as bad, then mark the latest changeset which is free from the problem as good. Bisect will update your working directory to a revision for testing (unless the -U/--noupdate option is specified). Once you have performed tests, mark the working directory as good or bad, and bisect will either update to another candidate changeset or announce that it has found the bad revision. As a shortcut, you can also use the revision argument to mark a revision as good or bad without checking it out first. If you supply a command, it will be used for automatic bisection. The environment variable HG_NODE will contain the ID of the changeset being tested. The exit status of the command will be used to mark revisions as good or bad: status 0 means good, 125 means to skip the revision, 127 (command not found) will abort the bisection, and any other non-zero exit status means the revision is bad. .. container:: verbose Some examples: - start a bisection with known bad revision 34, and good revision 12:: hg bisect --bad 34 hg bisect --good 12 - advance the current bisection by marking current revision as good or bad:: hg bisect --good hg bisect --bad - mark the current revision, or a known revision, to be skipped (e.g. if that revision is not usable because of another issue):: hg bisect --skip hg bisect --skip 23 - skip all revisions that do not touch directories ``foo`` or ``bar``:: hg bisect --skip "!( file('path:foo') & file('path:bar') )" - forget the current bisection:: hg bisect --reset - use 'make && make tests' to automatically find the first broken revision:: hg bisect --reset hg bisect --bad 34 hg bisect --good 12 hg bisect --command "make && make tests" - see all changesets whose states are already known in the current bisection:: hg log -r "bisect(pruned)" - see the changeset currently being bisected (especially useful if running with -U/--noupdate):: hg log -r "bisect(current)" - see all changesets that took part in the current bisection:: hg log -r "bisect(range)" - you can even get a nice graph:: hg log --graph -r "bisect(range)" See :hg:`help revisions.bisect` for more about the `bisect()` predicate. Returns 0 on success. """ # backward compatibility if rev in "good bad reset init".split(): ui.warn(_("(use of 'hg bisect ' is deprecated)\n")) cmd, rev, extra = rev, extra, None if cmd == "good": good = True elif cmd == "bad": bad = True else: reset = True elif extra: raise error.Abort(_('incompatible arguments')) incompatibles = { '--bad': bad, '--command': bool(command), '--extend': extend, '--good': good, '--reset': reset, '--skip': skip, } enabled = [x for x in incompatibles if incompatibles[x]] if len(enabled) > 1: raise error.Abort(_('%s and %s are incompatible') % tuple(sorted(enabled)[0:2])) if reset: hbisect.resetstate(repo) return state = hbisect.load_state(repo) # update state if good or bad or skip: if rev: nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])] else: nodes = [repo.lookup('.')] if good: state['good'] += nodes elif bad: state['bad'] += nodes elif skip: state['skip'] += nodes hbisect.save_state(repo, state) if not (state['good'] and state['bad']): return def mayupdate(repo, node, show_stats=True): """common used update sequence""" if noupdate: return cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) return hg.clean(repo, node, show_stats=show_stats) displayer = logcmdutil.changesetdisplayer(ui, repo, {}) if command: changesets = 1 if noupdate: try: node = state['current'][0] except LookupError: raise error.Abort(_('current bisect revision is unknown - ' 'start a new bisect to fix')) else: node, p2 = repo.dirstate.parents() if p2 != nullid: raise error.Abort(_('current bisect revision is a merge')) if rev: node = repo[scmutil.revsingle(repo, rev, node)].node() try: while changesets: # update state state['current'] = [node] hbisect.save_state(repo, state) status = ui.system(command, environ={'HG_NODE': hex(node)}, blockedtag='bisect_check') if status == 125: transition = "skip" elif status == 0: transition = "good" # status < 0 means process was killed elif status == 127: raise error.Abort(_("failed to execute %s") % command) elif status < 0: raise error.Abort(_("%s killed") % command) else: transition = "bad" state[transition].append(node) ctx = repo[node] ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)) hbisect.checkstate(state) # bisect nodes, changesets, bgood = hbisect.bisect(repo, state) # update to next check node = nodes[0] mayupdate(repo, node, show_stats=False) finally: state['current'] = [node] hbisect.save_state(repo, state) hbisect.printresult(ui, repo, state, displayer, nodes, bgood) return hbisect.checkstate(state) # actually bisect nodes, changesets, good = hbisect.bisect(repo, state) if extend: if not changesets: extendnode = hbisect.extendrange(repo, state, nodes, good) if extendnode is not None: ui.write(_("Extending search to changeset %d:%s\n") % (extendnode.rev(), extendnode)) state['current'] = [extendnode.node()] hbisect.save_state(repo, state) return mayupdate(repo, extendnode.node()) raise error.Abort(_("nothing to extend")) if changesets == 0: hbisect.printresult(ui, repo, state, displayer, nodes, good) else: assert len(nodes) == 1 # only a single node can be tested next node = nodes[0] # compute the approximate number of remaining tests tests, size = 0, 2 while size <= changesets: tests, size = tests + 1, size * 2 rev = repo.changelog.rev(node) ui.write(_("Testing changeset %d:%s " "(%d changesets remaining, ~%d tests)\n") % (rev, short(node), changesets, tests)) state['current'] = [node] hbisect.save_state(repo, state) return mayupdate(repo, node) @command('bookmarks|bookmark', [('f', 'force', False, _('force')), ('r', 'rev', '', _('revision for bookmark action'), _('REV')), ('d', 'delete', False, _('delete a given bookmark')), ('m', 'rename', '', _('rename a given bookmark'), _('OLD')), ('i', 'inactive', False, _('mark a bookmark inactive')), ('l', 'list', False, _('list existing bookmarks')), ] + formatteropts, _('hg bookmarks [OPTIONS]... [NAME]...'), helpcategory=command.CATEGORY_CHANGE_ORGANIZATION) def bookmark(ui, repo, *names, **opts): '''create a new bookmark or list existing bookmarks Bookmarks are labels on changesets to help track lines of development. Bookmarks are unversioned and can be moved, renamed and deleted. Deleting or moving a bookmark has no effect on the associated changesets. Creating or updating to a bookmark causes it to be marked as 'active'. The active bookmark is indicated with a '*'. When a commit is made, the active bookmark will advance to the new commit. A plain :hg:`update` will also advance an active bookmark, if possible. Updating away from a bookmark will cause it to be deactivated. Bookmarks can be pushed and pulled between repositories (see :hg:`help push` and :hg:`help pull`). If a shared bookmark has diverged, a new 'divergent bookmark' of the form 'name@path' will be created. Using :hg:`merge` will resolve the divergence. Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying the active bookmark's name. A bookmark named '@' has the special property that :hg:`clone` will check it out by default if it exists. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions such as ``{bookmark}``. See also :hg:`help templates`. :active: Boolean. True if the bookmark is active. Examples: - create an active bookmark for a new line of development:: hg book new-feature - create an inactive bookmark as a place marker:: hg book -i reviewed - create an inactive bookmark on another changeset:: hg book -r .^ tested - rename bookmark turkey to dinner:: hg book -m turkey dinner - move the '@' bookmark from another branch:: hg book -f @ - print only the active bookmark name:: hg book -ql . ''' opts = pycompat.byteskwargs(opts) force = opts.get('force') rev = opts.get('rev') inactive = opts.get('inactive') # meaning add/rename to inactive bookmark selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)] if len(selactions) > 1: raise error.Abort(_('--%s and --%s are incompatible') % tuple(selactions[:2])) if selactions: action = selactions[0] elif names or rev: action = 'add' elif inactive: action = 'inactive' # meaning deactivate else: action = 'list' if rev and action in {'delete', 'rename', 'list'}: raise error.Abort(_("--rev is incompatible with --%s") % action) if inactive and action in {'delete', 'list'}: raise error.Abort(_("--inactive is incompatible with --%s") % action) if not names and action in {'add', 'delete'}: raise error.Abort(_("bookmark name required")) if action in {'add', 'delete', 'rename', 'inactive'}: with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr: if action == 'delete': names = pycompat.maplist(repo._bookmarks.expandname, names) bookmarks.delete(repo, tr, names) elif action == 'rename': if not names: raise error.Abort(_("new bookmark name required")) elif len(names) > 1: raise error.Abort(_("only one new bookmark name allowed")) oldname = repo._bookmarks.expandname(opts['rename']) bookmarks.rename(repo, tr, oldname, names[0], force, inactive) elif action == 'add': bookmarks.addbookmarks(repo, tr, names, rev, force, inactive) elif action == 'inactive': if len(repo._bookmarks) == 0: ui.status(_("no bookmarks set\n")) elif not repo._activebookmark: ui.status(_("no active bookmark\n")) else: bookmarks.deactivate(repo) elif action == 'list': names = pycompat.maplist(repo._bookmarks.expandname, names) with ui.formatter('bookmarks', opts) as fm: bookmarks.printbookmarks(ui, repo, fm, names) else: raise error.ProgrammingError('invalid action: %s' % action) @command('branch', [('f', 'force', None, _('set branch name even if it shadows an existing branch')), ('C', 'clean', None, _('reset branch name to parent branch name')), ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')), ], _('[-fC] [NAME]'), helpcategory=command.CATEGORY_CHANGE_ORGANIZATION) def branch(ui, repo, label=None, **opts): """set or show the current branch name .. note:: Branch names are permanent and global. Use :hg:`bookmark` to create a light-weight bookmark instead. See :hg:`help glossary` for more information about named branches and bookmarks. With no argument, show the current branch name. With one argument, set the working directory branch name (the branch will not exist in the repository until the next commit). Standard practice recommends that primary development take place on the 'default' branch. Unless -f/--force is specified, branch will not let you set a branch name that already exists. Use -C/--clean to reset the working directory branch to that of the parent of the working directory, negating a previous branch change. Use the command :hg:`update` to switch to an existing branch. Use :hg:`commit --close-branch` to mark this branch head as closed. When all heads of a branch are closed, the branch will be considered closed. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) revs = opts.get('rev') if label: label = label.strip() if not opts.get('clean') and not label: if revs: raise error.Abort(_("no branch name specified for the revisions")) ui.write("%s\n" % repo.dirstate.branch()) return with repo.wlock(): if opts.get('clean'): label = repo['.'].branch() repo.dirstate.setbranch(label) ui.status(_('reset working directory to branch %s\n') % label) elif label: scmutil.checknewlabel(repo, label, 'branch') if revs: return cmdutil.changebranch(ui, repo, revs, label) if not opts.get('force') and label in repo.branchmap(): if label not in [p.branch() for p in repo[None].parents()]: raise error.Abort(_('a branch of the same name already' ' exists'), # i18n: "it" refers to an existing branch hint=_("use 'hg update' to switch to it")) repo.dirstate.setbranch(label) ui.status(_('marked working directory as branch %s\n') % label) # find any open named branches aside from default for n, h, t, c in repo.branchmap().iterbranches(): if n != "default" and not c: return 0 ui.status(_('(branches are permanent and global, ' 'did you want a bookmark?)\n')) @command('branches', [('a', 'active', False, _('show only branches that have unmerged heads (DEPRECATED)')), ('c', 'closed', False, _('show normal and closed branches')), ('r', 'rev', [], _('show branch name(s) of the given rev')) ] + formatteropts, _('[-c]'), helpcategory=command.CATEGORY_CHANGE_ORGANIZATION, intents={INTENT_READONLY}) def branches(ui, repo, active=False, closed=False, **opts): """list repository named branches List the repository's named branches, indicating which ones are inactive. If -c/--closed is specified, also list branches which have been marked closed (see :hg:`commit --close-branch`). Use the command :hg:`update` to switch to an existing branch. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions such as ``{branch}``. See also :hg:`help templates`. :active: Boolean. True if the branch is active. :closed: Boolean. True if the branch is closed. :current: Boolean. True if it is the current branch. Returns 0. """ opts = pycompat.byteskwargs(opts) revs = opts.get('rev') selectedbranches = None if revs: revs = scmutil.revrange(repo, revs) getbi = repo.revbranchcache().branchinfo selectedbranches = {getbi(r)[0] for r in revs} ui.pager('branches') fm = ui.formatter('branches', opts) hexfunc = fm.hexfunc allheads = set(repo.heads()) branches = [] for tag, heads, tip, isclosed in repo.branchmap().iterbranches(): if selectedbranches is not None and tag not in selectedbranches: continue isactive = False if not isclosed: openheads = set(repo.branchmap().iteropen(heads)) isactive = bool(openheads & allheads) branches.append((tag, repo[tip], isactive, not isclosed)) branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True) for tag, ctx, isactive, isopen in branches: if active and not isactive: continue if isactive: label = 'branches.active' notice = '' elif not isopen: if not closed: continue label = 'branches.closed' notice = _(' (closed)') else: label = 'branches.inactive' notice = _(' (inactive)') current = (tag == repo.dirstate.branch()) if current: label = 'branches.current' fm.startitem() fm.write('branch', '%s', tag, label=label) rev = ctx.rev() padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0) fmt = ' ' * padsize + ' %d:%s' fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()), label='log.changeset changeset.%s' % ctx.phasestr()) fm.context(ctx=ctx) fm.data(active=isactive, closed=not isopen, current=current) if not ui.quiet: fm.plain(notice) fm.plain('\n') fm.end() @command('bundle', [('f', 'force', None, _('run even when the destination is unrelated')), ('r', 'rev', [], _('a changeset intended to be added to the destination'), _('REV')), ('b', 'branch', [], _('a specific branch you would like to bundle'), _('BRANCH')), ('', 'base', [], _('a base changeset assumed to be available at the destination'), _('REV')), ('a', 'all', None, _('bundle all changesets in the repository')), ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')), ] + remoteopts, _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'), helpcategory=command.CATEGORY_IMPORT_EXPORT) def bundle(ui, repo, fname, dest=None, **opts): """create a bundle file Generate a bundle file containing data to be transferred to another repository. To create a bundle containing all changesets, use -a/--all (or --base null). Otherwise, hg assumes the destination will have all the nodes you specify with --base parameters. Otherwise, hg will assume the repository has all the nodes in destination, or default-push/default if no destination is specified, where destination is the repository you provide through DEST option. You can change bundle format with the -t/--type option. See :hg:`help bundlespec` for documentation on this format. By default, the most appropriate format is used and compression defaults to bzip2. The bundle file can then be transferred using conventional means and applied to another repository with the unbundle or pull command. This is useful when direct push and pull are not available or when exporting an entire repository is undesirable. Applying bundles preserves all changeset contents including permissions, copy/rename information, and revision history. Returns 0 on success, 1 if no changes found. """ opts = pycompat.byteskwargs(opts) revs = None if 'rev' in opts: revstrings = opts['rev'] revs = scmutil.revrange(repo, revstrings) if revstrings and not revs: raise error.Abort(_('no commits to bundle')) bundletype = opts.get('type', 'bzip2').lower() try: bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False) except error.UnsupportedBundleSpecification as e: raise error.Abort(pycompat.bytestr(e), hint=_("see 'hg help bundlespec' for supported " "values for --type")) cgversion = bundlespec.contentopts["cg.version"] # Packed bundles are a pseudo bundle format for now. if cgversion == 's1': raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'), hint=_("use 'hg debugcreatestreamclonebundle'")) if opts.get('all'): if dest: raise error.Abort(_("--all is incompatible with specifying " "a destination")) if opts.get('base'): ui.warn(_("ignoring --base because --all was specified\n")) base = [nullrev] else: base = scmutil.revrange(repo, opts.get('base')) if cgversion not in changegroup.supportedoutgoingversions(repo): raise error.Abort(_("repository does not support bundle version %s") % cgversion) if base: if dest: raise error.Abort(_("--base is incompatible with specifying " "a destination")) common = [repo[rev].node() for rev in base] heads = [repo[r].node() for r in revs] if revs else None outgoing = discovery.outgoing(repo, common, heads) else: dest = ui.expandpath(dest or 'default-push', dest or 'default') dest, branches = hg.parseurl(dest, opts.get('branch')) other = hg.peer(repo, opts, dest) revs = [repo[r].hex() for r in revs] revs, checkout = hg.addbranchrevs(repo, repo, branches, revs) heads = revs and pycompat.maplist(repo.lookup, revs) or revs outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=heads, force=opts.get('force'), portable=True) if not outgoing.missing: scmutil.nochangesfound(ui, repo, not base and outgoing.excluded) return 1 if cgversion == '01': #bundle1 bversion = 'HG10' + bundlespec.wirecompression bcompression = None elif cgversion in ('02', '03'): bversion = 'HG20' bcompression = bundlespec.wirecompression else: raise error.ProgrammingError( 'bundle: unexpected changegroup version %s' % cgversion) # TODO compression options should be derived from bundlespec parsing. # This is a temporary hack to allow adjusting bundle compression # level without a) formalizing the bundlespec changes to declare it # b) introducing a command flag. compopts = {} complevel = ui.configint('experimental', 'bundlecomplevel.' + bundlespec.compression) if complevel is None: complevel = ui.configint('experimental', 'bundlecomplevel') if complevel is not None: compopts['level'] = complevel # Allow overriding the bundling of obsmarker in phases through # configuration while we don't have a bundle version that include them if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'): bundlespec.contentopts['obsolescence'] = True if repo.ui.configbool('experimental', 'bundle-phases'): bundlespec.contentopts['phases'] = True bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing, bundlespec.contentopts, compression=bcompression, compopts=compopts) @command('cat', [('o', 'output', '', _('print output to file with formatted name'), _('FORMAT')), ('r', 'rev', '', _('print the given revision'), _('REV')), ('', 'decode', None, _('apply any matching decode filter')), ] + walkopts + formatteropts, _('[OPTION]... FILE...'), helpcategory=command.CATEGORY_FILE_CONTENTS, inferrepo=True, intents={INTENT_READONLY}) def cat(ui, repo, file1, *pats, **opts): """output the current or given revision of files Print the specified files as they were at the given revision. If no revision is given, the parent of the working directory is used. Output may be to a file, in which case the name of the file is given using a template string. See :hg:`help templates`. In addition to the common template keywords, the following formatting rules are supported: :``%%``: literal "%" character :``%s``: basename of file being printed :``%d``: dirname of file being printed, or '.' if in repository root :``%p``: root-relative path name of file being printed :``%H``: changeset hash (40 hexadecimal digits) :``%R``: changeset revision number :``%h``: short-form changeset hash (12 hexadecimal digits) :``%r``: zero-padded changeset revision number :``%b``: basename of the exporting repository :``\\``: literal "\\" character .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :data: String. File content. :path: String. Repository-absolute path of the file. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev) m = scmutil.match(ctx, (file1,) + pats, opts) fntemplate = opts.pop('output', '') if cmdutil.isstdiofilename(fntemplate): fntemplate = '' if fntemplate: fm = formatter.nullformatter(ui, 'cat', opts) else: ui.pager('cat') fm = ui.formatter('cat', opts) with fm: return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **pycompat.strkwargs(opts)) @command('clone', [('U', 'noupdate', None, _('the clone will include an empty working ' 'directory (only a repository)')), ('u', 'updaterev', '', _('revision, tag, or branch to check out'), _('REV')), ('r', 'rev', [], _('do not clone everything, but include this changeset' ' and its ancestors'), _('REV')), ('b', 'branch', [], _('do not clone everything, but include this branch\'s' ' changesets and their ancestors'), _('BRANCH')), ('', 'pull', None, _('use pull protocol to copy metadata')), ('', 'uncompressed', None, _('an alias to --stream (DEPRECATED)')), ('', 'stream', None, _('clone with minimal data processing')), ] + remoteopts, _('[OPTION]... SOURCE [DEST]'), helpcategory=command.CATEGORY_REPO_CREATION, helpbasic=True, norepo=True) def clone(ui, source, dest=None, **opts): """make a copy of an existing repository Create a copy of an existing repository in a new directory. If no destination directory name is specified, it defaults to the basename of the source. The location of the source is added to the new repository's ``.hg/hgrc`` file, as the default to be used for future pulls. Only local paths and ``ssh://`` URLs are supported as destinations. For ``ssh://`` destinations, no working directory or ``.hg/hgrc`` will be created on the remote side. If the source repository has a bookmark called '@' set, that revision will be checked out in the new repository by default. To check out a particular version, use -u/--update, or -U/--noupdate to create a clone with no working directory. To pull only a subset of changesets, specify one or more revisions identifiers with -r/--rev or branches with -b/--branch. The resulting clone will contain only the specified changesets and their ancestors. These options (or 'clone src#rev dest') imply --pull, even for local source repositories. In normal clone mode, the remote normalizes repository data into a common exchange format and the receiving end translates this data into its local storage format. --stream activates a different clone mode that essentially copies repository files from the remote with minimal data processing. This significantly reduces the CPU cost of a clone both remotely and locally. However, it often increases the transferred data size by 30-40%. This can result in substantially faster clones where I/O throughput is plentiful, especially for larger repositories. A side-effect of --stream clones is that storage settings and requirements on the remote are applied locally: a modern client may inherit legacy or inefficient storage used by the remote or a legacy Mercurial client may not be able to clone from a modern Mercurial remote. .. note:: Specifying a tag will include the tagged changeset but not the changeset containing the tag. .. container:: verbose For efficiency, hardlinks are used for cloning whenever the source and destination are on the same filesystem (note this applies only to the repository data, not to the working directory). Some filesystems, such as AFS, implement hardlinking incorrectly, but do not report errors. In these cases, use the --pull option to avoid hardlinking. Mercurial will update the working directory to the first applicable revision from this list: a) null if -U or the source repository has no changesets b) if -u . and the source repository is local, the first parent of the source repository's working directory c) the changeset specified with -u (if a branch name, this means the latest head of that branch) d) the changeset specified with -r e) the tipmost head specified with -b f) the tipmost head specified with the url#branch source syntax g) the revision marked with the '@' bookmark, if present h) the tipmost head of the default branch i) tip When cloning from servers that support it, Mercurial may fetch pre-generated data from a server-advertised URL or inline from the same stream. When this is done, hooks operating on incoming changesets and changegroups may fire more than once, once for each pre-generated bundle and as well as for any additional remaining data. In addition, if an error occurs, the repository may be rolled back to a partial clone. This behavior may change in future releases. See :hg:`help -e clonebundles` for more. Examples: - clone a remote repository to a new directory named hg/:: hg clone https://www.mercurial-scm.org/repo/hg/ - create a lightweight local clone:: hg clone project/ project-feature/ - clone from an absolute path on an ssh server (note double-slash):: hg clone ssh://user@server//home/projects/alpha/ - do a streaming clone while checking out a specified version:: hg clone --stream http://server/repo -u 1.5 - create a repository without changesets after a particular revision:: hg clone -r 04e544 experimental/ good/ - clone (and track) a particular named branch:: hg clone https://www.mercurial-scm.org/repo/hg/#stable See :hg:`help urls` for details on specifying URLs. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) if opts.get('noupdate') and opts.get('updaterev'): raise error.Abort(_("cannot specify both --noupdate and --updaterev")) # --include/--exclude can come from narrow or sparse. includepats, excludepats = None, None # hg.clone() differentiates between None and an empty set. So make sure # patterns are sets if narrow is requested without patterns. if opts.get('narrow'): includepats = set() excludepats = set() if opts.get('include'): includepats = narrowspec.parsepatterns(opts.get('include')) if opts.get('exclude'): excludepats = narrowspec.parsepatterns(opts.get('exclude')) r = hg.clone(ui, opts, source, dest, pull=opts.get('pull'), stream=opts.get('stream') or opts.get('uncompressed'), revs=opts.get('rev'), update=opts.get('updaterev') or not opts.get('noupdate'), branch=opts.get('branch'), shareopts=opts.get('shareopts'), storeincludepats=includepats, storeexcludepats=excludepats, depth=opts.get('depth') or None) return r is None @command('commit|ci', [('A', 'addremove', None, _('mark new/missing files as added/removed before committing')), ('', 'close-branch', None, _('mark a branch head as closed')), ('', 'amend', None, _('amend the parent of the working directory')), ('s', 'secret', None, _('use the secret phase for committing')), ('e', 'edit', None, _('invoke editor on commit messages')), ('i', 'interactive', None, _('use interactive mode')), ] + walkopts + commitopts + commitopts2 + subrepoopts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_COMMITTING, helpbasic=True, inferrepo=True) def commit(ui, repo, *pats, **opts): """commit the specified files or all outstanding changes Commit changes to the given files into the repository. Unlike a centralized SCM, this operation is a local operation. See :hg:`push` for a way to actively distribute your changes. If a list of files is omitted, all changes reported by :hg:`status` will be committed. If you are committing the result of a merge, do not provide any filenames or -I/-X filters. If no commit message is specified, Mercurial starts your configured editor where you can enter a message. In case your commit fails, you will find a backup of your message in ``.hg/last-message.txt``. The --close-branch flag can be used to mark the current branch head closed. When all heads of a branch are closed, the branch will be considered closed and no longer listed. The --amend flag can be used to amend the parent of the working directory with a new commit that contains the changes in the parent in addition to those currently reported by :hg:`status`, if there are any. The old commit is stored in a backup bundle in ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle` on how to restore it). Message, user and date are taken from the amended commit unless specified. When a message isn't specified on the command line, the editor will open with the message of the amended commit. It is not possible to amend public changesets (see :hg:`help phases`) or changesets that have children. See :hg:`help dates` for a list of formats valid for -d/--date. Returns 0 on success, 1 if nothing changed. .. container:: verbose Examples: - commit all files ending in .py:: hg commit --include "set:**.py" - commit all non-binary files:: hg commit --exclude "set:binary()" - amend the current commit and set the date to now:: hg commit --amend --date now """ with repo.wlock(), repo.lock(): return _docommit(ui, repo, *pats, **opts) def _docommit(ui, repo, *pats, **opts): if opts.get(r'interactive'): opts.pop(r'interactive') ret = cmdutil.dorecord(ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts) # ret can be 0 (no changes to record) or the value returned by # commit(), 1 if nothing changed or None on success. return 1 if ret == 0 else ret opts = pycompat.byteskwargs(opts) if opts.get('subrepos'): if opts.get('amend'): raise error.Abort(_('cannot amend with --subrepos')) # Let --subrepos on the command line override config setting. ui.setconfig('ui', 'commitsubrepos', True, 'commit') cmdutil.checkunfinished(repo, commit=True) branch = repo[None].branch() bheads = repo.branchheads(branch) extra = {} if opts.get('close_branch'): extra['close'] = '1' if repo['.'].closesbranch(): raise error.Abort(_('current revision is already a branch closing' ' head')) elif not bheads: raise error.Abort(_('branch "%s" has no heads to close') % branch) elif branch == repo['.'].branch() and repo['.'].node() not in bheads: raise error.Abort(_('can only close branch heads')) elif opts.get('amend'): if (repo['.'].p1().branch() != branch and repo['.'].p2().branch() != branch): raise error.Abort(_('can only close branch heads')) if opts.get('amend'): if ui.configbool('ui', 'commitsubrepos'): raise error.Abort(_('cannot amend with ui.commitsubrepos enabled')) old = repo['.'] rewriteutil.precheck(repo, [old.rev()], 'amend') # Currently histedit gets confused if an amend happens while histedit # is in progress. Since we have a checkunfinished command, we are # temporarily honoring it. # # Note: eventually this guard will be removed. Please do not expect # this behavior to remain. if not obsolete.isenabled(repo, obsolete.createmarkersopt): cmdutil.checkunfinished(repo) node = cmdutil.amend(ui, repo, old, extra, pats, opts) if node == old.node(): ui.status(_("nothing changed\n")) return 1 else: def commitfunc(ui, repo, message, match, opts): overrides = {} if opts.get('secret'): overrides[('phases', 'new-commit')] = 'secret' baseui = repo.baseui with baseui.configoverride(overrides, 'commit'): with ui.configoverride(overrides, 'commit'): editform = cmdutil.mergeeditform(repo[None], 'commit.normal') editor = cmdutil.getcommiteditor( editform=editform, **pycompat.strkwargs(opts)) return repo.commit(message, opts.get('user'), opts.get('date'), match, editor=editor, extra=extra) node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: stat = cmdutil.postcommitstatus(repo, pats, opts) if stat[3]: ui.status(_("nothing changed (%d missing files, see " "'hg status')\n") % len(stat[3])) else: ui.status(_("nothing changed\n")) return 1 cmdutil.commitstatus(repo, node, branch, bheads, opts) if not ui.quiet and ui.configbool('commands', 'commit.post-status'): status(ui, repo, modified=True, added=True, removed=True, deleted=True, unknown=True, subrepos=opts.get('subrepos')) @command('config|showconfig|debugconfig', [('u', 'untrusted', None, _('show untrusted configuration options')), ('e', 'edit', None, _('edit user config')), ('l', 'local', None, _('edit repository config')), ('g', 'global', None, _('edit global config'))] + formatteropts, _('[-u] [NAME]...'), helpcategory=command.CATEGORY_HELP, optionalrepo=True, intents={INTENT_READONLY}) def config(ui, repo, *values, **opts): """show combined config settings from all hgrc files With no arguments, print names and values of all config items. With one argument of the form section.name, print just the value of that config item. With multiple arguments, print names and values of all config items with matching section names or section.names. With --edit, start an editor on the user-level config file. With --global, edit the system-wide config file. With --local, edit the repository-level config file. With --debug, the source (filename and line number) is printed for each config item. See :hg:`help config` for more information about config files. .. container:: verbose Template: The following keywords are supported. See also :hg:`help templates`. :name: String. Config name. :source: String. Filename and line number where the item is defined. :value: String. Config value. Returns 0 on success, 1 if NAME does not exist. """ opts = pycompat.byteskwargs(opts) if opts.get('edit') or opts.get('local') or opts.get('global'): if opts.get('local') and opts.get('global'): raise error.Abort(_("can't use --local and --global together")) if opts.get('local'): if not repo: raise error.Abort(_("can't use --local outside a repository")) paths = [repo.vfs.join('hgrc')] elif opts.get('global'): paths = rcutil.systemrcpath() else: paths = rcutil.userrcpath() for f in paths: if os.path.exists(f): break else: if opts.get('global'): samplehgrc = uimod.samplehgrcs['global'] elif opts.get('local'): samplehgrc = uimod.samplehgrcs['local'] else: samplehgrc = uimod.samplehgrcs['user'] f = paths[0] fp = open(f, "wb") fp.write(util.tonativeeol(samplehgrc)) fp.close() editor = ui.geteditor() ui.system("%s \"%s\"" % (editor, f), onerr=error.Abort, errprefix=_("edit failed"), blockedtag='config_edit') return ui.pager('config') fm = ui.formatter('config', opts) for t, f in rcutil.rccomponents(): if t == 'path': ui.debug('read config from: %s\n' % f) elif t == 'items': for section, name, value, source in f: ui.debug('set config by: %s\n' % source) else: raise error.ProgrammingError('unknown rctype: %s' % t) untrusted = bool(opts.get('untrusted')) selsections = selentries = [] if values: selsections = [v for v in values if '.' not in v] selentries = [v for v in values if '.' in v] uniquesel = (len(selentries) == 1 and not selsections) selsections = set(selsections) selentries = set(selentries) matched = False for section, name, value in ui.walkconfig(untrusted=untrusted): source = ui.configsource(section, name, untrusted) value = pycompat.bytestr(value) if fm.isplain(): source = source or 'none' value = value.replace('\n', '\\n') entryname = section + '.' + name if values and not (section in selsections or entryname in selentries): continue fm.startitem() fm.condwrite(ui.debugflag, 'source', '%s: ', source) if uniquesel: fm.data(name=entryname) fm.write('value', '%s\n', value) else: fm.write('name value', '%s=%s\n', entryname, value) matched = True fm.end() if matched: return 0 return 1 @command('copy|cp', [('A', 'after', None, _('record a copy that has already occurred')), ('f', 'force', None, _('forcibly copy over an existing managed file')), ] + walkopts + dryrunopts, _('[OPTION]... [SOURCE]... DEST'), helpcategory=command.CATEGORY_FILE_CONTENTS) def copy(ui, repo, *pats, **opts): """mark files as copied for the next commit Mark dest as having copies of source files. If dest is a directory, copies are put in that directory. If dest is a file, the source must be a single file. By default, this command copies the contents of files as they exist in the working directory. If invoked with -A/--after, the operation is recorded, but no copying is performed. This command takes effect with the next commit. To undo a copy before that, see :hg:`revert`. Returns 0 on success, 1 if errors are encountered. """ opts = pycompat.byteskwargs(opts) with repo.wlock(False): return cmdutil.copy(ui, repo, pats, opts) @command( 'debugcommands', [], _('[COMMAND]'), helpcategory=command.CATEGORY_HELP, norepo=True) def debugcommands(ui, cmd='', *args): """list all available commands and options""" for cmd, vals in sorted(table.iteritems()): cmd = cmd.split('|')[0] opts = ', '.join([i[1] for i in vals[1]]) ui.write('%s: %s\n' % (cmd, opts)) @command('debugcomplete', [('o', 'options', None, _('show the command options'))], _('[-o] CMD'), helpcategory=command.CATEGORY_HELP, norepo=True) def debugcomplete(ui, cmd='', **opts): """returns the completion list associated with the given command""" if opts.get(r'options'): options = [] otables = [globalopts] if cmd: aliases, entry = cmdutil.findcmd(cmd, table, False) otables.append(entry[1]) for t in otables: for o in t: if "(DEPRECATED)" in o[3]: continue if o[0]: options.append('-%s' % o[0]) options.append('--%s' % o[1]) ui.write("%s\n" % "\n".join(options)) return cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table) if ui.verbose: cmdlist = [' '.join(c[0]) for c in cmdlist.values()] ui.write("%s\n" % "\n".join(sorted(cmdlist))) @command('diff', [('r', 'rev', [], _('revision'), _('REV')), ('c', 'change', '', _('change made by revision'), _('REV')) ] + diffopts + diffopts2 + walkopts + subrepoopts, _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'), helpcategory=command.CATEGORY_FILE_CONTENTS, helpbasic=True, inferrepo=True, intents={INTENT_READONLY}) def diff(ui, repo, *pats, **opts): """diff repository (or selected files) Show differences between revisions for the specified files. Differences between files are shown using the unified diff format. .. note:: :hg:`diff` may generate unexpected results for merges, as it will default to comparing against the working directory's first parent changeset if no revisions are specified. When two revision arguments are given, then changes are shown between those revisions. If only one revision is specified then that revision is compared to the working directory, and, when no revisions are specified, the working directory files are compared to its first parent. Alternatively you can specify -c/--change with a revision to see the changes in that changeset relative to its first parent. Without the -a/--text option, diff will avoid generating diffs of files it detects as binary. With -a, diff will generate a diff anyway, probably with undesirable results. Use the -g/--git option to generate diffs in the git extended diff format. For more information, read :hg:`help diffs`. .. container:: verbose Examples: - compare a file in the current working directory to its parent:: hg diff foo.c - compare two historical versions of a directory, with rename info:: hg diff --git -r 1.0:1.2 lib/ - get change stats relative to the last change on some date:: hg diff --stat -r "date('may 2')" - diff all newly-added files that contain a keyword:: hg diff "set:added() and grep(GNU)" - compare a revision and its parents:: hg diff -c 9353 # compare against first parent hg diff -r 9353^:9353 # same using revset syntax hg diff -r 9353^2:9353 # compare against the second parent Returns 0 on success. """ opts = pycompat.byteskwargs(opts) revs = opts.get('rev') change = opts.get('change') stat = opts.get('stat') reverse = opts.get('reverse') if revs and change: msg = _('cannot specify --rev and --change at the same time') raise error.Abort(msg) elif change: repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn') ctx2 = scmutil.revsingle(repo, change, None) ctx1 = ctx2.p1() else: repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn') ctx1, ctx2 = scmutil.revpair(repo, revs) node1, node2 = ctx1.node(), ctx2.node() if reverse: node1, node2 = node2, node1 diffopts = patch.diffallopts(ui, opts) m = scmutil.match(ctx2, pats, opts) m = repo.narrowmatch(m) ui.pager('diff') logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat, listsubrepos=opts.get('subrepos'), root=opts.get('root')) @command('export', [('B', 'bookmark', '', _('export changes only reachable by given bookmark'), _('BOOKMARK')), ('o', 'output', '', _('print output to file with formatted name'), _('FORMAT')), ('', 'switch-parent', None, _('diff against the second parent')), ('r', 'rev', [], _('revisions to export'), _('REV')), ] + diffopts + formatteropts, _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), helpcategory=command.CATEGORY_IMPORT_EXPORT, helpbasic=True, intents={INTENT_READONLY}) def export(ui, repo, *changesets, **opts): """dump the header and diffs for one or more changesets Print the changeset header and diffs for one or more revisions. If no revision is given, the parent of the working directory is used. The information shown in the changeset header is: author, date, branch name (if non-default), changeset hash, parent(s) and commit comment. .. note:: :hg:`export` may generate unexpected diff output for merge changesets, as it will compare the merge changeset against its first parent only. Output may be to a file, in which case the name of the file is given using a template string. See :hg:`help templates`. In addition to the common template keywords, the following formatting rules are supported: :``%%``: literal "%" character :``%H``: changeset hash (40 hexadecimal digits) :``%N``: number of patches being generated :``%R``: changeset revision number :``%b``: basename of the exporting repository :``%h``: short-form changeset hash (12 hexadecimal digits) :``%m``: first line of the commit message (only alphanumeric characters) :``%n``: zero-padded sequence number, starting at 1 :``%r``: zero-padded changeset revision number :``\\``: literal "\\" character Without the -a/--text option, export will avoid generating diffs of files it detects as binary. With -a, export will generate a diff anyway, probably with undesirable results. With -B/--bookmark changesets reachable by the given bookmark are selected. Use the -g/--git option to generate diffs in the git extended diff format. See :hg:`help diffs` for more information. With the --switch-parent option, the diff will be against the second parent. It can be useful to review a merge. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :diff: String. Diff content. :parents: List of strings. Parent nodes of the changeset. Examples: - use export and import to transplant a bugfix to the current branch:: hg export -r 9353 | hg import - - export all the changesets between two revisions to a file with rename information:: hg export --git -r 123:150 > changes.txt - split outgoing changes into a series of patches with descriptive names:: hg export -r "outgoing()" -o "%n-%m.patch" Returns 0 on success. """ opts = pycompat.byteskwargs(opts) bookmark = opts.get('bookmark') changesets += tuple(opts.get('rev', [])) if bookmark and changesets: raise error.Abort(_("-r and -B are mutually exclusive")) if bookmark: if bookmark not in repo._bookmarks: raise error.Abort(_("bookmark '%s' not found") % bookmark) revs = scmutil.bookmarkrevs(repo, bookmark) else: if not changesets: changesets = ['.'] repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn') revs = scmutil.revrange(repo, changesets) if not revs: raise error.Abort(_("export requires at least one changeset")) if len(revs) > 1: ui.note(_('exporting patches:\n')) else: ui.note(_('exporting patch:\n')) fntemplate = opts.get('output') if cmdutil.isstdiofilename(fntemplate): fntemplate = '' if fntemplate: fm = formatter.nullformatter(ui, 'export', opts) else: ui.pager('export') fm = ui.formatter('export', opts) with fm: cmdutil.export(repo, revs, fm, fntemplate=fntemplate, switch_parent=opts.get('switch_parent'), opts=patch.diffallopts(ui, opts)) @command('files', [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')), ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), ] + walkopts + formatteropts + subrepoopts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, intents={INTENT_READONLY}) def files(ui, repo, *pats, **opts): """list tracked files Print files under Mercurial control in the working directory or specified revision for given files (excluding removed files). Files can be specified as filenames or filesets. If no files are given to match, this command prints the names of all files under Mercurial control. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :flags: String. Character denoting file's symlink and executable bits. :path: String. Repository-absolute path of the file. :size: Integer. Size of the file in bytes. Examples: - list all files under the current directory:: hg files . - shows sizes and flags for current revision:: hg files -vr . - list all files named README:: hg files -I "**/README" - list all binary files:: hg files "set:binary()" - find files containing a regular expression:: hg files "set:grep('bob')" - search tracked file contents with xargs and grep:: hg files -0 | xargs -0 grep foo See :hg:`help patterns` and :hg:`help filesets` for more information on specifying file patterns. Returns 0 if a match is found, 1 otherwise. """ opts = pycompat.byteskwargs(opts) rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev, None) end = '\n' if opts.get('print0'): end = '\0' fmt = '%s' + end m = scmutil.match(ctx, pats, opts) ui.pager('files') uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True) with ui.formatter('files', opts) as fm: return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt, opts.get('subrepos')) @command( 'forget', [('i', 'interactive', None, _('use interactive mode')), ] + walkopts + dryrunopts, _('[OPTION]... FILE...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, helpbasic=True, inferrepo=True) def forget(ui, repo, *pats, **opts): """forget the specified files on the next commit Mark the specified files so they will no longer be tracked after the next commit. This only removes files from the current branch, not from the entire project history, and it does not delete them from the working directory. To delete the file from the working directory, see :hg:`remove`. To undo a forget before the next commit, see :hg:`add`. .. container:: verbose Examples: - forget newly-added binary files:: hg forget "set:added() and binary()" - forget files that would be excluded by .hgignore:: hg forget "set:hgignore()" Returns 0 on success. """ opts = pycompat.byteskwargs(opts) if not pats: raise error.Abort(_('no files specified')) m = scmutil.match(repo[None], pats, opts) dryrun, interactive = opts.get('dry_run'), opts.get('interactive') uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn, explicitonly=False, dryrun=dryrun, interactive=interactive)[0] return rejected and 1 or 0 @command( 'graft', [('r', 'rev', [], _('revisions to graft'), _('REV')), ('', 'base', '', _('base revision when doing the graft merge (ADVANCED)'), _('REV')), ('c', 'continue', False, _('resume interrupted graft')), ('', 'stop', False, _('stop interrupted graft')), ('', 'abort', False, _('abort interrupted graft')), ('e', 'edit', False, _('invoke editor on commit messages')), ('', 'log', None, _('append graft info to log message')), ('', 'no-commit', None, _("don't commit, just apply the changes in working directory")), ('f', 'force', False, _('force graft')), ('D', 'currentdate', False, _('record the current date as commit date')), ('U', 'currentuser', False, _('record the current user as committer'))] + commitopts2 + mergetoolopts + dryrunopts, _('[OPTION]... [-r REV]... REV...'), helpcategory=command.CATEGORY_CHANGE_MANAGEMENT) def graft(ui, repo, *revs, **opts): '''copy changes from other branches onto the current branch This command uses Mercurial's merge logic to copy individual changes from other branches without merging branches in the history graph. This is sometimes known as 'backporting' or 'cherry-picking'. By default, graft will copy user, date, and description from the source changesets. Changesets that are ancestors of the current revision, that have already been grafted, or that are merges will be skipped. If --log is specified, log messages will have a comment appended of the form:: (grafted from CHANGESETHASH) If --force is specified, revisions will be grafted even if they are already ancestors of, or have been grafted to, the destination. This is useful when the revisions have since been backed out. If a graft merge results in conflicts, the graft process is interrupted so that the current merge can be manually resolved. Once all conflicts are addressed, the graft process can be continued with the -c/--continue option. The -c/--continue option reapplies all the earlier options. .. container:: verbose The --base option exposes more of how graft internally uses merge with a custom base revision. --base can be used to specify another ancestor than the first and only parent. The command:: hg graft -r 345 --base 234 is thus pretty much the same as:: hg diff -r 234 -r 345 | hg import but using merge to resolve conflicts and track moved files. The result of a merge can thus be backported as a single commit by specifying one of the merge parents as base, and thus effectively grafting the changes from the other side. It is also possible to collapse multiple changesets and clean up history by specifying another ancestor as base, much like rebase --collapse --keep. The commit message can be tweaked after the fact using commit --amend . For using non-ancestors as the base to backout changes, see the backout command and the hidden --parent option. .. container:: verbose Examples: - copy a single change to the stable branch and edit its description:: hg update stable hg graft --edit 9393 - graft a range of changesets with one exception, updating dates:: hg graft -D "2085::2093 and not 2091" - continue a graft after resolving conflicts:: hg graft -c - show the source of a grafted changeset:: hg log --debug -r . - show revisions sorted by date:: hg log -r "sort(all(), date)" - backport the result of a merge as a single commit:: hg graft -r 123 --base 123^ - land a feature branch as one changeset:: hg up -cr default hg graft -r featureX --base "ancestor('featureX', 'default')" See :hg:`help revisions` for more about specifying revisions. Returns 0 on successful completion. ''' with repo.wlock(): return _dograft(ui, repo, *revs, **opts) def _dograft(ui, repo, *revs, **opts): opts = pycompat.byteskwargs(opts) if revs and opts.get('rev'): ui.warn(_('warning: inconsistent use of --rev might give unexpected ' 'revision ordering!\n')) revs = list(revs) revs.extend(opts.get('rev')) basectx = None if opts.get('base'): basectx = scmutil.revsingle(repo, opts['base'], None) # a dict of data to be stored in state file statedata = {} # list of new nodes created by ongoing graft statedata['newnodes'] = [] if opts.get('user') and opts.get('currentuser'): raise error.Abort(_('--user and --currentuser are mutually exclusive')) if opts.get('date') and opts.get('currentdate'): raise error.Abort(_('--date and --currentdate are mutually exclusive')) if not opts.get('user') and opts.get('currentuser'): opts['user'] = ui.username() if not opts.get('date') and opts.get('currentdate'): opts['date'] = "%d %d" % dateutil.makedate() editor = cmdutil.getcommiteditor(editform='graft', **pycompat.strkwargs(opts)) cont = False if opts.get('no_commit'): if opts.get('edit'): raise error.Abort(_("cannot specify --no-commit and " "--edit together")) if opts.get('currentuser'): raise error.Abort(_("cannot specify --no-commit and " "--currentuser together")) if opts.get('currentdate'): raise error.Abort(_("cannot specify --no-commit and " "--currentdate together")) if opts.get('log'): raise error.Abort(_("cannot specify --no-commit and " "--log together")) graftstate = statemod.cmdstate(repo, 'graftstate') if opts.get('stop'): if opts.get('continue'): raise error.Abort(_("cannot use '--continue' and " "'--stop' together")) if opts.get('abort'): raise error.Abort(_("cannot use '--abort' and '--stop' together")) if any((opts.get('edit'), opts.get('log'), opts.get('user'), opts.get('date'), opts.get('currentdate'), opts.get('currentuser'), opts.get('rev'))): raise error.Abort(_("cannot specify any other flag with '--stop'")) return _stopgraft(ui, repo, graftstate) elif opts.get('abort'): if opts.get('continue'): raise error.Abort(_("cannot use '--continue' and " "'--abort' together")) if any((opts.get('edit'), opts.get('log'), opts.get('user'), opts.get('date'), opts.get('currentdate'), opts.get('currentuser'), opts.get('rev'))): raise error.Abort(_("cannot specify any other flag with '--abort'")) return _abortgraft(ui, repo, graftstate) elif opts.get('continue'): cont = True if revs: raise error.Abort(_("can't specify --continue and revisions")) # read in unfinished revisions if graftstate.exists(): statedata = _readgraftstate(repo, graftstate) if statedata.get('date'): opts['date'] = statedata['date'] if statedata.get('user'): opts['user'] = statedata['user'] if statedata.get('log'): opts['log'] = True if statedata.get('no_commit'): opts['no_commit'] = statedata.get('no_commit') nodes = statedata['nodes'] revs = [repo[node].rev() for node in nodes] else: cmdutil.wrongtooltocontinue(repo, _('graft')) else: if not revs: raise error.Abort(_('no revisions specified')) cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) revs = scmutil.revrange(repo, revs) skipped = set() if basectx is None: # check for merges for rev in repo.revs('%ld and merge()', revs): ui.warn(_('skipping ungraftable merge revision %d\n') % rev) skipped.add(rev) revs = [r for r in revs if r not in skipped] if not revs: return -1 if basectx is not None and len(revs) != 1: raise error.Abort(_('only one revision allowed with --base ')) # Don't check in the --continue case, in effect retaining --force across # --continues. That's because without --force, any revisions we decided to # skip would have been filtered out here, so they wouldn't have made their # way to the graftstate. With --force, any revisions we would have otherwise # skipped would not have been filtered out, and if they hadn't been applied # already, they'd have been in the graftstate. if not (cont or opts.get('force')) and basectx is None: # check for ancestors of dest branch crev = repo['.'].rev() ancestors = repo.changelog.ancestors([crev], inclusive=True) # XXX make this lazy in the future # don't mutate while iterating, create a copy for rev in list(revs): if rev in ancestors: ui.warn(_('skipping ancestor revision %d:%s\n') % (rev, repo[rev])) # XXX remove on list is slow revs.remove(rev) if not revs: return -1 # analyze revs for earlier grafts ids = {} for ctx in repo.set("%ld", revs): ids[ctx.hex()] = ctx.rev() n = ctx.extra().get('source') if n: ids[n] = ctx.rev() # check ancestors for earlier grafts ui.debug('scanning for duplicate grafts\n') # The only changesets we can be sure doesn't contain grafts of any # revs, are the ones that are common ancestors of *all* revs: for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs): ctx = repo[rev] n = ctx.extra().get('source') if n in ids: try: r = repo[n].rev() except error.RepoLookupError: r = None if r in revs: ui.warn(_('skipping revision %d:%s ' '(already grafted to %d:%s)\n') % (r, repo[r], rev, ctx)) revs.remove(r) elif ids[n] in revs: if r is None: ui.warn(_('skipping already grafted revision %d:%s ' '(%d:%s also has unknown origin %s)\n') % (ids[n], repo[ids[n]], rev, ctx, n[:12])) else: ui.warn(_('skipping already grafted revision %d:%s ' '(%d:%s also has origin %d:%s)\n') % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])) revs.remove(ids[n]) elif ctx.hex() in ids: r = ids[ctx.hex()] if r in revs: ui.warn(_('skipping already grafted revision %d:%s ' '(was grafted from %d:%s)\n') % (r, repo[r], rev, ctx)) revs.remove(r) if not revs: return -1 if opts.get('no_commit'): statedata['no_commit'] = True for pos, ctx in enumerate(repo.set("%ld", revs)): desc = '%d:%s "%s"' % (ctx.rev(), ctx, ctx.description().split('\n', 1)[0]) names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) if names: desc += ' (%s)' % ' '.join(names) ui.status(_('grafting %s\n') % desc) if opts.get('dry_run'): continue source = ctx.extra().get('source') extra = {} if source: extra['source'] = source extra['intermediate-source'] = ctx.hex() else: extra['source'] = ctx.hex() user = ctx.user() if opts.get('user'): user = opts['user'] statedata['user'] = user date = ctx.date() if opts.get('date'): date = opts['date'] statedata['date'] = date message = ctx.description() if opts.get('log'): message += '\n(grafted from %s)' % ctx.hex() statedata['log'] = True # we don't merge the first commit when continuing if not cont: # perform the graft merge with p1(rev) as 'ancestor' overrides = {('ui', 'forcemerge'): opts.get('tool', '')} base = ctx.p1() if basectx is None else basectx with ui.configoverride(overrides, 'graft'): stats = mergemod.graft(repo, ctx, base, ['local', 'graft']) # report any conflicts if stats.unresolvedcount > 0: # write out state for --continue nodes = [repo[rev].hex() for rev in revs[pos:]] statedata['nodes'] = nodes stateversion = 1 graftstate.save(stateversion, statedata) hint = _("use 'hg resolve' and 'hg graft --continue'") raise error.Abort( _("unresolved conflicts, can't continue"), hint=hint) else: cont = False # commit if --no-commit is false if not opts.get('no_commit'): node = repo.commit(text=message, user=user, date=date, extra=extra, editor=editor) if node is None: ui.warn( _('note: graft of %d:%s created no changes to commit\n') % (ctx.rev(), ctx)) # checking that newnodes exist because old state files won't have it elif statedata.get('newnodes') is not None: statedata['newnodes'].append(node) # remove state when we complete successfully if not opts.get('dry_run'): graftstate.delete() return 0 def _abortgraft(ui, repo, graftstate): """abort the interrupted graft and rollbacks to the state before interrupted graft""" if not graftstate.exists(): raise error.Abort(_("no interrupted graft to abort")) statedata = _readgraftstate(repo, graftstate) newnodes = statedata.get('newnodes') if newnodes is None: # and old graft state which does not have all the data required to abort # the graft raise error.Abort(_("cannot abort using an old graftstate")) # changeset from which graft operation was started if len(newnodes) > 0: startctx = repo[newnodes[0]].p1() else: startctx = repo['.'] # whether to strip or not cleanup = False if newnodes: newnodes = [repo[r].rev() for r in newnodes] cleanup = True # checking that none of the newnodes turned public or is public immutable = [c for c in newnodes if not repo[c].mutable()] if immutable: repo.ui.warn(_("cannot clean up public changesets %s\n") % ', '.join(bytes(repo[r]) for r in immutable), hint=_("see 'hg help phases' for details")) cleanup = False # checking that no new nodes are created on top of grafted revs desc = set(repo.changelog.descendants(newnodes)) if desc - set(newnodes): repo.ui.warn(_("new changesets detected on destination " "branch, can't strip\n")) cleanup = False if cleanup: with repo.wlock(), repo.lock(): hg.updaterepo(repo, startctx.node(), overwrite=True) # stripping the new nodes created strippoints = [c.node() for c in repo.set("roots(%ld)", newnodes)] repair.strip(repo.ui, repo, strippoints, backup=False) if not cleanup: # we don't update to the startnode if we can't strip startctx = repo['.'] hg.updaterepo(repo, startctx.node(), overwrite=True) ui.status(_("graft aborted\n")) ui.status(_("working directory is now at %s\n") % startctx.hex()[:12]) graftstate.delete() return 0 def _readgraftstate(repo, graftstate): """read the graft state file and return a dict of the data stored in it""" try: return graftstate.read() except error.CorruptedState: nodes = repo.vfs.read('graftstate').splitlines() return {'nodes': nodes} def _stopgraft(ui, repo, graftstate): """stop the interrupted graft""" if not graftstate.exists(): raise error.Abort(_("no interrupted graft found")) pctx = repo['.'] hg.updaterepo(repo, pctx.node(), overwrite=True) graftstate.delete() ui.status(_("stopped the interrupted graft\n")) ui.status(_("working directory is now at %s\n") % pctx.hex()[:12]) return 0 @command('grep', [('0', 'print0', None, _('end fields with NUL')), ('', 'all', None, _('print all revisions that match (DEPRECATED) ')), ('', 'diff', None, _('print all revisions when the term was introduced ' 'or removed')), ('a', 'text', None, _('treat all files as text')), ('f', 'follow', None, _('follow changeset history,' ' or file history across copies and renames')), ('i', 'ignore-case', None, _('ignore case when matching')), ('l', 'files-with-matches', None, _('print only filenames and revisions that match')), ('n', 'line-number', None, _('print matching line numbers')), ('r', 'rev', [], _('only search files changed within revision range'), _('REV')), ('', 'all-files', None, _('include all files in the changeset while grepping (EXPERIMENTAL)')), ('u', 'user', None, _('list the author (long with -v)')), ('d', 'date', None, _('list the date (short with -q)')), ] + formatteropts + walkopts, _('[OPTION]... PATTERN [FILE]...'), helpcategory=command.CATEGORY_FILE_CONTENTS, inferrepo=True, intents={INTENT_READONLY}) def grep(ui, repo, pattern, *pats, **opts): """search revision history for a pattern in specified files Search revision history for a regular expression in the specified files or the entire project. By default, grep prints the most recent revision number for each file in which it finds a match. To get it to print every revision that contains a change in match status ("-" for a match that becomes a non-match, or "+" for a non-match that becomes a match), use the --diff flag. PATTERN can be any Python (roughly Perl-compatible) regular expression. If no FILEs are specified (and -f/--follow isn't set), all files in the repository are searched, including those that don't exist in the current branch or have been deleted in a prior changeset. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :change: String. Character denoting insertion ``+`` or removal ``-``. Available if ``--diff`` is specified. :lineno: Integer. Line number of the match. :path: String. Repository-absolute path of the file. :texts: List of text chunks. And each entry of ``{texts}`` provides the following sub-keywords. :matched: Boolean. True if the chunk matches the specified pattern. :text: String. Chunk content. See :hg:`help templates.operators` for the list expansion syntax. Returns 0 if a match is found, 1 otherwise. """ opts = pycompat.byteskwargs(opts) diff = opts.get('all') or opts.get('diff') all_files = opts.get('all_files') if diff and opts.get('all_files'): raise error.Abort(_('--diff and --all-files are mutually exclusive')) # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working if opts.get('all_files') is None and not opts.get('rev') and not diff: # experimental config: commands.grep.all-files opts['all_files'] = ui.configbool('commands', 'grep.all-files') plaingrep = opts.get('all_files') and not opts.get('rev') if plaingrep: opts['rev'] = ['wdir()'] reflags = re.M if opts.get('ignore_case'): reflags |= re.I try: regexp = util.re.compile(pattern, reflags) except re.error as inst: ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)) return 1 sep, eol = ':', '\n' if opts.get('print0'): sep = eol = '\0' getfile = util.lrucachefunc(repo.file) def matchlines(body): begin = 0 linenum = 0 while begin < len(body): match = regexp.search(body, begin) if not match: break mstart, mend = match.span() linenum += body.count('\n', begin, mstart) + 1 lstart = body.rfind('\n', begin, mstart) + 1 or begin begin = body.find('\n', mend) + 1 or len(body) + 1 lend = begin - 1 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend] class linestate(object): def __init__(self, line, linenum, colstart, colend): self.line = line self.linenum = linenum self.colstart = colstart self.colend = colend def __hash__(self): return hash((self.linenum, self.line)) def __eq__(self, other): return self.line == other.line def findpos(self): """Iterate all (start, end) indices of matches""" yield self.colstart, self.colend p = self.colend while p < len(self.line): m = regexp.search(self.line, p) if not m: break yield m.span() p = m.end() matches = {} copies = {} def grepbody(fn, rev, body): matches[rev].setdefault(fn, []) m = matches[rev][fn] for lnum, cstart, cend, line in matchlines(body): s = linestate(line, lnum, cstart, cend) m.append(s) def difflinestates(a, b): sm = difflib.SequenceMatcher(None, a, b) for tag, alo, ahi, blo, bhi in sm.get_opcodes(): if tag == r'insert': for i in pycompat.xrange(blo, bhi): yield ('+', b[i]) elif tag == r'delete': for i in pycompat.xrange(alo, ahi): yield ('-', a[i]) elif tag == r'replace': for i in pycompat.xrange(alo, ahi): yield ('-', a[i]) for i in pycompat.xrange(blo, bhi): yield ('+', b[i]) uipathfn = scmutil.getuipathfn(repo) def display(fm, fn, ctx, pstates, states): rev = scmutil.intrev(ctx) if fm.isplain(): formatuser = ui.shortuser else: formatuser = pycompat.bytestr if ui.quiet: datefmt = '%Y-%m-%d' else: datefmt = '%a %b %d %H:%M:%S %Y %1%2' found = False @util.cachefunc def binary(): flog = getfile(fn) try: return stringutil.binary(flog.read(ctx.filenode(fn))) except error.WdirUnsupported: return ctx[fn].isbinary() fieldnamemap = {'linenumber': 'lineno'} if diff: iter = difflinestates(pstates, states) else: iter = [('', l) for l in states] for change, l in iter: fm.startitem() fm.context(ctx=ctx) fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn) fm.plain(uipathfn(fn), label='grep.filename') cols = [ ('rev', '%d', rev, not plaingrep, ''), ('linenumber', '%d', l.linenum, opts.get('line_number'), ''), ] if diff: cols.append( ('change', '%s', change, True, 'grep.inserted ' if change == '+' else 'grep.deleted ') ) cols.extend([ ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''), ('date', '%s', fm.formatdate(ctx.date(), datefmt), opts.get('date'), ''), ]) for name, fmt, data, cond, extra_label in cols: if cond: fm.plain(sep, label='grep.sep') field = fieldnamemap.get(name, name) label = extra_label + ('grep.%s' % name) fm.condwrite(cond, field, fmt, data, label=label) if not opts.get('files_with_matches'): fm.plain(sep, label='grep.sep') if not opts.get('text') and binary(): fm.plain(_(" Binary file matches")) else: displaymatches(fm.nested('texts', tmpl='{text}'), l) fm.plain(eol) found = True if opts.get('files_with_matches'): break return found def displaymatches(fm, l): p = 0 for s, e in l.findpos(): if p < s: fm.startitem() fm.write('text', '%s', l.line[p:s]) fm.data(matched=False) fm.startitem() fm.write('text', '%s', l.line[s:e], label='grep.match') fm.data(matched=True) p = e if p < len(l.line): fm.startitem() fm.write('text', '%s', l.line[p:]) fm.data(matched=False) fm.end() skip = set() revfiles = {} match = scmutil.match(repo[None], pats, opts) found = False follow = opts.get('follow') getrenamed = scmutil.getrenamedfn(repo) def prep(ctx, fns): rev = ctx.rev() pctx = ctx.p1() parent = pctx.rev() matches.setdefault(rev, {}) matches.setdefault(parent, {}) files = revfiles.setdefault(rev, []) for fn in fns: flog = getfile(fn) try: fnode = ctx.filenode(fn) except error.LookupError: continue copy = None if follow: copy = getrenamed(fn, rev) if copy: copies.setdefault(rev, {})[fn] = copy if fn in skip: skip.add(copy) if fn in skip: continue files.append(fn) if fn not in matches[rev]: try: content = flog.read(fnode) except error.WdirUnsupported: content = ctx[fn].data() grepbody(fn, rev, content) pfn = copy or fn if pfn not in matches[parent]: try: fnode = pctx.filenode(pfn) grepbody(pfn, parent, flog.read(fnode)) except error.LookupError: pass ui.pager('grep') fm = ui.formatter('grep', opts) for ctx in cmdutil.walkchangerevs(repo, match, opts, prep): rev = ctx.rev() parent = ctx.p1().rev() for fn in sorted(revfiles.get(rev, [])): states = matches[rev][fn] copy = copies.get(rev, {}).get(fn) if fn in skip: if copy: skip.add(copy) continue pstates = matches.get(parent, {}).get(copy or fn, []) if pstates or states: r = display(fm, fn, ctx, pstates, states) found = found or r if r and not diff and not all_files: skip.add(fn) if copy: skip.add(copy) del revfiles[rev] # We will keep the matches dict for the duration of the window # clear the matches dict once the window is over if not revfiles: matches.clear() fm.end() return not found @command('heads', [('r', 'rev', '', _('show only heads which are descendants of STARTREV'), _('STARTREV')), ('t', 'topo', False, _('show topological heads only')), ('a', 'active', False, _('show active branchheads only (DEPRECATED)')), ('c', 'closed', False, _('show normal and closed branch heads')), ] + templateopts, _('[-ct] [-r STARTREV] [REV]...'), helpcategory=command.CATEGORY_CHANGE_NAVIGATION, intents={INTENT_READONLY}) def heads(ui, repo, *branchrevs, **opts): """show branch heads With no arguments, show all open branch heads in the repository. Branch heads are changesets that have no descendants on the same branch. They are where development generally takes place and are the usual targets for update and merge operations. If one or more REVs are given, only open branch heads on the branches associated with the specified changesets are shown. This means that you can use :hg:`heads .` to see the heads on the currently checked-out branch. If -c/--closed is specified, also show branch heads marked closed (see :hg:`commit --close-branch`). If STARTREV is specified, only those heads that are descendants of STARTREV will be displayed. If -t/--topo is specified, named branch mechanics will be ignored and only topological heads (changesets with no children) will be shown. Returns 0 if matching heads are found, 1 if not. """ opts = pycompat.byteskwargs(opts) start = None rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') start = scmutil.revsingle(repo, rev, None).node() if opts.get('topo'): heads = [repo[h] for h in repo.heads(start)] else: heads = [] for branch in repo.branchmap(): heads += repo.branchheads(branch, start, opts.get('closed')) heads = [repo[h] for h in heads] if branchrevs: branches = set(repo[r].branch() for r in scmutil.revrange(repo, branchrevs)) heads = [h for h in heads if h.branch() in branches] if opts.get('active') and branchrevs: dagheads = repo.heads(start) heads = [h for h in heads if h.node() in dagheads] if branchrevs: haveheads = set(h.branch() for h in heads) if branches - haveheads: headless = ', '.join(b for b in branches - haveheads) msg = _('no open branch heads found on branches %s') if opts.get('rev'): msg += _(' (started at %s)') % opts['rev'] ui.warn((msg + '\n') % headless) if not heads: return 1 ui.pager('heads') heads = sorted(heads, key=lambda x: -x.rev()) displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for ctx in heads: displayer.show(ctx) displayer.close() @command('help', [('e', 'extension', None, _('show only help for extensions')), ('c', 'command', None, _('show only help for commands')), ('k', 'keyword', None, _('show topics matching keyword')), ('s', 'system', [], _('show help for specific platform(s)'), _('PLATFORM')), ], _('[-eck] [-s PLATFORM] [TOPIC]'), helpcategory=command.CATEGORY_HELP, norepo=True, intents={INTENT_READONLY}) def help_(ui, name=None, **opts): """show help for a given topic or a help overview With no arguments, print a list of commands with short help messages. Given a topic, extension, or command name, print help for that topic. Returns 0 if successful. """ keep = opts.get(r'system') or [] if len(keep) == 0: if pycompat.sysplatform.startswith('win'): keep.append('windows') elif pycompat.sysplatform == 'OpenVMS': keep.append('vms') elif pycompat.sysplatform == 'plan9': keep.append('plan9') else: keep.append('unix') keep.append(pycompat.sysplatform.lower()) if ui.verbose: keep.append('verbose') commands = sys.modules[__name__] formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts) ui.pager('help') ui.write(formatted) @command('identify|id', [('r', 'rev', '', _('identify the specified revision'), _('REV')), ('n', 'num', None, _('show local revision number')), ('i', 'id', None, _('show global revision id')), ('b', 'branch', None, _('show branch')), ('t', 'tags', None, _('show tags')), ('B', 'bookmarks', None, _('show bookmarks')), ] + remoteopts + formatteropts, _('[-nibtB] [-r REV] [SOURCE]'), helpcategory=command.CATEGORY_CHANGE_NAVIGATION, optionalrepo=True, intents={INTENT_READONLY}) def identify(ui, repo, source=None, rev=None, num=None, id=None, branch=None, tags=None, bookmarks=None, **opts): """identify the working directory or specified revision Print a summary identifying the repository state at REV using one or two parent hash identifiers, followed by a "+" if the working directory has uncommitted changes, the branch name (if not default), a list of tags, and a list of bookmarks. When REV is not given, print a summary of the current state of the repository including the working directory. Specify -r. to get information of the working directory parent without scanning uncommitted changes. Specifying a path to a repository root or Mercurial bundle will cause lookup to operate on that repository/bundle. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :dirty: String. Character ``+`` denoting if the working directory has uncommitted changes. :id: String. One or two nodes, optionally followed by ``+``. :parents: List of strings. Parent nodes of the changeset. Examples: - generate a build identifier for the working directory:: hg id --id > build-id.dat - find the revision corresponding to a tag:: hg id -n -r 1.3 - check the most recent revision of a remote repository:: hg id -r tip https://www.mercurial-scm.org/repo/hg/ See :hg:`log` for generating more information about specific revisions, including full hash identifiers. Returns 0 if successful. """ opts = pycompat.byteskwargs(opts) if not repo and not source: raise error.Abort(_("there is no Mercurial repository here " "(.hg not found)")) default = not (num or id or branch or tags or bookmarks) output = [] revs = [] if source: source, branches = hg.parseurl(ui.expandpath(source)) peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo repo = peer.local() revs, checkout = hg.addbranchrevs(repo, peer, branches, None) fm = ui.formatter('identify', opts) fm.startitem() if not repo: if num or branch or tags: raise error.Abort( _("can't query remote revision number, branch, or tags")) if not rev and revs: rev = revs[0] if not rev: rev = "tip" remoterev = peer.lookup(rev) hexrev = fm.hexfunc(remoterev) if default or id: output = [hexrev] fm.data(id=hexrev) @util.cachefunc def getbms(): bms = [] if 'bookmarks' in peer.listkeys('namespaces'): hexremoterev = hex(remoterev) bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems() if bmr == hexremoterev] return sorted(bms) if fm.isplain(): if bookmarks: output.extend(getbms()) elif default and not ui.quiet: # multiple bookmarks for a single parent separated by '/' bm = '/'.join(getbms()) if bm: output.append(bm) else: fm.data(node=hex(remoterev)) if bookmarks or 'bookmarks' in fm.datahint(): fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark')) else: if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev, None) if ctx.rev() is None: ctx = repo[None] parents = ctx.parents() taglist = [] for p in parents: taglist.extend(p.tags()) dirty = "" if ctx.dirty(missing=True, merge=False, branch=False): dirty = '+' fm.data(dirty=dirty) hexoutput = [fm.hexfunc(p.node()) for p in parents] if default or id: output = ["%s%s" % ('+'.join(hexoutput), dirty)] fm.data(id="%s%s" % ('+'.join(hexoutput), dirty)) if num: numoutput = ["%d" % p.rev() for p in parents] output.append("%s%s" % ('+'.join(numoutput), dirty)) fm.data(parents=fm.formatlist([fm.hexfunc(p.node()) for p in parents], name='node')) else: hexoutput = fm.hexfunc(ctx.node()) if default or id: output = [hexoutput] fm.data(id=hexoutput) if num: output.append(pycompat.bytestr(ctx.rev())) taglist = ctx.tags() if default and not ui.quiet: b = ctx.branch() if b != 'default': output.append("(%s)" % b) # multiple tags for a single parent separated by '/' t = '/'.join(taglist) if t: output.append(t) # multiple bookmarks for a single parent separated by '/' bm = '/'.join(ctx.bookmarks()) if bm: output.append(bm) else: if branch: output.append(ctx.branch()) if tags: output.extend(taglist) if bookmarks: output.extend(ctx.bookmarks()) fm.data(node=ctx.hex()) fm.data(branch=ctx.branch()) fm.data(tags=fm.formatlist(taglist, name='tag', sep=':')) fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark')) fm.context(ctx=ctx) fm.plain("%s\n" % ' '.join(output)) fm.end() @command('import|patch', [('p', 'strip', 1, _('directory strip option for patch. This has the same ' 'meaning as the corresponding patch option'), _('NUM')), ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')), ('e', 'edit', False, _('invoke editor on commit messages')), ('f', 'force', None, _('skip check for outstanding uncommitted changes (DEPRECATED)')), ('', 'no-commit', None, _("don't commit, just update the working directory")), ('', 'bypass', None, _("apply patch without touching the working directory")), ('', 'partial', None, _('commit even if some hunks fail')), ('', 'exact', None, _('abort if patch would apply lossily')), ('', 'prefix', '', _('apply patch to subdirectory'), _('DIR')), ('', 'import-branch', None, _('use any branch information in patch (implied by --exact)'))] + commitopts + commitopts2 + similarityopts, _('[OPTION]... PATCH...'), helpcategory=command.CATEGORY_IMPORT_EXPORT) def import_(ui, repo, patch1=None, *patches, **opts): """import an ordered set of patches Import a list of patches and commit them individually (unless --no-commit is specified). To read a patch from standard input (stdin), use "-" as the patch name. If a URL is specified, the patch will be downloaded from there. Import first applies changes to the working directory (unless --bypass is specified), import will abort if there are outstanding changes. Use --bypass to apply and commit patches directly to the repository, without affecting the working directory. Without --exact, patches will be applied on top of the working directory parent revision. You can import a patch straight from a mail message. Even patches as attachments work (to use the body part, it must have type text/plain or text/x-patch). From and Subject headers of email message are used as default committer and commit message. All text/plain body parts before first diff are added to the commit message. If the imported patch was generated by :hg:`export`, user and description from patch override values from message headers and body. Values given on command line with -m/--message and -u/--user override these. If --exact is specified, import will set the working directory to the parent of each patch before applying it, and will abort if the resulting changeset has a different ID than the one recorded in the patch. This will guard against various ways that portable patch formats and mail systems might fail to transfer Mercurial data or metadata. See :hg:`bundle` for lossless transmission. Use --partial to ensure a changeset will be created from the patch even if some hunks fail to apply. Hunks that fail to apply will be written to a .rej file. Conflicts can then be resolved by hand before :hg:`commit --amend` is run to update the created changeset. This flag exists to let people import patches that partially apply without losing the associated metadata (author, date, description, ...). .. note:: When no hunks apply cleanly, :hg:`import --partial` will create an empty changeset, importing only the patch metadata. With -s/--similarity, hg will attempt to discover renames and copies in the patch in the same way as :hg:`addremove`. It is possible to use external patch programs to perform the patch by setting the ``ui.patch`` configuration option. For the default internal tool, the fuzz can also be configured via ``patch.fuzz``. See :hg:`help config` for more information about configuration files and how to use these options. See :hg:`help dates` for a list of formats valid for -d/--date. .. container:: verbose Examples: - import a traditional patch from a website and detect renames:: hg import -s 80 http://example.com/bugfix.patch - import a changeset from an hgweb server:: hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa - import all the patches in an Unix-style mbox:: hg import incoming-patches.mbox - import patches from stdin:: hg import - - attempt to exactly restore an exported changeset (not always possible):: hg import --exact proposed-fix.patch - use an external tool to apply a patch which is too fuzzy for the default internal tool. hg import --config ui.patch="patch --merge" fuzzy.patch - change the default fuzzing from 2 to a less strict 7 hg import --config ui.fuzz=7 fuzz.patch Returns 0 on success, 1 on partial success (see --partial). """ opts = pycompat.byteskwargs(opts) if not patch1: raise error.Abort(_('need at least one patch to import')) patches = (patch1,) + patches date = opts.get('date') if date: opts['date'] = dateutil.parsedate(date) exact = opts.get('exact') update = not opts.get('bypass') if not update and opts.get('no_commit'): raise error.Abort(_('cannot use --no-commit with --bypass')) try: sim = float(opts.get('similarity') or 0) except ValueError: raise error.Abort(_('similarity must be a number')) if sim < 0 or sim > 100: raise error.Abort(_('similarity must be between 0 and 100')) if sim and not update: raise error.Abort(_('cannot use --similarity with --bypass')) if exact: if opts.get('edit'): raise error.Abort(_('cannot use --exact with --edit')) if opts.get('prefix'): raise error.Abort(_('cannot use --exact with --prefix')) base = opts["base"] msgs = [] ret = 0 with repo.wlock(): if update: cmdutil.checkunfinished(repo) if (exact or not opts.get('force')): cmdutil.bailifchanged(repo) if not opts.get('no_commit'): lock = repo.lock tr = lambda: repo.transaction('import') dsguard = util.nullcontextmanager else: lock = util.nullcontextmanager tr = util.nullcontextmanager dsguard = lambda: dirstateguard.dirstateguard(repo, 'import') with lock(), tr(), dsguard(): parents = repo[None].parents() for patchurl in patches: if patchurl == '-': ui.status(_('applying patch from stdin\n')) patchfile = ui.fin patchurl = 'stdin' # for error message else: patchurl = os.path.join(base, patchurl) ui.status(_('applying %s\n') % patchurl) patchfile = hg.openpath(ui, patchurl, sendaccept=False) haspatch = False for hunk in patch.split(patchfile): with patch.extract(ui, hunk) as patchdata: msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata, parents, opts, msgs, hg.clean) if msg: haspatch = True ui.note(msg + '\n') if update or exact: parents = repo[None].parents() else: parents = [repo[node]] if rej: ui.write_err(_("patch applied partially\n")) ui.write_err(_("(fix the .rej files and run " "`hg commit --amend`)\n")) ret = 1 break if not haspatch: raise error.Abort(_('%s: no diffs found') % patchurl) if msgs: repo.savecommitmessage('\n* * *\n'.join(msgs)) return ret @command('incoming|in', [('f', 'force', None, _('run even if remote repository is unrelated')), ('n', 'newest-first', None, _('show newest record first')), ('', 'bundle', '', _('file to store the bundles into'), _('FILE')), ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')), ('B', 'bookmarks', False, _("compare bookmarks")), ('b', 'branch', [], _('a specific branch you would like to pull'), _('BRANCH')), ] + logopts + remoteopts + subrepoopts, _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT) def incoming(ui, repo, source="default", **opts): """show new changesets found in source Show new changesets found in the specified path/URL or the default pull location. These are the changesets that would have been pulled by :hg:`pull` at the time you issued this command. See pull for valid source format details. .. container:: verbose With -B/--bookmarks, the result of bookmark comparison between local and remote repositories is displayed. With -v/--verbose, status is also displayed for each bookmark like below:: BM1 01234567890a added BM2 1234567890ab advanced BM3 234567890abc diverged BM4 34567890abcd changed The action taken locally when pulling depends on the status of each bookmark: :``added``: pull will create it :``advanced``: pull will update it :``diverged``: pull will create a divergent bookmark :``changed``: result depends on remote changesets From the point of view of pulling behavior, bookmark existing only in the remote repository are treated as ``added``, even if it is in fact locally deleted. .. container:: verbose For remote repository, using --bundle avoids downloading the changesets twice if the incoming is followed by a pull. Examples: - show incoming changes with patches and full description:: hg incoming -vp - show incoming changes excluding merges, store a bundle:: hg in -vpM --bundle incoming.hg hg pull incoming.hg - briefly list changes inside a bundle:: hg in changes.hg -T "{desc|firstline}\\n" Returns 0 if there are incoming changes, 1 otherwise. """ opts = pycompat.byteskwargs(opts) if opts.get('graph'): logcmdutil.checkunsupportedgraphflags([], opts) def display(other, chlist, displayer): revdag = logcmdutil.graphrevs(other, chlist, opts) logcmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True) return 0 if opts.get('bundle') and opts.get('subrepos'): raise error.Abort(_('cannot combine --bundle and --subrepos')) if opts.get('bookmarks'): source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch')) other = hg.peer(repo, opts, source) if 'bookmarks' not in other.listkeys('namespaces'): ui.warn(_("remote doesn't support bookmarks\n")) return 0 ui.pager('incoming') ui.status(_('comparing with %s\n') % util.hidepassword(source)) return bookmarks.incoming(ui, repo, other) repo._subtoppath = ui.expandpath(source) try: return hg.incoming(ui, repo, source, opts) finally: del repo._subtoppath @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'), helpcategory=command.CATEGORY_REPO_CREATION, helpbasic=True, norepo=True) def init(ui, dest=".", **opts): """create a new repository in the given directory Initialize a new repository in the given directory. If the given directory does not exist, it will be created. If no directory is given, the current directory is used. It is possible to specify an ``ssh://`` URL as the destination. See :hg:`help urls` for more information. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) hg.peer(ui, opts, ui.expandpath(dest), create=True) @command('locate', [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')), ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), ('f', 'fullpath', None, _('print complete paths from the filesystem root')), ] + walkopts, _('[OPTION]... [PATTERN]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY) def locate(ui, repo, *pats, **opts): """locate files matching specific patterns (DEPRECATED) Print files under Mercurial control in the working directory whose names match the given patterns. By default, this command searches all directories in the working directory. To search just the current directory and its subdirectories, use "--include .". If no patterns are given to match, this command prints the names of all files under Mercurial control in the working directory. If you want to feed the output of this command into the "xargs" command, use the -0 option to both this command and "xargs". This will avoid the problem of "xargs" treating single filenames that contain whitespace as multiple filenames. See :hg:`help files` for a more versatile command. Returns 0 if a match is found, 1 otherwise. """ opts = pycompat.byteskwargs(opts) if opts.get('print0'): end = '\0' else: end = '\n' ctx = scmutil.revsingle(repo, opts.get('rev'), None) ret = 1 m = scmutil.match(ctx, pats, opts, default='relglob', badfn=lambda x, y: False) ui.pager('locate') if ctx.rev() is None: # When run on the working copy, "locate" includes removed files, so # we get the list of files from the dirstate. filesgen = sorted(repo.dirstate.matches(m)) else: filesgen = ctx.matches(m) uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats)) for abs in filesgen: if opts.get('fullpath'): ui.write(repo.wjoin(abs), end) else: ui.write(uipathfn(abs), end) ret = 0 return ret @command('log|history', [('f', 'follow', None, _('follow changeset history, or file history across copies and renames')), ('', 'follow-first', None, _('only follow the first parent of merge changesets (DEPRECATED)')), ('d', 'date', '', _('show revisions matching date spec'), _('DATE')), ('C', 'copies', None, _('show copied files')), ('k', 'keyword', [], _('do case-insensitive search for a given text'), _('TEXT')), ('r', 'rev', [], _('show the specified revision or revset'), _('REV')), ('L', 'line-range', [], _('follow line range of specified file (EXPERIMENTAL)'), _('FILE,RANGE')), ('', 'removed', None, _('include revisions where files were removed')), ('m', 'only-merges', None, _('show only merges (DEPRECATED) (use -r "merge()" instead)')), ('u', 'user', [], _('revisions committed by user'), _('USER')), ('', 'only-branch', [], _('show only changesets within the given named branch (DEPRECATED)'), _('BRANCH')), ('b', 'branch', [], _('show changesets within the given named branch'), _('BRANCH')), ('P', 'prune', [], _('do not display revision or any of its ancestors'), _('REV')), ] + logopts + walkopts, _('[OPTION]... [FILE]'), helpcategory=command.CATEGORY_CHANGE_NAVIGATION, helpbasic=True, inferrepo=True, intents={INTENT_READONLY}) def log(ui, repo, *pats, **opts): """show revision history of entire repository or files Print the revision history of the specified files or the entire project. If no revision range is specified, the default is ``tip:0`` unless --follow is set, in which case the working directory parent is used as the starting revision. File history is shown without following rename or copy history of files. Use -f/--follow with a filename to follow history across renames and copies. --follow without a filename will only show ancestors of the starting revision. By default this command prints revision number and changeset id, tags, non-trivial parents, user, date and time, and a summary for each commit. When the -v/--verbose switch is used, the list of changed files and full commit message are shown. With --graph the revisions are shown as an ASCII art DAG with the most recent changeset at the top. 'o' is a changeset, '@' is a working directory parent, '_' closes a branch, 'x' is obsolete, '*' is unstable, and '+' represents a fork where the changeset from the lines below is a parent of the 'o' merge on the same line. Paths in the DAG are represented with '|', '/' and so forth. ':' in place of a '|' indicates one or more revisions in a path are omitted. .. container:: verbose Use -L/--line-range FILE,M:N options to follow the history of lines from M to N in FILE. With -p/--patch only diff hunks affecting specified line range will be shown. This option requires --follow; it can be specified multiple times. Currently, this option is not compatible with --graph. This option is experimental. .. note:: :hg:`log --patch` may generate unexpected diff output for merge changesets, as it will only compare the merge changeset against its first parent. Also, only files different from BOTH parents will appear in files:. .. note:: For performance reasons, :hg:`log FILE` may omit duplicate changes made on branches and will not show removals or mode changes. To see all such changes, use the --removed switch. .. container:: verbose .. note:: The history resulting from -L/--line-range options depends on diff options; for instance if white-spaces are ignored, respective changes with only white-spaces in specified line range will not be listed. .. container:: verbose Some examples: - changesets with full descriptions and file lists:: hg log -v - changesets ancestral to the working directory:: hg log -f - last 10 commits on the current branch:: hg log -l 10 -b . - changesets showing all modifications of a file, including removals:: hg log --removed file.c - all changesets that touch a directory, with diffs, excluding merges:: hg log -Mp lib/ - all revision numbers that match a keyword:: hg log -k bug --template "{rev}\\n" - the full hash identifier of the working directory parent:: hg log -r . --template "{node}\\n" - list available log templates:: hg log -T list - check if a given changeset is included in a tagged release:: hg log -r "a21ccf and ancestor(1.9)" - find all changesets by some user in a date range:: hg log -k alice -d "may 2008 to jul 2008" - summary of all changesets after the last tag:: hg log -r "last(tagged())::" --template "{desc|firstline}\\n" - changesets touching lines 13 to 23 for file.c:: hg log -L file.c,13:23 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of main.c with patch:: hg log -L file.c,13:23 -L main.c,2:6 -p See :hg:`help dates` for a list of formats valid for -d/--date. See :hg:`help revisions` for more about specifying and ordering revisions. See :hg:`help templates` for more about pre-packaged styles and specifying custom templates. The default template used by the log command can be customized via the ``ui.logtemplate`` configuration setting. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) linerange = opts.get('line_range') if linerange and not opts.get('follow'): raise error.Abort(_('--line-range requires --follow')) if linerange and pats: # TODO: take pats as patterns with no line-range filter raise error.Abort( _('FILE arguments are not compatible with --line-range option') ) repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn') revs, differ = logcmdutil.getrevs(repo, pats, opts) if linerange: # TODO: should follow file history from logcmdutil._initialrevs(), # then filter the result by logcmdutil._makerevset() and --limit revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts) getcopies = None if opts.get('copies'): endrev = None if revs: endrev = revs.max() + 1 getcopies = scmutil.getcopiesfn(repo, endrev=endrev) ui.pager('log') displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ, buffered=True) if opts.get('graph'): displayfn = logcmdutil.displaygraphrevs else: displayfn = logcmdutil.displayrevs displayfn(ui, repo, revs, displayer, getcopies) @command('manifest', [('r', 'rev', '', _('revision to display'), _('REV')), ('', 'all', False, _("list files from all revisions"))] + formatteropts, _('[-r REV]'), helpcategory=command.CATEGORY_MAINTENANCE, intents={INTENT_READONLY}) def manifest(ui, repo, node=None, rev=None, **opts): """output the current or given revision of the project manifest Print a list of version controlled files for the given revision. If no revision is given, the first parent of the working directory is used, or the null revision if no revision is checked out. With -v, print file permissions, symlink and executable bits. With --debug, print file revision hashes. If option --all is specified, the list of all files from all revisions is printed. This includes deleted and renamed files. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) fm = ui.formatter('manifest', opts) if opts.get('all'): if rev or node: raise error.Abort(_("can't specify a revision with --all")) res = set() for rev in repo: ctx = repo[rev] res |= set(ctx.files()) ui.pager('manifest') for f in sorted(res): fm.startitem() fm.write("path", '%s\n', f) fm.end() return if rev and node: raise error.Abort(_("please specify just one revision")) if not node: node = rev char = {'l': '@', 'x': '*', '': '', 't': 'd'} mode = {'l': '644', 'x': '755', '': '644', 't': '755'} if node: repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn') ctx = scmutil.revsingle(repo, node) mf = ctx.manifest() ui.pager('manifest') for f in ctx: fm.startitem() fm.context(ctx=ctx) fl = ctx[f].flags() fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f])) fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl]) fm.write('path', '%s\n', f) fm.end() @command('merge', [('f', 'force', None, _('force a merge including outstanding changes (DEPRECATED)')), ('r', 'rev', '', _('revision to merge'), _('REV')), ('P', 'preview', None, _('review revisions to merge (no merge is performed)')), ('', 'abort', None, _('abort the ongoing merge')), ] + mergetoolopts, _('[-P] [[-r] REV]'), helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True) def merge(ui, repo, node=None, **opts): """merge another revision into working directory The current working directory is updated with all changes made in the requested revision since the last common predecessor revision. Files that changed between either parent are marked as changed for the next commit and a commit must be performed before any further updates to the repository are allowed. The next commit will have two parents. ``--tool`` can be used to specify the merge tool used for file merges. It overrides the HGMERGE environment variable and your configuration files. See :hg:`help merge-tools` for options. If no revision is specified, the working directory's parent is a head revision, and the current branch contains exactly one other head, the other head is merged with by default. Otherwise, an explicit revision with which to merge must be provided. See :hg:`help resolve` for information on handling file conflicts. To undo an uncommitted merge, use :hg:`merge --abort` which will check out a clean copy of the original merge parent, losing all changes. Returns 0 on success, 1 if there are unresolved files. """ opts = pycompat.byteskwargs(opts) abort = opts.get('abort') if abort and repo.dirstate.p2() == nullid: cmdutil.wrongtooltocontinue(repo, _('merge')) if abort: if node: raise error.Abort(_("cannot specify a node with --abort")) if opts.get('rev'): raise error.Abort(_("cannot specify both --rev and --abort")) if opts.get('preview'): raise error.Abort(_("cannot specify --preview with --abort")) if opts.get('rev') and node: raise error.Abort(_("please specify just one revision")) if not node: node = opts.get('rev') if node: node = scmutil.revsingle(repo, node).node() if not node and not abort: node = repo[destutil.destmerge(repo)].node() if opts.get('preview'): # find nodes that are ancestors of p2 but not of p1 p1 = repo.lookup('.') p2 = node nodes = repo.changelog.findmissing(common=[p1], heads=[p2]) displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for node in nodes: displayer.show(repo[node]) displayer.close() return 0 # ui.forcemerge is an internal variable, do not document overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'merge'): force = opts.get('force') labels = ['working copy', 'merge rev'] return hg.merge(repo, node, force=force, mergeforce=force, labels=labels, abort=abort) @command('outgoing|out', [('f', 'force', None, _('run even when the destination is unrelated')), ('r', 'rev', [], _('a changeset intended to be included in the destination'), _('REV')), ('n', 'newest-first', None, _('show newest record first')), ('B', 'bookmarks', False, _('compare bookmarks')), ('b', 'branch', [], _('a specific branch you would like to push'), _('BRANCH')), ] + logopts + remoteopts + subrepoopts, _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT) def outgoing(ui, repo, dest=None, **opts): """show changesets not found in the destination Show changesets not found in the specified destination repository or the default push location. These are the changesets that would be pushed if a push was requested. See pull for details of valid destination formats. .. container:: verbose With -B/--bookmarks, the result of bookmark comparison between local and remote repositories is displayed. With -v/--verbose, status is also displayed for each bookmark like below:: BM1 01234567890a added BM2 deleted BM3 234567890abc advanced BM4 34567890abcd diverged BM5 4567890abcde changed The action taken when pushing depends on the status of each bookmark: :``added``: push with ``-B`` will create it :``deleted``: push with ``-B`` will delete it :``advanced``: push will update it :``diverged``: push with ``-B`` will update it :``changed``: push with ``-B`` will update it From the point of view of pushing behavior, bookmarks existing only in the remote repository are treated as ``deleted``, even if it is in fact added remotely. Returns 0 if there are outgoing changes, 1 otherwise. """ # hg._outgoing() needs to re-resolve the path in order to handle #branch # style URLs, so don't overwrite dest. path = ui.paths.getpath(dest, default=('default-push', 'default')) if not path: raise error.Abort(_('default repository not configured!'), hint=_("see 'hg help config.paths'")) opts = pycompat.byteskwargs(opts) if opts.get('graph'): logcmdutil.checkunsupportedgraphflags([], opts) o, other = hg._outgoing(ui, repo, dest, opts) if not o: cmdutil.outgoinghooks(ui, repo, other, opts, o) return revdag = logcmdutil.graphrevs(repo, o, opts) ui.pager('outgoing') displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True) logcmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) cmdutil.outgoinghooks(ui, repo, other, opts, o) return 0 if opts.get('bookmarks'): dest = path.pushloc or path.loc other = hg.peer(repo, opts, dest) if 'bookmarks' not in other.listkeys('namespaces'): ui.warn(_("remote doesn't support bookmarks\n")) return 0 ui.status(_('comparing with %s\n') % util.hidepassword(dest)) ui.pager('outgoing') return bookmarks.outgoing(ui, repo, other) repo._subtoppath = path.pushloc or path.loc try: return hg.outgoing(ui, repo, dest, opts) finally: del repo._subtoppath @command('parents', [('r', 'rev', '', _('show parents of the specified revision'), _('REV')), ] + templateopts, _('[-r REV] [FILE]'), helpcategory=command.CATEGORY_CHANGE_NAVIGATION, inferrepo=True) def parents(ui, repo, file_=None, **opts): """show the parents of the working directory or revision (DEPRECATED) Print the working directory's parent revisions. If a revision is given via -r/--rev, the parent of that revision will be printed. If a file argument is given, the revision in which the file was last changed (before the working directory revision or the argument to --rev if given) is printed. This command is equivalent to:: hg log -r "p1()+p2()" or hg log -r "p1(REV)+p2(REV)" or hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))" See :hg:`summary` and :hg:`help revsets` for related information. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev, None) if file_: m = scmutil.match(ctx, (file_,), opts) if m.anypats() or len(m.files()) != 1: raise error.Abort(_('can only specify an explicit filename')) file_ = m.files()[0] filenodes = [] for cp in ctx.parents(): if not cp: continue try: filenodes.append(cp.filenode(file_)) except error.LookupError: pass if not filenodes: raise error.Abort(_("'%s' not found in manifest!") % file_) p = [] for fn in filenodes: fctx = repo.filectx(file_, fileid=fn) p.append(fctx.node()) else: p = [cp.node() for cp in ctx.parents()] displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for n in p: if n != nullid: displayer.show(repo[n]) displayer.close() @command('paths', formatteropts, _('[NAME]'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, optionalrepo=True, intents={INTENT_READONLY}) def paths(ui, repo, search=None, **opts): """show aliases for remote repositories Show definition of symbolic path name NAME. If no name is given, show definition of all available names. Option -q/--quiet suppresses all output when searching for NAME and shows only the path names when listing all definitions. Path names are defined in the [paths] section of your configuration file and in ``/etc/mercurial/hgrc``. If run inside a repository, ``.hg/hgrc`` is used, too. The path names ``default`` and ``default-push`` have a special meaning. When performing a push or pull operation, they are used as fallbacks if no location is specified on the command-line. When ``default-push`` is set, it will be used for push and ``default`` will be used for pull; otherwise ``default`` is used as the fallback for both. When cloning a repository, the clone source is written as ``default`` in ``.hg/hgrc``. .. note:: ``default`` and ``default-push`` apply to all inbound (e.g. :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and :hg:`bundle`) operations. See :hg:`help urls` for more information. .. container:: verbose Template: The following keywords are supported. See also :hg:`help templates`. :name: String. Symbolic name of the path alias. :pushurl: String. URL for push operations. :url: String. URL or directory path for the other operations. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) ui.pager('paths') if search: pathitems = [(name, path) for name, path in ui.paths.iteritems() if name == search] else: pathitems = sorted(ui.paths.iteritems()) fm = ui.formatter('paths', opts) if fm.isplain(): hidepassword = util.hidepassword else: hidepassword = bytes if ui.quiet: namefmt = '%s\n' else: namefmt = '%s = ' showsubopts = not search and not ui.quiet for name, path in pathitems: fm.startitem() fm.condwrite(not search, 'name', namefmt, name) fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc)) for subopt, value in sorted(path.suboptions.items()): assert subopt not in ('name', 'url') if showsubopts: fm.plain('%s:%s = ' % (name, subopt)) fm.condwrite(showsubopts, subopt, '%s\n', value) fm.end() if search and not pathitems: if not ui.quiet: ui.warn(_("not found!\n")) return 1 else: return 0 @command('phase', [('p', 'public', False, _('set changeset phase to public')), ('d', 'draft', False, _('set changeset phase to draft')), ('s', 'secret', False, _('set changeset phase to secret')), ('f', 'force', False, _('allow to move boundary backward')), ('r', 'rev', [], _('target revision'), _('REV')), ], _('[-p|-d|-s] [-f] [-r] [REV...]'), helpcategory=command.CATEGORY_CHANGE_ORGANIZATION) def phase(ui, repo, *revs, **opts): """set or show the current phase name With no argument, show the phase name of the current revision(s). With one of -p/--public, -d/--draft or -s/--secret, change the phase value of the specified revisions. Unless -f/--force is specified, :hg:`phase` won't move changesets from a lower phase to a higher phase. Phases are ordered as follows:: public < draft < secret Returns 0 on success, 1 if some phases could not be changed. (For more information about the phases concept, see :hg:`help phases`.) """ opts = pycompat.byteskwargs(opts) # search for a unique phase argument targetphase = None for idx, name in enumerate(phases.cmdphasenames): if opts[name]: if targetphase is not None: raise error.Abort(_('only one phase can be specified')) targetphase = idx # look for specified revision revs = list(revs) revs.extend(opts['rev']) if not revs: # display both parents as the second parent phase can influence # the phase of a merge commit revs = [c.rev() for c in repo[None].parents()] revs = scmutil.revrange(repo, revs) ret = 0 if targetphase is None: # display for r in revs: ctx = repo[r] ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr())) else: with repo.lock(), repo.transaction("phase") as tr: # set phase if not revs: raise error.Abort(_('empty revision set')) nodes = [repo[r].node() for r in revs] # moving revision from public to draft may hide them # We have to check result on an unfiltered repository unfi = repo.unfiltered() getphase = unfi._phasecache.phase olddata = [getphase(unfi, r) for r in unfi] phases.advanceboundary(repo, tr, targetphase, nodes) if opts['force']: phases.retractboundary(repo, tr, targetphase, nodes) getphase = unfi._phasecache.phase newdata = [getphase(unfi, r) for r in unfi] changes = sum(newdata[r] != olddata[r] for r in unfi) cl = unfi.changelog rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase] if rejected: ui.warn(_('cannot move %i changesets to a higher ' 'phase, use --force\n') % len(rejected)) ret = 1 if changes: msg = _('phase changed for %i changesets\n') % changes if ret: ui.status(msg) else: ui.note(msg) else: ui.warn(_('no phases changed\n')) return ret def postincoming(ui, repo, modheads, optupdate, checkout, brev): """Run after a changegroup has been added via pull/unbundle This takes arguments below: :modheads: change of heads by pull/unbundle :optupdate: updating working directory is needed or not :checkout: update destination revision (or None to default destination) :brev: a name, which might be a bookmark to be activated after updating """ if modheads == 0: return if optupdate: try: return hg.updatetotally(ui, repo, checkout, brev) except error.UpdateAbort as inst: msg = _("not updating: %s") % stringutil.forcebytestr(inst) hint = inst.hint raise error.UpdateAbort(msg, hint=hint) if modheads is not None and modheads > 1: currentbranchheads = len(repo.branchheads()) if currentbranchheads == modheads: ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n")) elif currentbranchheads > 1: ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to " "merge)\n")) else: ui.status(_("(run 'hg heads' to see heads)\n")) elif not ui.configbool('commands', 'update.requiredest'): ui.status(_("(run 'hg update' to get a working copy)\n")) @command('pull', [('u', 'update', None, _('update to new branch head if new descendants were pulled')), ('f', 'force', None, _('run even when remote repository is unrelated')), ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')), ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')), ('b', 'branch', [], _('a specific branch you would like to pull'), _('BRANCH')), ] + remoteopts, _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, helpbasic=True) def pull(ui, repo, source="default", **opts): """pull changes from the specified source Pull changes from a remote repository to a local one. This finds all changes from the repository at the specified path or URL and adds them to a local repository (the current one unless -R is specified). By default, this does not update the copy of the project in the working directory. When cloning from servers that support it, Mercurial may fetch pre-generated data. When this is done, hooks operating on incoming changesets and changegroups may fire more than once, once for each pre-generated bundle and as well as for any additional remaining data. See :hg:`help -e clonebundles` for more. Use :hg:`incoming` if you want to see what would have been added by a pull at the time you issued this command. If you then decide to add those changes to the repository, you should use :hg:`pull -r X` where ``X`` is the last changeset listed by :hg:`incoming`. If SOURCE is omitted, the 'default' path will be used. See :hg:`help urls` for more information. Specifying bookmark as ``.`` is equivalent to specifying the active bookmark's name. Returns 0 on success, 1 if an update had unresolved files. """ opts = pycompat.byteskwargs(opts) if ui.configbool('commands', 'update.requiredest') and opts.get('update'): msg = _('update destination required by configuration') hint = _('use hg pull followed by hg update DEST') raise error.Abort(msg, hint=hint) source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch')) ui.status(_('pulling from %s\n') % util.hidepassword(source)) other = hg.peer(repo, opts, source) try: revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev')) pullopargs = {} nodes = None if opts.get('bookmark') or revs: # The list of bookmark used here is the same used to actually update # the bookmark names, to avoid the race from issue 4689 and we do # all lookup and bookmark queries in one go so they see the same # version of the server state (issue 4700). nodes = [] fnodes = [] revs = revs or [] if revs and not other.capable('lookup'): err = _("other repository doesn't support revision lookup, " "so a rev cannot be specified.") raise error.Abort(err) with other.commandexecutor() as e: fremotebookmarks = e.callcommand('listkeys', { 'namespace': 'bookmarks' }) for r in revs: fnodes.append(e.callcommand('lookup', {'key': r})) remotebookmarks = fremotebookmarks.result() remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks) pullopargs['remotebookmarks'] = remotebookmarks for b in opts.get('bookmark', []): b = repo._bookmarks.expandname(b) if b not in remotebookmarks: raise error.Abort(_('remote bookmark %s not found!') % b) nodes.append(remotebookmarks[b]) for i, rev in enumerate(revs): node = fnodes[i].result() nodes.append(node) if rev == checkout: checkout = node wlock = util.nullcontextmanager() if opts.get('update'): wlock = repo.wlock() with wlock: pullopargs.update(opts.get('opargs', {})) modheads = exchange.pull(repo, other, heads=nodes, force=opts.get('force'), bookmarks=opts.get('bookmark', ()), opargs=pullopargs).cgresult # brev is a name, which might be a bookmark to be activated at # the end of the update. In other words, it is an explicit # destination of the update brev = None if checkout: checkout = repo.unfiltered().changelog.rev(checkout) # order below depends on implementation of # hg.addbranchrevs(). opts['bookmark'] is ignored, # because 'checkout' is determined without it. if opts.get('rev'): brev = opts['rev'][0] elif opts.get('branch'): brev = opts['branch'][0] else: brev = branches[0] repo._subtoppath = source try: ret = postincoming(ui, repo, modheads, opts.get('update'), checkout, brev) except error.FilteredRepoLookupError as exc: msg = _('cannot update to target: %s') % exc.args[0] exc.args = (msg,) + exc.args[1:] raise finally: del repo._subtoppath finally: other.close() return ret @command('push', [('f', 'force', None, _('force push')), ('r', 'rev', [], _('a changeset intended to be included in the destination'), _('REV')), ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')), ('b', 'branch', [], _('a specific branch you would like to push'), _('BRANCH')), ('', 'new-branch', False, _('allow pushing a new branch')), ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')), ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')), ] + remoteopts, _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, helpbasic=True) def push(ui, repo, dest=None, **opts): """push changes to the specified destination Push changesets from the local repository to the specified destination. This operation is symmetrical to pull: it is identical to a pull in the destination repository from the current one. By default, push will not allow creation of new heads at the destination, since multiple heads would make it unclear which head to use. In this situation, it is recommended to pull and merge before pushing. Use --new-branch if you want to allow push to create a new named branch that is not present at the destination. This allows you to only create a new branch without forcing other changes. .. note:: Extra care should be taken with the -f/--force option, which will push all new heads on all branches, an action which will almost always cause confusion for collaborators. If -r/--rev is used, the specified revision and all its ancestors will be pushed to the remote repository. If -B/--bookmark is used, the specified bookmarked revision, its ancestors, and the bookmark will be pushed to the remote repository. Specifying ``.`` is equivalent to specifying the active bookmark's name. Please see :hg:`help urls` for important details about ``ssh://`` URLs. If DESTINATION is omitted, a default path will be used. .. container:: verbose The --pushvars option sends strings to the server that become environment variables prepended with ``HG_USERVAR_``. For example, ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment. pushvars can provide for user-overridable hooks as well as set debug levels. One example is having a hook that blocks commits containing conflict markers, but enables the user to override the hook if the file is using conflict markers for testing purposes or the file format has strings that look like conflict markers. By default, servers will ignore `--pushvars`. To enable it add the following to your configuration file:: [push] pushvars.server = true Returns 0 if push was successful, 1 if nothing to push. """ opts = pycompat.byteskwargs(opts) if opts.get('bookmark'): ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push') for b in opts['bookmark']: # translate -B options to -r so changesets get pushed b = repo._bookmarks.expandname(b) if b in repo._bookmarks: opts.setdefault('rev', []).append(b) else: # if we try to push a deleted bookmark, translate it to null # this lets simultaneous -r, -b options continue working opts.setdefault('rev', []).append("null") path = ui.paths.getpath(dest, default=('default-push', 'default')) if not path: raise error.Abort(_('default repository not configured!'), hint=_("see 'hg help config.paths'")) dest = path.pushloc or path.loc branches = (path.branch, opts.get('branch') or []) ui.status(_('pushing to %s\n') % util.hidepassword(dest)) revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev')) other = hg.peer(repo, opts, dest) if revs: revs = [repo[r].node() for r in scmutil.revrange(repo, revs)] if not revs: raise error.Abort(_("specified revisions evaluate to an empty set"), hint=_("use different revision arguments")) elif path.pushrev: # It doesn't make any sense to specify ancestor revisions. So limit # to DAG heads to make discovery simpler. expr = revsetlang.formatspec('heads(%r)', path.pushrev) revs = scmutil.revrange(repo, [expr]) revs = [repo[rev].node() for rev in revs] if not revs: raise error.Abort(_('default push revset for path evaluates to an ' 'empty set')) repo._subtoppath = dest try: # push subrepos depth-first for coherent ordering c = repo['.'] subs = c.substate # only repos that are committed for s in sorted(subs): result = c.sub(s).push(opts) if result == 0: return not result finally: del repo._subtoppath opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it opargs.setdefault('pushvars', []).extend(opts.get('pushvars', [])) pushop = exchange.push(repo, other, opts.get('force'), revs=revs, newbranch=opts.get('new_branch'), bookmarks=opts.get('bookmark', ()), publish=opts.get('publish'), opargs=opargs) result = not pushop.cgresult if pushop.bkresult is not None: if pushop.bkresult == 2: result = 2 elif not result and pushop.bkresult: result = 2 return result @command('recover', [('','verify', True, "run `hg verify` after succesful recover"), ], helpcategory=command.CATEGORY_MAINTENANCE) def recover(ui, repo, **opts): """roll back an interrupted transaction Recover from an interrupted commit or pull. This command tries to fix the repository status after an interrupted operation. It should only be necessary when Mercurial suggests it. Returns 0 if successful, 1 if nothing to recover or verify fails. """ ret = repo.recover() if ret: if opts[r'verify']: return hg.verify(repo) else: msg = _("(verify step skipped, run `hg verify` to check your " "repository content)\n") ui.warn(msg) return 0 return 1 @command('remove|rm', [('A', 'after', None, _('record delete for missing files')), ('f', 'force', None, _('forget added files, delete modified files')), ] + subrepoopts + walkopts + dryrunopts, _('[OPTION]... FILE...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, helpbasic=True, inferrepo=True) def remove(ui, repo, *pats, **opts): """remove the specified files on the next commit Schedule the indicated files for removal from the current branch. This command schedules the files to be removed at the next commit. To undo a remove before that, see :hg:`revert`. To undo added files, see :hg:`forget`. .. container:: verbose -A/--after can be used to remove only files that have already been deleted, -f/--force can be used to force deletion, and -Af can be used to remove files from the next revision without deleting them from the working directory. The following table details the behavior of remove for different file states (columns) and option combinations (rows). The file states are Added [A], Clean [C], Modified [M] and Missing [!] (as reported by :hg:`status`). The actions are Warn, Remove (from branch) and Delete (from disk): ========= == == == == opt/state A C M ! ========= == == == == none W RD W R -f R RD RD R -A W W W R -Af R R R R ========= == == == == .. note:: :hg:`remove` never deletes files in Added [A] state from the working directory, not even if ``--force`` is specified. Returns 0 on success, 1 if any warnings encountered. """ opts = pycompat.byteskwargs(opts) after, force = opts.get('after'), opts.get('force') dryrun = opts.get('dry_run') if not pats and not after: raise error.Abort(_('no files specified')) m = scmutil.match(repo[None], pats, opts) subrepos = opts.get('subrepos') uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos, dryrun=dryrun) @command('rename|move|mv', [('A', 'after', None, _('record a rename that has already occurred')), ('f', 'force', None, _('forcibly copy over an existing managed file')), ] + walkopts + dryrunopts, _('[OPTION]... SOURCE... DEST'), helpcategory=command.CATEGORY_WORKING_DIRECTORY) def rename(ui, repo, *pats, **opts): """rename files; equivalent of copy + remove Mark dest as copies of sources; mark sources for deletion. If dest is a directory, copies are put in that directory. If dest is a file, there can only be one source. By default, this command copies the contents of files as they exist in the working directory. If invoked with -A/--after, the operation is recorded, but no copying is performed. This command takes effect at the next commit. To undo a rename before that, see :hg:`revert`. Returns 0 on success, 1 if errors are encountered. """ opts = pycompat.byteskwargs(opts) with repo.wlock(False): return cmdutil.copy(ui, repo, pats, opts, rename=True) @command('resolve', [('a', 'all', None, _('select all unresolved files')), ('l', 'list', None, _('list state of files needing merge')), ('m', 'mark', None, _('mark files as resolved')), ('u', 'unmark', None, _('mark files as unresolved')), ('n', 'no-status', None, _('hide status prefix')), ('', 're-merge', None, _('re-merge files'))] + mergetoolopts + walkopts + formatteropts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, inferrepo=True) def resolve(ui, repo, *pats, **opts): """redo merges or set/view the merge status of files Merges with unresolved conflicts are often the result of non-interactive merging using the ``internal:merge`` configuration setting, or a command-line merge tool like ``diff3``. The resolve command is used to manage the files involved in a merge, after :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the working directory must have two parents). See :hg:`help merge-tools` for information on configuring merge tools. The resolve command can be used in the following ways: - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge the specified files, discarding any previous merge attempts. Re-merging is not performed for files already marked as resolved. Use ``--all/-a`` to select all unresolved files. ``--tool`` can be used to specify the merge tool used for the given files. It overrides the HGMERGE environment variable and your configuration files. Previous file contents are saved with a ``.orig`` suffix. - :hg:`resolve -m [FILE]`: mark a file as having been resolved (e.g. after having manually fixed-up the files). The default is to mark all unresolved files. - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The default is to mark all resolved files. - :hg:`resolve -l`: list files which had or still have conflicts. In the printed list, ``U`` = unresolved and ``R`` = resolved. You can use ``set:unresolved()`` or ``set:resolved()`` to filter the list. See :hg:`help filesets` for details. .. note:: Mercurial will not let you commit files with unresolved merge conflicts. You must use :hg:`resolve -m ...` before you can commit after a conflicting merge. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``. :path: String. Repository-absolute path of the file. Returns 0 on success, 1 if any files fail a resolve attempt. """ opts = pycompat.byteskwargs(opts) confirm = ui.configbool('commands', 'resolve.confirm') flaglist = 'all mark unmark list no_status re_merge'.split() all, mark, unmark, show, nostatus, remerge = [ opts.get(o) for o in flaglist] actioncount = len(list(filter(None, [show, mark, unmark, remerge]))) if actioncount > 1: raise error.Abort(_("too many actions specified")) elif (actioncount == 0 and ui.configbool('commands', 'resolve.explicit-re-merge')): hint = _('use --mark, --unmark, --list or --re-merge') raise error.Abort(_('no action specified'), hint=hint) if pats and all: raise error.Abort(_("can't specify --all and patterns")) if not (all or pats or show or mark or unmark): raise error.Abort(_('no files or directories specified'), hint=('use --all to re-merge all unresolved files')) if confirm: if all: if ui.promptchoice(_(b're-merge all unresolved files (yn)?' b'$$ &Yes $$ &No')): raise error.Abort(_('user quit')) if mark and not pats: if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?' b'$$ &Yes $$ &No')): raise error.Abort(_('user quit')) if unmark and not pats: if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?' b'$$ &Yes $$ &No')): raise error.Abort(_('user quit')) uipathfn = scmutil.getuipathfn(repo) if show: ui.pager('resolve') fm = ui.formatter('resolve', opts) ms = mergemod.mergestate.read(repo) wctx = repo[None] m = scmutil.match(wctx, pats, opts) # Labels and keys based on merge state. Unresolved path conflicts show # as 'P'. Resolved path conflicts show as 'R', the same as normal # resolved conflicts. mergestateinfo = { mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'), mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'), mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'), mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'), mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved', 'D'), } for f in ms: if not m(f): continue label, key = mergestateinfo[ms[f]] fm.startitem() fm.context(ctx=wctx) fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label) fm.data(path=f) fm.plain('%s\n' % uipathfn(f), label=label) fm.end() return 0 with repo.wlock(): ms = mergemod.mergestate.read(repo) if not (ms.active() or repo.dirstate.p2() != nullid): raise error.Abort( _('resolve command not applicable when not merging')) wctx = repo[None] if (ms.mergedriver and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED): proceed = mergemod.driverpreprocess(repo, ms, wctx) ms.commit() # allow mark and unmark to go through if not mark and not unmark and not proceed: return 1 m = scmutil.match(wctx, pats, opts) ret = 0 didwork = False runconclude = False tocomplete = [] hasconflictmarkers = [] if mark: markcheck = ui.config('commands', 'resolve.mark-check') if markcheck not in ['warn', 'abort']: # Treat all invalid / unrecognized values as 'none'. markcheck = False for f in ms: if not m(f): continue didwork = True # don't let driver-resolved files be marked, and run the conclude # step if asked to resolve if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED: exact = m.exact(f) if mark: if exact: ui.warn(_('not marking %s as it is driver-resolved\n') % uipathfn(f)) elif unmark: if exact: ui.warn(_('not unmarking %s as it is driver-resolved\n') % uipathfn(f)) else: runconclude = True continue # path conflicts must be resolved manually if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH, mergemod.MERGE_RECORD_RESOLVED_PATH): if mark: ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH) elif unmark: ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH) elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH: ui.warn(_('%s: path conflict must be resolved manually\n') % uipathfn(f)) continue if mark: if markcheck: fdata = repo.wvfs.tryread(f) if (filemerge.hasconflictmarkers(fdata) and ms[f] != mergemod.MERGE_RECORD_RESOLVED): hasconflictmarkers.append(f) ms.mark(f, mergemod.MERGE_RECORD_RESOLVED) elif unmark: ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED) else: # backup pre-resolve (merge uses .orig for its own purposes) a = repo.wjoin(f) try: util.copyfile(a, a + ".resolve") except (IOError, OSError) as inst: if inst.errno != errno.ENOENT: raise try: # preresolve file overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'resolve'): complete, r = ms.preresolve(f, wctx) if not complete: tocomplete.append(f) elif r: ret = 1 finally: ms.commit() # replace filemerge's .orig file with our resolve file, but only # for merges that are complete if complete: try: util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f)) except OSError as inst: if inst.errno != errno.ENOENT: raise if hasconflictmarkers: ui.warn(_('warning: the following files still have conflict ' 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n' for f in hasconflictmarkers)) if markcheck == 'abort' and not all and not pats: raise error.Abort(_('conflict markers detected'), hint=_('use --all to mark anyway')) for f in tocomplete: try: # resolve file overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'resolve'): r = ms.resolve(f, wctx) if r: ret = 1 finally: ms.commit() # replace filemerge's .orig file with our resolve file a = repo.wjoin(f) try: util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f)) except OSError as inst: if inst.errno != errno.ENOENT: raise ms.commit() ms.recordactions() if not didwork and pats: hint = None if not any([p for p in pats if p.find(':') >= 0]): pats = ['path:%s' % p for p in pats] m = scmutil.match(wctx, pats, opts) for f in ms: if not m(f): continue def flag(o): if o == 're_merge': return '--re-merge ' return '-%s ' % o[0:1] flags = ''.join([flag(o) for o in flaglist if opts.get(o)]) hint = _("(try: hg resolve %s%s)\n") % ( flags, ' '.join(pats)) break ui.warn(_("arguments do not match paths that need resolving\n")) if hint: ui.warn(hint) elif ms.mergedriver and ms.mdstate() != 's': # run conclude step when either a driver-resolved file is requested # or there are no driver-resolved files # we can't use 'ret' to determine whether any files are unresolved # because we might not have tried to resolve some if ((runconclude or not list(ms.driverresolved())) and not list(ms.unresolved())): proceed = mergemod.driverconclude(repo, ms, wctx) ms.commit() if not proceed: return 1 # Nudge users into finishing an unfinished operation unresolvedf = list(ms.unresolved()) driverresolvedf = list(ms.driverresolved()) if not unresolvedf and not driverresolvedf: ui.status(_('(no more unresolved files)\n')) cmdutil.checkafterresolved(repo) elif not unresolvedf: ui.status(_('(no more unresolved files -- ' 'run "hg resolve --all" to conclude)\n')) return ret @command('revert', [('a', 'all', None, _('revert all changes when no arguments given')), ('d', 'date', '', _('tipmost revision matching date'), _('DATE')), ('r', 'rev', '', _('revert to the specified revision'), _('REV')), ('C', 'no-backup', None, _('do not save backup copies of files')), ('i', 'interactive', None, _('interactively select the changes')), ] + walkopts + dryrunopts, _('[OPTION]... [-r REV] [NAME]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY) def revert(ui, repo, *pats, **opts): """restore files to their checkout state .. note:: To check out earlier revisions, you should use :hg:`update REV`. To cancel an uncommitted merge (and lose your changes), use :hg:`merge --abort`. With no revision specified, revert the specified files or directories to the contents they had in the parent of the working directory. This restores the contents of files to an unmodified state and unschedules adds, removes, copies, and renames. If the working directory has two parents, you must explicitly specify a revision. Using the -r/--rev or -d/--date options, revert the given files or directories to their states as of a specific revision. Because revert does not change the working directory parents, this will cause these files to appear modified. This can be helpful to "back out" some or all of an earlier change. See :hg:`backout` for a related method. Modified files are saved with a .orig suffix before reverting. To disable these backups, use --no-backup. It is possible to store the backup files in a custom directory relative to the root of the repository by setting the ``ui.origbackuppath`` configuration option. See :hg:`help dates` for a list of formats valid for -d/--date. See :hg:`help backout` for a way to reverse the effect of an earlier changeset. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) if opts.get("date"): if opts.get("rev"): raise error.Abort(_("you can't specify a revision and a date")) opts["rev"] = cmdutil.finddate(ui, repo, opts["date"]) parent, p2 = repo.dirstate.parents() if not opts.get('rev') and p2 != nullid: # revert after merge is a trap for new users (issue2915) raise error.Abort(_('uncommitted merge with no revision specified'), hint=_("use 'hg update' or see 'hg help revert'")) rev = opts.get('rev') if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev) if (not (pats or opts.get('include') or opts.get('exclude') or opts.get('all') or opts.get('interactive'))): msg = _("no files or directories specified") if p2 != nullid: hint = _("uncommitted merge, use --all to discard all changes," " or 'hg update -C .' to abort the merge") raise error.Abort(msg, hint=hint) dirty = any(repo.status()) node = ctx.node() if node != parent: if dirty: hint = _("uncommitted changes, use --all to discard all" " changes, or 'hg update %d' to update") % ctx.rev() else: hint = _("use --all to revert all files," " or 'hg update %d' to update") % ctx.rev() elif dirty: hint = _("uncommitted changes, use --all to discard all changes") else: hint = _("use --all to revert all files") raise error.Abort(msg, hint=hint) return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)) @command( 'rollback', dryrunopts + [('f', 'force', False, _('ignore safety measures'))], helpcategory=command.CATEGORY_MAINTENANCE) def rollback(ui, repo, **opts): """roll back the last transaction (DANGEROUS) (DEPRECATED) Please use :hg:`commit --amend` instead of rollback to correct mistakes in the last commit. This command should be used with care. There is only one level of rollback, and there is no way to undo a rollback. It will also restore the dirstate at the time of the last transaction, losing any dirstate changes since that time. This command does not alter the working directory. Transactions are used to encapsulate the effects of all commands that create new changesets or propagate existing changesets into a repository. .. container:: verbose For example, the following commands are transactional, and their effects can be rolled back: - commit - import - pull - push (with this repository as the destination) - unbundle To avoid permanent data loss, rollback will refuse to rollback a commit transaction if it isn't checked out. Use --force to override this protection. The rollback command can be entirely disabled by setting the ``ui.rollback`` configuration setting to false. If you're here because you want to use rollback and it's disabled, you can re-enable the command by setting ``ui.rollback`` to true. This command is not intended for use on public repositories. Once changes are visible for pull by other users, rolling a transaction back locally is ineffective (someone else may already have pulled the changes). Furthermore, a race is possible with readers of the repository; for example an in-progress pull from the repository may fail if a rollback is performed. Returns 0 on success, 1 if no rollback data is available. """ if not ui.configbool('ui', 'rollback'): raise error.Abort(_('rollback is disabled because it is unsafe'), hint=('see `hg help -v rollback` for information')) return repo.rollback(dryrun=opts.get(r'dry_run'), force=opts.get(r'force')) @command( 'root', [] + formatteropts, intents={INTENT_READONLY}, helpcategory=command.CATEGORY_WORKING_DIRECTORY) def root(ui, repo, **opts): """print the root (top) of the current working directory Print the root directory of the current repository. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :hgpath: String. Path to the .hg directory. :storepath: String. Path to the directory holding versioned data. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) with ui.formatter('root', opts) as fm: fm.startitem() fm.write('reporoot', '%s\n', repo.root) fm.data(hgpath=repo.path, storepath=repo.spath) @command('serve', [('A', 'accesslog', '', _('name of access log file to write to'), _('FILE')), ('d', 'daemon', None, _('run server in background')), ('', 'daemon-postexec', [], _('used internally by daemon mode')), ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')), # use string type, then we can check if something was passed ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')), ('a', 'address', '', _('address to listen on (default: all interfaces)'), _('ADDR')), ('', 'prefix', '', _('prefix path to serve from (default: server root)'), _('PREFIX')), ('n', 'name', '', _('name to show in web pages (default: working directory)'), _('NAME')), ('', 'web-conf', '', _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')), ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'), _('FILE')), ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')), ('', 'stdio', None, _('for remote clients (ADVANCED)')), ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')), ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')), ('', 'style', '', _('template style to use'), _('STYLE')), ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), ('', 'certificate', '', _('SSL certificate file'), _('FILE')), ('', 'print-url', None, _('start and print only the URL'))] + subrepoopts, _('[OPTION]...'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, helpbasic=True, optionalrepo=True) def serve(ui, repo, **opts): """start stand-alone webserver Start a local HTTP repository browser and pull server. You can use this for ad-hoc sharing and browsing of repositories. It is recommended to use a real web server to serve a repository for longer periods of time. Please note that the server does not implement access control. This means that, by default, anybody can read from the server and nobody can write to it by default. Set the ``web.allow-push`` option to ``*`` to allow everybody to push to the server. You should use a real web server if you need to authenticate users. By default, the server logs accesses to stdout and errors to stderr. Use the -A/--accesslog and -E/--errorlog options to log to files. To have the server choose a free port number to listen on, specify a port number of 0; in this case, the server will print the port number it uses. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) if opts["stdio"] and opts["cmdserver"]: raise error.Abort(_("cannot use --stdio with --cmdserver")) if opts["print_url"] and ui.verbose: raise error.Abort(_("cannot use --print-url with --verbose")) if opts["stdio"]: if repo is None: raise error.RepoError(_("there is no Mercurial repository here" " (.hg not found)")) s = wireprotoserver.sshserver(ui, repo) s.serve_forever() service = server.createservice(ui, repo, opts) return server.runservice(opts, initfn=service.init, runfn=service.run) +@command('shelve', + [('A', 'addremove', None, + _('mark new/missing files as added/removed before shelving')), + ('u', 'unknown', None, + _('store unknown files in the shelve')), + ('', 'cleanup', None, + _('delete all shelved changes')), + ('', 'date', '', + _('shelve with the specified commit date'), _('DATE')), + ('d', 'delete', None, + _('delete the named shelved change(s)')), + ('e', 'edit', False, + _('invoke editor on commit messages')), + ('k', 'keep', False, + _('shelve, but keep changes in the working directory')), + ('l', 'list', None, + _('list current shelves')), + ('m', 'message', '', + _('use text as shelve message'), _('TEXT')), + ('n', 'name', '', + _('use the given name for the shelved commit'), _('NAME')), + ('p', 'patch', None, + _('output patches for changes (provide the names of the shelved ' + 'changes as positional arguments)')), + ('i', 'interactive', None, + _('interactive mode, only works while creating a shelve')), + ('', 'stat', None, + _('output diffstat-style summary of changes (provide the names of ' + 'the shelved changes as positional arguments)') + )] + cmdutil.walkopts, + _('hg shelve [OPTION]... [FILE]...'), + helpcategory=command.CATEGORY_WORKING_DIRECTORY) +def shelve(ui, repo, *pats, **opts): + '''save and set aside changes from the working directory + + Shelving takes files that "hg status" reports as not clean, saves + the modifications to a bundle (a shelved change), and reverts the + files so that their state in the working directory becomes clean. + + To restore these changes to the working directory, using "hg + unshelve"; this will work even if you switch to a different + commit. + + When no files are specified, "hg shelve" saves all not-clean + files. If specific files or directories are named, only changes to + those files are shelved. + + In bare shelve (when no files are specified, without interactive, + include and exclude option), shelving remembers information if the + working directory was on newly created branch, in other words working + directory was on different branch than its first parent. In this + situation unshelving restores branch information to the working directory. + + Each shelved change has a name that makes it easier to find later. + The name of a shelved change defaults to being based on the active + bookmark, or if there is no active bookmark, the current named + branch. To specify a different name, use ``--name``. + + To see a list of existing shelved changes, use the ``--list`` + option. For each shelved change, this will print its name, age, + and description; use ``--patch`` or ``--stat`` for more details. + + To delete specific shelved changes, use ``--delete``. To delete + all shelved changes, use ``--cleanup``. + ''' + opts = pycompat.byteskwargs(opts) + allowables = [ + ('addremove', {'create'}), # 'create' is pseudo action + ('unknown', {'create'}), + ('cleanup', {'cleanup'}), +# ('date', {'create'}), # ignored for passing '--date "0 0"' in tests + ('delete', {'delete'}), + ('edit', {'create'}), + ('keep', {'create'}), + ('list', {'list'}), + ('message', {'create'}), + ('name', {'create'}), + ('patch', {'patch', 'list'}), + ('stat', {'stat', 'list'}), + ] + def checkopt(opt): + if opts.get(opt): + for i, allowable in allowables: + if opts[i] and opt not in allowable: + raise error.Abort(_("options '--%s' and '--%s' may not be " + "used together") % (opt, i)) + return True + if checkopt('cleanup'): + if pats: + raise error.Abort(_("cannot specify names when using '--cleanup'")) + return shelvemod.cleanupcmd(ui, repo) + elif checkopt('delete'): + return shelvemod.deletecmd(ui, repo, pats) + elif checkopt('list'): + return shelvemod.listcmd(ui, repo, pats, opts) + elif checkopt('patch') or checkopt('stat'): + return shelvemod.patchcmds(ui, repo, pats, opts) + else: + return shelvemod.createcmd(ui, repo, pats, opts) + _NOTTERSE = 'nothing' @command('status|st', [('A', 'all', None, _('show status of all files')), ('m', 'modified', None, _('show only modified files')), ('a', 'added', None, _('show only added files')), ('r', 'removed', None, _('show only removed files')), ('d', 'deleted', None, _('show only deleted (but tracked) files')), ('c', 'clean', None, _('show only files without changes')), ('u', 'unknown', None, _('show only unknown (not tracked) files')), ('i', 'ignored', None, _('show only ignored files')), ('n', 'no-status', None, _('hide status prefix')), ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')), ('C', 'copies', None, _('show source of copied files')), ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), ('', 'rev', [], _('show difference from revision'), _('REV')), ('', 'change', '', _('list the changed files of a revision'), _('REV')), ] + walkopts + subrepoopts + formatteropts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, helpbasic=True, inferrepo=True, intents={INTENT_READONLY}) def status(ui, repo, *pats, **opts): """show changed files in the working directory Show status of files in the repository. If names are given, only files that match are shown. Files that are clean or ignored or the source of a copy/move operation, are not listed unless -c/--clean, -i/--ignored, -C/--copies or -A/--all are given. Unless options described with "show only ..." are given, the options -mardu are used. Option -q/--quiet hides untracked (unknown and ignored) files unless explicitly requested with -u/--unknown or -i/--ignored. .. note:: :hg:`status` may appear to disagree with diff if permissions have changed or a merge has occurred. The standard diff format does not report permission changes and diff only reports changes relative to one merge parent. If one revision is given, it is used as the base revision. If two revisions are given, the differences between them are shown. The --change option can also be used as a shortcut to list the changed files of a revision from its first parent. The codes used to show the status of files are:: M = modified A = added R = removed C = clean ! = missing (deleted by non-hg command, but still tracked) ? = not tracked I = ignored = origin of the previous file (with --copies) .. container:: verbose The -t/--terse option abbreviates the output by showing only the directory name if all the files in it share the same status. The option takes an argument indicating the statuses to abbreviate: 'm' for 'modified', 'a' for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean. It abbreviates only those statuses which are passed. Note that clean and ignored files are not displayed with '--terse ic' unless the -c/--clean and -i/--ignored options are also used. The -v/--verbose option shows information when the repository is in an unfinished merge, shelve, rebase state etc. You can have this behavior turned on by default by enabling the ``commands.status.verbose`` option. You can skip displaying some of these states by setting ``commands.status.skipstates`` to one or more of: 'bisect', 'graft', 'histedit', 'merge', 'rebase', or 'unshelve'. Template: The following keywords are supported in addition to the common template keywords and functions. See also :hg:`help templates`. :path: String. Repository-absolute path of the file. :source: String. Repository-absolute path of the file originated from. Available if ``--copies`` is specified. :status: String. Character denoting file's status. Examples: - show changes in the working directory relative to a changeset:: hg status --rev 9353 - show changes in the working directory relative to the current directory (see :hg:`help patterns` for more information):: hg status re: - show all changes including copies in an existing changeset:: hg status --copies --change 9353 - get a NUL separated list of added files, suitable for xargs:: hg status -an0 - show more information about the repository status, abbreviating added, removed, modified, deleted, and untracked paths:: hg status -v -t mardu Returns 0 on success. """ opts = pycompat.byteskwargs(opts) revs = opts.get('rev') change = opts.get('change') terse = opts.get('terse') if terse is _NOTTERSE: if revs: terse = '' else: terse = ui.config('commands', 'status.terse') if revs and change: msg = _('cannot specify --rev and --change at the same time') raise error.Abort(msg) elif revs and terse: msg = _('cannot use --terse with --rev') raise error.Abort(msg) elif change: repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn') ctx2 = scmutil.revsingle(repo, change, None) ctx1 = ctx2.p1() else: repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn') ctx1, ctx2 = scmutil.revpair(repo, revs) forcerelativevalue = None if ui.hasconfig('commands', 'status.relative'): forcerelativevalue = ui.configbool('commands', 'status.relative') uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats), forcerelativevalue=forcerelativevalue) if opts.get('print0'): end = '\0' else: end = '\n' copy = {} states = 'modified added removed deleted unknown ignored clean'.split() show = [k for k in states if opts.get(k)] if opts.get('all'): show += ui.quiet and (states[:4] + ['clean']) or states if not show: if ui.quiet: show = states[:4] else: show = states[:5] m = scmutil.match(ctx2, pats, opts) if terse: # we need to compute clean and unknown to terse stat = repo.status(ctx1.node(), ctx2.node(), m, 'ignored' in show or 'i' in terse, clean=True, unknown=True, listsubrepos=opts.get('subrepos')) stat = cmdutil.tersedir(stat, terse) else: stat = repo.status(ctx1.node(), ctx2.node(), m, 'ignored' in show, 'clean' in show, 'unknown' in show, opts.get('subrepos')) changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat) if (opts.get('all') or opts.get('copies') or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'): copy = copies.pathcopies(ctx1, ctx2, m) ui.pager('status') fm = ui.formatter('status', opts) fmt = '%s' + end showchar = not opts.get('no_status') for state, char, files in changestates: if state in show: label = 'status.' + state for f in files: fm.startitem() fm.context(ctx=ctx2) fm.data(path=f) fm.condwrite(showchar, 'status', '%s ', char, label=label) fm.plain(fmt % uipathfn(f), label=label) if f in copy: fm.data(source=copy[f]) fm.plain((' %s' + end) % uipathfn(copy[f]), label='status.copied') if ((ui.verbose or ui.configbool('commands', 'status.verbose')) and not ui.plain()): cmdutil.morestatus(repo, fm) fm.end() @command('summary|sum', [('', 'remote', None, _('check for push and pull'))], '[--remote]', helpcategory=command.CATEGORY_WORKING_DIRECTORY, helpbasic=True, intents={INTENT_READONLY}) def summary(ui, repo, **opts): """summarize working directory state This generates a brief summary of the working directory state, including parents, branch, commit status, phase and available updates. With the --remote option, this will check the default paths for incoming and outgoing changes. This can be time-consuming. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) ui.pager('summary') ctx = repo[None] parents = ctx.parents() pnode = parents[0].node() marks = [] try: ms = mergemod.mergestate.read(repo) except error.UnsupportedMergeRecords as e: s = ' '.join(e.recordtypes) ui.warn( _('warning: merge state has unsupported record types: %s\n') % s) unresolved = [] else: unresolved = list(ms.unresolved()) for p in parents: # label with log.changeset (instead of log.parent) since this # shows a working directory parent *changeset*: # i18n: column positioning for "hg summary" ui.write(_('parent: %d:%s ') % (p.rev(), p), label=logcmdutil.changesetlabels(p)) ui.write(' '.join(p.tags()), label='log.tag') if p.bookmarks(): marks.extend(p.bookmarks()) if p.rev() == -1: if not len(repo): ui.write(_(' (empty repository)')) else: ui.write(_(' (no revision checked out)')) if p.obsolete(): ui.write(_(' (obsolete)')) if p.isunstable(): instabilities = (ui.label(instability, 'trouble.%s' % instability) for instability in p.instabilities()) ui.write(' (' + ', '.join(instabilities) + ')') ui.write('\n') if p.description(): ui.status(' ' + p.description().splitlines()[0].strip() + '\n', label='log.summary') branch = ctx.branch() bheads = repo.branchheads(branch) # i18n: column positioning for "hg summary" m = _('branch: %s\n') % branch if branch != 'default': ui.write(m, label='log.branch') else: ui.status(m, label='log.branch') if marks: active = repo._activebookmark # i18n: column positioning for "hg summary" ui.write(_('bookmarks:'), label='log.bookmark') if active is not None: if active in marks: ui.write(' *' + active, label=bookmarks.activebookmarklabel) marks.remove(active) else: ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel) for m in marks: ui.write(' ' + m, label='log.bookmark') ui.write('\n', label='log.bookmark') status = repo.status(unknown=True) c = repo.dirstate.copies() copied, renamed = [], [] for d, s in c.iteritems(): if s in status.removed: status.removed.remove(s) renamed.append(d) else: copied.append(d) if d in status.added: status.added.remove(d) subs = [s for s in ctx.substate if ctx.sub(s).dirty()] labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified), (ui.label(_('%d added'), 'status.added'), status.added), (ui.label(_('%d removed'), 'status.removed'), status.removed), (ui.label(_('%d renamed'), 'status.copied'), renamed), (ui.label(_('%d copied'), 'status.copied'), copied), (ui.label(_('%d deleted'), 'status.deleted'), status.deleted), (ui.label(_('%d unknown'), 'status.unknown'), status.unknown), (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved), (ui.label(_('%d subrepos'), 'status.modified'), subs)] t = [] for l, s in labels: if s: t.append(l % len(s)) t = ', '.join(t) cleanworkdir = False if repo.vfs.exists('graftstate'): t += _(' (graft in progress)') if repo.vfs.exists('updatestate'): t += _(' (interrupted update)') elif len(parents) > 1: t += _(' (merge)') elif branch != parents[0].branch(): t += _(' (new branch)') elif (parents[0].closesbranch() and pnode in repo.branchheads(branch, closed=True)): t += _(' (head closed)') elif not (status.modified or status.added or status.removed or renamed or copied or subs): t += _(' (clean)') cleanworkdir = True elif pnode not in bheads: t += _(' (new branch head)') if parents: pendingphase = max(p.phase() for p in parents) else: pendingphase = phases.public if pendingphase > phases.newcommitphase(ui): t += ' (%s)' % phases.phasenames[pendingphase] if cleanworkdir: # i18n: column positioning for "hg summary" ui.status(_('commit: %s\n') % t.strip()) else: # i18n: column positioning for "hg summary" ui.write(_('commit: %s\n') % t.strip()) # all ancestors of branch heads - all ancestors of parent = new csets new = len(repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)) if new == 0: # i18n: column positioning for "hg summary" ui.status(_('update: (current)\n')) elif pnode not in bheads: # i18n: column positioning for "hg summary" ui.write(_('update: %d new changesets (update)\n') % new) else: # i18n: column positioning for "hg summary" ui.write(_('update: %d new changesets, %d branch heads (merge)\n') % (new, len(bheads))) t = [] draft = len(repo.revs('draft()')) if draft: t.append(_('%d draft') % draft) secret = len(repo.revs('secret()')) if secret: t.append(_('%d secret') % secret) if draft or secret: ui.status(_('phases: %s\n') % ', '.join(t)) if obsolete.isenabled(repo, obsolete.createmarkersopt): for trouble in ("orphan", "contentdivergent", "phasedivergent"): numtrouble = len(repo.revs(trouble + "()")) # We write all the possibilities to ease translation troublemsg = { "orphan": _("orphan: %d changesets"), "contentdivergent": _("content-divergent: %d changesets"), "phasedivergent": _("phase-divergent: %d changesets"), } if numtrouble > 0: ui.status(troublemsg[trouble] % numtrouble + "\n") cmdutil.summaryhooks(ui, repo) if opts.get('remote'): needsincoming, needsoutgoing = True, True else: needsincoming, needsoutgoing = False, False for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None): if i: needsincoming = True if o: needsoutgoing = True if not needsincoming and not needsoutgoing: return def getincoming(): source, branches = hg.parseurl(ui.expandpath('default')) sbranch = branches[0] try: other = hg.peer(repo, {}, source) except error.RepoError: if opts.get('remote'): raise return source, sbranch, None, None, None revs, checkout = hg.addbranchrevs(repo, other, branches, None) if revs: revs = [other.lookup(rev) for rev in revs] ui.debug('comparing with %s\n' % util.hidepassword(source)) repo.ui.pushbuffer() commoninc = discovery.findcommonincoming(repo, other, heads=revs) repo.ui.popbuffer() return source, sbranch, other, commoninc, commoninc[1] if needsincoming: source, sbranch, sother, commoninc, incoming = getincoming() else: source = sbranch = sother = commoninc = incoming = None def getoutgoing(): dest, branches = hg.parseurl(ui.expandpath('default-push', 'default')) dbranch = branches[0] revs, checkout = hg.addbranchrevs(repo, repo, branches, None) if source != dest: try: dother = hg.peer(repo, {}, dest) except error.RepoError: if opts.get('remote'): raise return dest, dbranch, None, None ui.debug('comparing with %s\n' % util.hidepassword(dest)) elif sother is None: # there is no explicit destination peer, but source one is invalid return dest, dbranch, None, None else: dother = sother if (source != dest or (sbranch is not None and sbranch != dbranch)): common = None else: common = commoninc if revs: revs = [repo.lookup(rev) for rev in revs] repo.ui.pushbuffer() outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs, commoninc=common) repo.ui.popbuffer() return dest, dbranch, dother, outgoing if needsoutgoing: dest, dbranch, dother, outgoing = getoutgoing() else: dest = dbranch = dother = outgoing = None if opts.get('remote'): t = [] if incoming: t.append(_('1 or more incoming')) o = outgoing.missing if o: t.append(_('%d outgoing') % len(o)) other = dother or sother if 'bookmarks' in other.listkeys('namespaces'): counts = bookmarks.summary(repo, other) if counts[0] > 0: t.append(_('%d incoming bookmarks') % counts[0]) if counts[1] > 0: t.append(_('%d outgoing bookmarks') % counts[1]) if t: # i18n: column positioning for "hg summary" ui.write(_('remote: %s\n') % (', '.join(t))) else: # i18n: column positioning for "hg summary" ui.status(_('remote: (synced)\n')) cmdutil.summaryremotehooks(ui, repo, opts, ((source, sbranch, sother, commoninc), (dest, dbranch, dother, outgoing))) @command('tag', [('f', 'force', None, _('force tag')), ('l', 'local', None, _('make the tag local')), ('r', 'rev', '', _('revision to tag'), _('REV')), ('', 'remove', None, _('remove a tag')), # -l/--local is already there, commitopts cannot be used ('e', 'edit', None, _('invoke editor on commit messages')), ('m', 'message', '', _('use text as commit message'), _('TEXT')), ] + commitopts2, _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'), helpcategory=command.CATEGORY_CHANGE_ORGANIZATION) def tag(ui, repo, name1, *names, **opts): """add one or more tags for the current or given revision Name a particular revision using . Tags are used to name particular revisions of the repository and are very useful to compare different revisions, to go back to significant earlier versions or to mark branch points as releases, etc. Changing an existing tag is normally disallowed; use -f/--force to override. If no revision is given, the parent of the working directory is used. To facilitate version control, distribution, and merging of tags, they are stored as a file named ".hgtags" which is managed similarly to other project files and can be hand-edited if necessary. This also means that tagging creates a new commit. The file ".hg/localtags" is used for local tags (not shared among repositories). Tag commits are usually made at the head of a branch. If the parent of the working directory is not a branch head, :hg:`tag` aborts; use -f/--force to force the tag commit to be based on a non-head changeset. See :hg:`help dates` for a list of formats valid for -d/--date. Since tag names have priority over branch names during revision lookup, using an existing branch name as a tag name is discouraged. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) with repo.wlock(), repo.lock(): rev_ = "." names = [t.strip() for t in (name1,) + names] if len(names) != len(set(names)): raise error.Abort(_('tag names must be unique')) for n in names: scmutil.checknewlabel(repo, n, 'tag') if not n: raise error.Abort(_('tag names cannot consist entirely of ' 'whitespace')) if opts.get('rev') and opts.get('remove'): raise error.Abort(_("--rev and --remove are incompatible")) if opts.get('rev'): rev_ = opts['rev'] message = opts.get('message') if opts.get('remove'): if opts.get('local'): expectedtype = 'local' else: expectedtype = 'global' for n in names: if repo.tagtype(n) == 'global': alltags = tagsmod.findglobaltags(ui, repo) if alltags[n][0] == nullid: raise error.Abort(_("tag '%s' is already removed") % n) if not repo.tagtype(n): raise error.Abort(_("tag '%s' does not exist") % n) if repo.tagtype(n) != expectedtype: if expectedtype == 'global': raise error.Abort(_("tag '%s' is not a global tag") % n) else: raise error.Abort(_("tag '%s' is not a local tag") % n) rev_ = 'null' if not message: # we don't translate commit messages message = 'Removed tag %s' % ', '.join(names) elif not opts.get('force'): for n in names: if n in repo.tags(): raise error.Abort(_("tag '%s' already exists " "(use -f to force)") % n) if not opts.get('local'): p1, p2 = repo.dirstate.parents() if p2 != nullid: raise error.Abort(_('uncommitted merge')) bheads = repo.branchheads() if not opts.get('force') and bheads and p1 not in bheads: raise error.Abort(_('working directory is not at a branch head ' '(use -f to force)')) node = scmutil.revsingle(repo, rev_).node() if not message: # we don't translate commit messages message = ('Added tag %s for changeset %s' % (', '.join(names), short(node))) date = opts.get('date') if date: date = dateutil.parsedate(date) if opts.get('remove'): editform = 'tag.remove' else: editform = 'tag.add' editor = cmdutil.getcommiteditor(editform=editform, **pycompat.strkwargs(opts)) # don't allow tagging the null rev if (not opts.get('remove') and scmutil.revsingle(repo, rev_).rev() == nullrev): raise error.Abort(_("cannot tag null revision")) tagsmod.tag(repo, names, node, message, opts.get('local'), opts.get('user'), date, editor=editor) @command( 'tags', formatteropts, '', helpcategory=command.CATEGORY_CHANGE_ORGANIZATION, intents={INTENT_READONLY}) def tags(ui, repo, **opts): """list repository tags This lists both regular and local tags. When the -v/--verbose switch is used, a third column "local" is printed for local tags. When the -q/--quiet switch is used, only the tag name is printed. .. container:: verbose Template: The following keywords are supported in addition to the common template keywords and functions such as ``{tag}``. See also :hg:`help templates`. :type: String. ``local`` for local tags. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) ui.pager('tags') fm = ui.formatter('tags', opts) hexfunc = fm.hexfunc for t, n in reversed(repo.tagslist()): hn = hexfunc(n) label = 'tags.normal' tagtype = '' if repo.tagtype(t) == 'local': label = 'tags.local' tagtype = 'local' fm.startitem() fm.context(repo=repo) fm.write('tag', '%s', t, label=label) fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s' fm.condwrite(not ui.quiet, 'rev node', fmt, repo.changelog.rev(n), hn, label=label) fm.condwrite(ui.verbose and tagtype, 'type', ' %s', tagtype, label=label) fm.plain('\n') fm.end() @command('tip', [('p', 'patch', None, _('show patch')), ('g', 'git', None, _('use git extended diff format')), ] + templateopts, _('[-p] [-g]'), helpcategory=command.CATEGORY_CHANGE_NAVIGATION) def tip(ui, repo, **opts): """show the tip revision (DEPRECATED) The tip revision (usually just called the tip) is the changeset most recently added to the repository (and therefore the most recently changed head). If you have just made a commit, that commit will be the tip. If you have just pulled changes from another repository, the tip of that repository becomes the current tip. The "tip" tag is special and cannot be renamed or assigned to a different changeset. This command is deprecated, please use :hg:`heads` instead. Returns 0 on success. """ opts = pycompat.byteskwargs(opts) displayer = logcmdutil.changesetdisplayer(ui, repo, opts) displayer.show(repo['tip']) displayer.close() @command('unbundle', [('u', 'update', None, _('update to new branch head if changesets were unbundled'))], _('[-u] FILE...'), helpcategory=command.CATEGORY_IMPORT_EXPORT) def unbundle(ui, repo, fname1, *fnames, **opts): """apply one or more bundle files Apply one or more bundle files generated by :hg:`bundle`. Returns 0 on success, 1 if an update has unresolved files. """ fnames = (fname1,) + fnames with repo.lock(): for fname in fnames: f = hg.openpath(ui, fname) gen = exchange.readbundle(ui, f, fname) if isinstance(gen, streamclone.streamcloneapplier): raise error.Abort( _('packed bundles cannot be applied with ' '"hg unbundle"'), hint=_('use "hg debugapplystreamclonebundle"')) url = 'bundle:' + fname try: txnname = 'unbundle' if not isinstance(gen, bundle2.unbundle20): txnname = 'unbundle\n%s' % util.hidepassword(url) with repo.transaction(txnname) as tr: op = bundle2.applybundle(repo, gen, tr, source='unbundle', url=url) except error.BundleUnknownFeatureError as exc: raise error.Abort( _('%s: unknown bundle feature, %s') % (fname, exc), hint=_("see https://mercurial-scm.org/" "wiki/BundleFeature for more " "information")) modheads = bundle2.combinechangegroupresults(op) return postincoming(ui, repo, modheads, opts.get(r'update'), None, None) +@command('unshelve', + [('a', 'abort', None, + _('abort an incomplete unshelve operation')), + ('c', 'continue', None, + _('continue an incomplete unshelve operation')), + ('k', 'keep', None, + _('keep shelve after unshelving')), + ('n', 'name', '', + _('restore shelved change with given name'), _('NAME')), + ('t', 'tool', '', _('specify merge tool')), + ('', 'date', '', + _('set date for temporary commits (DEPRECATED)'), _('DATE'))], + _('hg unshelve [[-n] SHELVED]'), + helpcategory=command.CATEGORY_WORKING_DIRECTORY) +def unshelve(ui, repo, *shelved, **opts): + """restore a shelved change to the working directory + + This command accepts an optional name of a shelved change to + restore. If none is given, the most recent shelved change is used. + + If a shelved change is applied successfully, the bundle that + contains the shelved changes is moved to a backup location + (.hg/shelve-backup). + + Since you can restore a shelved change on top of an arbitrary + commit, it is possible that unshelving will result in a conflict + between your changes and the commits you are unshelving onto. If + this occurs, you must resolve the conflict, then use + ``--continue`` to complete the unshelve operation. (The bundle + will not be moved until you successfully complete the unshelve.) + + (Alternatively, you can use ``--abort`` to abandon an unshelve + that causes a conflict. This reverts the unshelved changes, and + leaves the bundle in place.) + + If bare shelved change (when no files are specified, without interactive, + include and exclude option) was done on newly created branch it would + restore branch information to the working directory. + + After a successful unshelve, the shelved changes are stored in a + backup directory. Only the N most recent backups are kept. N + defaults to 10 but can be overridden using the ``shelve.maxbackups`` + configuration option. + + .. container:: verbose + + Timestamp in seconds is used to decide order of backups. More + than ``maxbackups`` backups are kept, if same timestamp + prevents from deciding exact order of them, for safety. + """ + with repo.wlock(): + return shelvemod._dounshelve(ui, repo, *shelved, **opts) + @command('update|up|checkout|co', [('C', 'clean', None, _('discard uncommitted changes (no backup)')), ('c', 'check', None, _('require clean working directory')), ('m', 'merge', None, _('merge uncommitted changes')), ('d', 'date', '', _('tipmost revision matching date'), _('DATE')), ('r', 'rev', '', _('revision'), _('REV')) ] + mergetoolopts, _('[-C|-c|-m] [-d DATE] [[-r] REV]'), helpcategory=command.CATEGORY_WORKING_DIRECTORY, helpbasic=True) def update(ui, repo, node=None, **opts): """update working directory (or switch revisions) Update the repository's working directory to the specified changeset. If no changeset is specified, update to the tip of the current named branch and move the active bookmark (see :hg:`help bookmarks`). Update sets the working directory's parent revision to the specified changeset (see :hg:`help parents`). If the changeset is not a descendant or ancestor of the working directory's parent and there are uncommitted changes, the update is aborted. With the -c/--check option, the working directory is checked for uncommitted changes; if none are found, the working directory is updated to the specified changeset. .. container:: verbose The -C/--clean, -c/--check, and -m/--merge options control what happens if the working directory contains uncommitted changes. At most of one of them can be specified. 1. If no option is specified, and if the requested changeset is an ancestor or descendant of the working directory's parent, the uncommitted changes are merged into the requested changeset and the merged result is left uncommitted. If the requested changeset is not an ancestor or descendant (that is, it is on another branch), the update is aborted and the uncommitted changes are preserved. 2. With the -m/--merge option, the update is allowed even if the requested changeset is not an ancestor or descendant of the working directory's parent. 3. With the -c/--check option, the update is aborted and the uncommitted changes are preserved. 4. With the -C/--clean option, uncommitted changes are discarded and the working directory is updated to the requested changeset. To cancel an uncommitted merge (and lose your changes), use :hg:`merge --abort`. Use null as the changeset to remove the working directory (like :hg:`clone -U`). If you want to revert just one file to an older revision, use :hg:`revert [-r REV] NAME`. See :hg:`help dates` for a list of formats valid for -d/--date. Returns 0 on success, 1 if there are unresolved files. """ rev = opts.get(r'rev') date = opts.get(r'date') clean = opts.get(r'clean') check = opts.get(r'check') merge = opts.get(r'merge') if rev and node: raise error.Abort(_("please specify just one revision")) if ui.configbool('commands', 'update.requiredest'): if not node and not rev and not date: raise error.Abort(_('you must specify a destination'), hint=_('for example: hg update ".::"')) if rev is None or rev == '': rev = node if date and rev is not None: raise error.Abort(_("you can't specify a revision and a date")) if len([x for x in (clean, check, merge) if x]) > 1: raise error.Abort(_("can only specify one of -C/--clean, -c/--check, " "or -m/--merge")) updatecheck = None if check: updatecheck = 'abort' elif merge: updatecheck = 'none' with repo.wlock(): cmdutil.clearunfinished(repo) if date: rev = cmdutil.finddate(ui, repo, date) # if we defined a bookmark, we have to remember the original name brev = rev if rev: repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn') ctx = scmutil.revsingle(repo, rev, default=None) rev = ctx.rev() hidden = ctx.hidden() overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')} with ui.configoverride(overrides, 'update'): ret = hg.updatetotally(ui, repo, rev, brev, clean=clean, updatecheck=updatecheck) if hidden: ctxstr = ctx.hex()[:12] ui.warn(_("updated to hidden changeset %s\n") % ctxstr) if ctx.obsolete(): obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx) ui.warn("(%s)\n" % obsfatemsg) return ret @command('verify', [('', 'full', False, 'perform more checks (EXPERIMENTAL)')], helpcategory=command.CATEGORY_MAINTENANCE) def verify(ui, repo, **opts): """verify the integrity of the repository Verify the integrity of the current repository. This will perform an extensive check of the repository's integrity, validating the hashes and checksums of each entry in the changelog, manifest, and tracked files, as well as the integrity of their crosslinks and indices. Please see https://mercurial-scm.org/wiki/RepositoryCorruption for more information about recovery from corruption of the repository. Returns 0 on success, 1 if errors are encountered. """ opts = pycompat.byteskwargs(opts) level = None if opts['full']: level = verifymod.VERIFY_FULL return hg.verify(repo, level) @command( 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP, norepo=True, intents={INTENT_READONLY}) def version_(ui, **opts): """output version and copyright information .. container:: verbose Template: The following keywords are supported. See also :hg:`help templates`. :extensions: List of extensions. :ver: String. Version number. And each entry of ``{extensions}`` provides the following sub-keywords in addition to ``{ver}``. :bundled: Boolean. True if included in the release. :name: String. Extension name. """ opts = pycompat.byteskwargs(opts) if ui.verbose: ui.pager('version') fm = ui.formatter("version", opts) fm.startitem() fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"), util.version()) license = _( "(see https://mercurial-scm.org for more information)\n" "\nCopyright (C) 2005-2019 Matt Mackall and others\n" "This is free software; see the source for copying conditions. " "There is NO\nwarranty; " "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ) if not ui.quiet: fm.plain(license) if ui.verbose: fm.plain(_("\nEnabled extensions:\n\n")) # format names and versions into columns names = [] vers = [] isinternals = [] for name, module in extensions.extensions(): names.append(name) vers.append(extensions.moduleversion(module) or None) isinternals.append(extensions.ismoduleinternal(module)) fn = fm.nested("extensions", tmpl='{name}\n') if names: namefmt = " %%-%ds " % max(len(n) for n in names) places = [_("external"), _("internal")] for n, v, p in zip(names, vers, isinternals): fn.startitem() fn.condwrite(ui.verbose, "name", namefmt, n) if ui.verbose: fn.plain("%s " % places[p]) fn.data(bundled=p) fn.condwrite(ui.verbose and v, "ver", "%s", v) if ui.verbose: fn.plain("\n") fn.end() fm.end() def loadcmdtable(ui, name, cmdtable): """Load command functions from specified cmdtable """ overrides = [cmd for cmd in cmdtable if cmd in table] if overrides: ui.warn(_("extension '%s' overrides commands: %s\n") % (name, " ".join(overrides))) table.update(cmdtable) diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -1,1496 +1,1499 @@ # configitems.py - centralized declaration of configuration option # # Copyright 2017 Pierre-Yves David # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import import functools import re from . import ( encoding, error, ) def loadconfigtable(ui, extname, configtable): """update config item known to the ui with the extension ones""" for section, items in sorted(configtable.items()): knownitems = ui._knownconfig.setdefault(section, itemregister()) knownkeys = set(knownitems) newkeys = set(items) for key in sorted(knownkeys & newkeys): msg = "extension '%s' overwrite config item '%s.%s'" msg %= (extname, section, key) ui.develwarn(msg, config='warn-config') knownitems.update(items) class configitem(object): """represent a known config item :section: the official config section where to find this item, :name: the official name within the section, :default: default value for this item, :alias: optional list of tuples as alternatives, :generic: this is a generic definition, match name using regular expression. """ def __init__(self, section, name, default=None, alias=(), generic=False, priority=0): self.section = section self.name = name self.default = default self.alias = list(alias) self.generic = generic self.priority = priority self._re = None if generic: self._re = re.compile(self.name) class itemregister(dict): """A specialized dictionary that can handle wild-card selection""" def __init__(self): super(itemregister, self).__init__() self._generics = set() def update(self, other): super(itemregister, self).update(other) self._generics.update(other._generics) def __setitem__(self, key, item): super(itemregister, self).__setitem__(key, item) if item.generic: self._generics.add(item) def get(self, key): baseitem = super(itemregister, self).get(key) if baseitem is not None and not baseitem.generic: return baseitem # search for a matching generic item generics = sorted(self._generics, key=(lambda x: (x.priority, x.name))) for item in generics: # we use 'match' instead of 'search' to make the matching simpler # for people unfamiliar with regular expression. Having the match # rooted to the start of the string will produce less surprising # result for user writing simple regex for sub-attribute. # # For example using "color\..*" match produces an unsurprising # result, while using search could suddenly match apparently # unrelated configuration that happens to contains "color." # anywhere. This is a tradeoff where we favor requiring ".*" on # some match to avoid the need to prefix most pattern with "^". # The "^" seems more error prone. if item._re.match(key): return item return None coreitems = {} def _register(configtable, *args, **kwargs): item = configitem(*args, **kwargs) section = configtable.setdefault(item.section, itemregister()) if item.name in section: msg = "duplicated config item registration for '%s.%s'" raise error.ProgrammingError(msg % (item.section, item.name)) section[item.name] = item # special value for case where the default is derived from other values dynamicdefault = object() # Registering actual config items def getitemregister(configtable): f = functools.partial(_register, configtable) # export pseudo enum as configitem.* f.dynamicdefault = dynamicdefault return f coreconfigitem = getitemregister(coreitems) def _registerdiffopts(section, configprefix=''): coreconfigitem(section, configprefix + 'nodates', default=False, ) coreconfigitem(section, configprefix + 'showfunc', default=False, ) coreconfigitem(section, configprefix + 'unified', default=None, ) coreconfigitem(section, configprefix + 'git', default=False, ) coreconfigitem(section, configprefix + 'ignorews', default=False, ) coreconfigitem(section, configprefix + 'ignorewsamount', default=False, ) coreconfigitem(section, configprefix + 'ignoreblanklines', default=False, ) coreconfigitem(section, configprefix + 'ignorewseol', default=False, ) coreconfigitem(section, configprefix + 'nobinary', default=False, ) coreconfigitem(section, configprefix + 'noprefix', default=False, ) coreconfigitem(section, configprefix + 'word-diff', default=False, ) coreconfigitem('alias', '.*', default=dynamicdefault, generic=True, ) coreconfigitem('auth', 'cookiefile', default=None, ) _registerdiffopts(section='annotate') # bookmarks.pushing: internal hack for discovery coreconfigitem('bookmarks', 'pushing', default=list, ) # bundle.mainreporoot: internal hack for bundlerepo coreconfigitem('bundle', 'mainreporoot', default='', ) coreconfigitem('censor', 'policy', default='abort', ) coreconfigitem('chgserver', 'idletimeout', default=3600, ) coreconfigitem('chgserver', 'skiphash', default=False, ) coreconfigitem('cmdserver', 'log', default=None, ) coreconfigitem('cmdserver', 'max-log-files', default=7, ) coreconfigitem('cmdserver', 'max-log-size', default='1 MB', ) coreconfigitem('cmdserver', 'max-repo-cache', default=0, ) coreconfigitem('cmdserver', 'message-encodings', default=list, ) coreconfigitem('cmdserver', 'track-log', default=lambda: ['chgserver', 'cmdserver', 'repocache'], ) coreconfigitem('color', '.*', default=None, generic=True, ) coreconfigitem('color', 'mode', default='auto', ) coreconfigitem('color', 'pagermode', default=dynamicdefault, ) _registerdiffopts(section='commands', configprefix='commit.interactive.') coreconfigitem('commands', 'commit.post-status', default=False, ) coreconfigitem('commands', 'grep.all-files', default=False, ) coreconfigitem('commands', 'resolve.confirm', default=False, ) coreconfigitem('commands', 'resolve.explicit-re-merge', default=False, ) coreconfigitem('commands', 'resolve.mark-check', default='none', ) _registerdiffopts(section='commands', configprefix='revert.interactive.') coreconfigitem('commands', 'show.aliasprefix', default=list, ) coreconfigitem('commands', 'status.relative', default=False, ) coreconfigitem('commands', 'status.skipstates', default=[], ) coreconfigitem('commands', 'status.terse', default='', ) coreconfigitem('commands', 'status.verbose', default=False, ) coreconfigitem('commands', 'update.check', default=None, ) coreconfigitem('commands', 'update.requiredest', default=False, ) coreconfigitem('committemplate', '.*', default=None, generic=True, ) coreconfigitem('convert', 'bzr.saverev', default=True, ) coreconfigitem('convert', 'cvsps.cache', default=True, ) coreconfigitem('convert', 'cvsps.fuzz', default=60, ) coreconfigitem('convert', 'cvsps.logencoding', default=None, ) coreconfigitem('convert', 'cvsps.mergefrom', default=None, ) coreconfigitem('convert', 'cvsps.mergeto', default=None, ) coreconfigitem('convert', 'git.committeractions', default=lambda: ['messagedifferent'], ) coreconfigitem('convert', 'git.extrakeys', default=list, ) coreconfigitem('convert', 'git.findcopiesharder', default=False, ) coreconfigitem('convert', 'git.remoteprefix', default='remote', ) coreconfigitem('convert', 'git.renamelimit', default=400, ) coreconfigitem('convert', 'git.saverev', default=True, ) coreconfigitem('convert', 'git.similarity', default=50, ) coreconfigitem('convert', 'git.skipsubmodules', default=False, ) coreconfigitem('convert', 'hg.clonebranches', default=False, ) coreconfigitem('convert', 'hg.ignoreerrors', default=False, ) coreconfigitem('convert', 'hg.revs', default=None, ) coreconfigitem('convert', 'hg.saverev', default=False, ) coreconfigitem('convert', 'hg.sourcename', default=None, ) coreconfigitem('convert', 'hg.startrev', default=None, ) coreconfigitem('convert', 'hg.tagsbranch', default='default', ) coreconfigitem('convert', 'hg.usebranchnames', default=True, ) coreconfigitem('convert', 'ignoreancestorcheck', default=False, ) coreconfigitem('convert', 'localtimezone', default=False, ) coreconfigitem('convert', 'p4.encoding', default=dynamicdefault, ) coreconfigitem('convert', 'p4.startrev', default=0, ) coreconfigitem('convert', 'skiptags', default=False, ) coreconfigitem('convert', 'svn.debugsvnlog', default=True, ) coreconfigitem('convert', 'svn.trunk', default=None, ) coreconfigitem('convert', 'svn.tags', default=None, ) coreconfigitem('convert', 'svn.branches', default=None, ) coreconfigitem('convert', 'svn.startrev', default=0, ) coreconfigitem('debug', 'dirstate.delaywrite', default=0, ) coreconfigitem('defaults', '.*', default=None, generic=True, ) coreconfigitem('devel', 'all-warnings', default=False, ) coreconfigitem('devel', 'bundle2.debug', default=False, ) coreconfigitem('devel', 'bundle.delta', default='', ) coreconfigitem('devel', 'cache-vfs', default=None, ) coreconfigitem('devel', 'check-locks', default=False, ) coreconfigitem('devel', 'check-relroot', default=False, ) coreconfigitem('devel', 'default-date', default=None, ) coreconfigitem('devel', 'deprec-warn', default=False, ) coreconfigitem('devel', 'disableloaddefaultcerts', default=False, ) coreconfigitem('devel', 'warn-empty-changegroup', default=False, ) coreconfigitem('devel', 'legacy.exchange', default=list, ) coreconfigitem('devel', 'servercafile', default='', ) coreconfigitem('devel', 'serverexactprotocol', default='', ) coreconfigitem('devel', 'serverrequirecert', default=False, ) coreconfigitem('devel', 'strip-obsmarkers', default=True, ) coreconfigitem('devel', 'warn-config', default=None, ) coreconfigitem('devel', 'warn-config-default', default=None, ) coreconfigitem('devel', 'user.obsmarker', default=None, ) coreconfigitem('devel', 'warn-config-unknown', default=None, ) coreconfigitem('devel', 'debug.copies', default=False, ) coreconfigitem('devel', 'debug.extensions', default=False, ) coreconfigitem('devel', 'debug.peer-request', default=False, ) _registerdiffopts(section='diff') coreconfigitem('email', 'bcc', default=None, ) coreconfigitem('email', 'cc', default=None, ) coreconfigitem('email', 'charsets', default=list, ) coreconfigitem('email', 'from', default=None, ) coreconfigitem('email', 'method', default='smtp', ) coreconfigitem('email', 'reply-to', default=None, ) coreconfigitem('email', 'to', default=None, ) coreconfigitem('experimental', 'archivemetatemplate', default=dynamicdefault, ) coreconfigitem('experimental', 'auto-publish', default='publish', ) coreconfigitem('experimental', 'bundle-phases', default=False, ) coreconfigitem('experimental', 'bundle2-advertise', default=True, ) coreconfigitem('experimental', 'bundle2-output-capture', default=False, ) coreconfigitem('experimental', 'bundle2.pushback', default=False, ) coreconfigitem('experimental', 'bundle2lazylocking', default=False, ) coreconfigitem('experimental', 'bundlecomplevel', default=None, ) coreconfigitem('experimental', 'bundlecomplevel.bzip2', default=None, ) coreconfigitem('experimental', 'bundlecomplevel.gzip', default=None, ) coreconfigitem('experimental', 'bundlecomplevel.none', default=None, ) coreconfigitem('experimental', 'bundlecomplevel.zstd', default=None, ) coreconfigitem('experimental', 'changegroup3', default=False, ) coreconfigitem('experimental', 'cleanup-as-archived', default=False, ) coreconfigitem('experimental', 'clientcompressionengines', default=list, ) coreconfigitem('experimental', 'copytrace', default='on', ) coreconfigitem('experimental', 'copytrace.movecandidateslimit', default=100, ) coreconfigitem('experimental', 'copytrace.sourcecommitlimit', default=100, ) coreconfigitem('experimental', 'copies.read-from', default="filelog-only", ) coreconfigitem('experimental', 'copies.write-to', default='filelog-only', ) coreconfigitem('experimental', 'crecordtest', default=None, ) coreconfigitem('experimental', 'directaccess', default=False, ) coreconfigitem('experimental', 'directaccess.revnums', default=False, ) coreconfigitem('experimental', 'editortmpinhg', default=False, ) coreconfigitem('experimental', 'evolution', default=list, ) coreconfigitem('experimental', 'evolution.allowdivergence', default=False, alias=[('experimental', 'allowdivergence')] ) coreconfigitem('experimental', 'evolution.allowunstable', default=None, ) coreconfigitem('experimental', 'evolution.createmarkers', default=None, ) coreconfigitem('experimental', 'evolution.effect-flags', default=True, alias=[('experimental', 'effect-flags')] ) coreconfigitem('experimental', 'evolution.exchange', default=None, ) coreconfigitem('experimental', 'evolution.bundle-obsmarker', default=False, ) coreconfigitem('experimental', 'log.topo', default=False, ) coreconfigitem('experimental', 'evolution.report-instabilities', default=True, ) coreconfigitem('experimental', 'evolution.track-operation', default=True, ) # repo-level config to exclude a revset visibility # # The target use case is to use `share` to expose different subset of the same # repository, especially server side. See also `server.view`. coreconfigitem('experimental', 'extra-filter-revs', default=None, ) coreconfigitem('experimental', 'maxdeltachainspan', default=-1, ) coreconfigitem('experimental', 'mergetempdirprefix', default=None, ) coreconfigitem('experimental', 'mmapindexthreshold', default=None, ) coreconfigitem('experimental', 'narrow', default=False, ) coreconfigitem('experimental', 'nonnormalparanoidcheck', default=False, ) coreconfigitem('experimental', 'exportableenviron', default=list, ) coreconfigitem('experimental', 'extendedheader.index', default=None, ) coreconfigitem('experimental', 'extendedheader.similarity', default=False, ) coreconfigitem('experimental', 'graphshorten', default=False, ) coreconfigitem('experimental', 'graphstyle.parent', default=dynamicdefault, ) coreconfigitem('experimental', 'graphstyle.missing', default=dynamicdefault, ) coreconfigitem('experimental', 'graphstyle.grandparent', default=dynamicdefault, ) coreconfigitem('experimental', 'hook-track-tags', default=False, ) coreconfigitem('experimental', 'httppeer.advertise-v2', default=False, ) coreconfigitem('experimental', 'httppeer.v2-encoder-order', default=None, ) coreconfigitem('experimental', 'httppostargs', default=False, ) coreconfigitem('experimental', 'mergedriver', default=None, ) coreconfigitem('experimental', 'nointerrupt', default=False) coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True) coreconfigitem('experimental', 'obsmarkers-exchange-debug', default=False, ) coreconfigitem('experimental', 'remotenames', default=False, ) coreconfigitem('experimental', 'removeemptydirs', default=True, ) coreconfigitem('experimental', 'revert.interactive.select-to-keep', default=False, ) coreconfigitem('experimental', 'revisions.prefixhexnode', default=False, ) coreconfigitem('experimental', 'revlogv2', default=None, ) coreconfigitem('experimental', 'revisions.disambiguatewithin', default=None, ) coreconfigitem('experimental', 'server.filesdata.recommended-batch-size', default=50000, ) coreconfigitem('experimental', 'server.manifestdata.recommended-batch-size', default=100000, ) coreconfigitem('experimental', 'server.stream-narrow-clones', default=False, ) coreconfigitem('experimental', 'single-head-per-branch', default=False, ) coreconfigitem('experimental', 'sshserver.support-v2', default=False, ) coreconfigitem('experimental', 'sparse-read', default=False, ) coreconfigitem('experimental', 'sparse-read.density-threshold', default=0.50, ) coreconfigitem('experimental', 'sparse-read.min-gap-size', default='65K', ) coreconfigitem('experimental', 'treemanifest', default=False, ) coreconfigitem('experimental', 'update.atomic-file', default=False, ) coreconfigitem('experimental', 'sshpeer.advertise-v2', default=False, ) coreconfigitem('experimental', 'web.apiserver', default=False, ) coreconfigitem('experimental', 'web.api.http-v2', default=False, ) coreconfigitem('experimental', 'web.api.debugreflect', default=False, ) coreconfigitem('experimental', 'worker.wdir-get-thread-safe', default=False, ) coreconfigitem('experimental', 'xdiff', default=False, ) coreconfigitem('extensions', '.*', default=None, generic=True, ) coreconfigitem('extdata', '.*', default=None, generic=True, ) coreconfigitem('format', 'bookmarks-in-store', default=False, ) coreconfigitem('format', 'chunkcachesize', default=None, ) coreconfigitem('format', 'dotencode', default=True, ) coreconfigitem('format', 'generaldelta', default=False, ) coreconfigitem('format', 'manifestcachesize', default=None, ) coreconfigitem('format', 'maxchainlen', default=dynamicdefault, ) coreconfigitem('format', 'obsstore-version', default=None, ) coreconfigitem('format', 'sparse-revlog', default=True, ) coreconfigitem('format', 'revlog-compression', default='zlib', alias=[('experimental', 'format.compression')] ) coreconfigitem('format', 'usefncache', default=True, ) coreconfigitem('format', 'usegeneraldelta', default=True, ) coreconfigitem('format', 'usestore', default=True, ) coreconfigitem('format', 'internal-phase', default=False, ) coreconfigitem('fsmonitor', 'warn_when_unused', default=True, ) coreconfigitem('fsmonitor', 'warn_update_file_count', default=50000, ) coreconfigitem('help', br'hidden-command\..*', default=False, generic=True, ) coreconfigitem('help', br'hidden-topic\..*', default=False, generic=True, ) coreconfigitem('hooks', '.*', default=dynamicdefault, generic=True, ) coreconfigitem('hgweb-paths', '.*', default=list, generic=True, ) coreconfigitem('hostfingerprints', '.*', default=list, generic=True, ) coreconfigitem('hostsecurity', 'ciphers', default=None, ) coreconfigitem('hostsecurity', 'disabletls10warning', default=False, ) coreconfigitem('hostsecurity', 'minimumprotocol', default=dynamicdefault, ) coreconfigitem('hostsecurity', '.*:minimumprotocol$', default=dynamicdefault, generic=True, ) coreconfigitem('hostsecurity', '.*:ciphers$', default=dynamicdefault, generic=True, ) coreconfigitem('hostsecurity', '.*:fingerprints$', default=list, generic=True, ) coreconfigitem('hostsecurity', '.*:verifycertsfile$', default=None, generic=True, ) coreconfigitem('http_proxy', 'always', default=False, ) coreconfigitem('http_proxy', 'host', default=None, ) coreconfigitem('http_proxy', 'no', default=list, ) coreconfigitem('http_proxy', 'passwd', default=None, ) coreconfigitem('http_proxy', 'user', default=None, ) coreconfigitem('http', 'timeout', default=None, ) coreconfigitem('logtoprocess', 'commandexception', default=None, ) coreconfigitem('logtoprocess', 'commandfinish', default=None, ) coreconfigitem('logtoprocess', 'command', default=None, ) coreconfigitem('logtoprocess', 'develwarn', default=None, ) coreconfigitem('logtoprocess', 'uiblocked', default=None, ) coreconfigitem('merge', 'checkunknown', default='abort', ) coreconfigitem('merge', 'checkignored', default='abort', ) coreconfigitem('experimental', 'merge.checkpathconflicts', default=False, ) coreconfigitem('merge', 'followcopies', default=True, ) coreconfigitem('merge', 'on-failure', default='continue', ) coreconfigitem('merge', 'preferancestor', default=lambda: ['*'], ) coreconfigitem('merge', 'strict-capability-check', default=False, ) coreconfigitem('merge-tools', '.*', default=None, generic=True, ) coreconfigitem('merge-tools', br'.*\.args$', default="$local $base $other", generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.binary$', default=False, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.check$', default=list, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.checkchanged$', default=False, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.executable$', default=dynamicdefault, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.fixeol$', default=False, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.gui$', default=False, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.mergemarkers$', default='basic', generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$', default=dynamicdefault, # take from ui.mergemarkertemplate generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.priority$', default=0, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.premerge$', default=dynamicdefault, generic=True, priority=-1, ) coreconfigitem('merge-tools', br'.*\.symlink$', default=False, generic=True, priority=-1, ) coreconfigitem('pager', 'attend-.*', default=dynamicdefault, generic=True, ) coreconfigitem('pager', 'ignore', default=list, ) coreconfigitem('pager', 'pager', default=dynamicdefault, ) coreconfigitem('patch', 'eol', default='strict', ) coreconfigitem('patch', 'fuzz', default=2, ) coreconfigitem('paths', 'default', default=None, ) coreconfigitem('paths', 'default-push', default=None, ) coreconfigitem('paths', '.*', default=None, generic=True, ) coreconfigitem('phases', 'checksubrepos', default='follow', ) coreconfigitem('phases', 'new-commit', default='draft', ) coreconfigitem('phases', 'publish', default=True, ) coreconfigitem('profiling', 'enabled', default=False, ) coreconfigitem('profiling', 'format', default='text', ) coreconfigitem('profiling', 'freq', default=1000, ) coreconfigitem('profiling', 'limit', default=30, ) coreconfigitem('profiling', 'nested', default=0, ) coreconfigitem('profiling', 'output', default=None, ) coreconfigitem('profiling', 'showmax', default=0.999, ) coreconfigitem('profiling', 'showmin', default=dynamicdefault, ) coreconfigitem('profiling', 'showtime', default=True, ) coreconfigitem('profiling', 'sort', default='inlinetime', ) coreconfigitem('profiling', 'statformat', default='hotpath', ) coreconfigitem('profiling', 'time-track', default=dynamicdefault, ) coreconfigitem('profiling', 'type', default='stat', ) coreconfigitem('progress', 'assume-tty', default=False, ) coreconfigitem('progress', 'changedelay', default=1, ) coreconfigitem('progress', 'clear-complete', default=True, ) coreconfigitem('progress', 'debug', default=False, ) coreconfigitem('progress', 'delay', default=3, ) coreconfigitem('progress', 'disable', default=False, ) coreconfigitem('progress', 'estimateinterval', default=60.0, ) coreconfigitem('progress', 'format', default=lambda: ['topic', 'bar', 'number', 'estimate'], ) coreconfigitem('progress', 'refresh', default=0.1, ) coreconfigitem('progress', 'width', default=dynamicdefault, ) coreconfigitem('push', 'pushvars.server', default=False, ) coreconfigitem('rewrite', 'backup-bundle', default=True, alias=[('ui', 'history-editing-backup')], ) coreconfigitem('rewrite', 'update-timestamp', default=False, ) coreconfigitem('storage', 'new-repo-backend', default='revlogv1', ) coreconfigitem('storage', 'revlog.optimize-delta-parent-choice', default=True, alias=[('format', 'aggressivemergedeltas')], ) coreconfigitem('storage', 'revlog.reuse-external-delta', default=True, ) coreconfigitem('storage', 'revlog.reuse-external-delta-parent', default=None, ) coreconfigitem('storage', 'revlog.zlib.level', default=None, ) coreconfigitem('storage', 'revlog.zstd.level', default=None, ) coreconfigitem('server', 'bookmarks-pushkey-compat', default=True, ) coreconfigitem('server', 'bundle1', default=True, ) coreconfigitem('server', 'bundle1gd', default=None, ) coreconfigitem('server', 'bundle1.pull', default=None, ) coreconfigitem('server', 'bundle1gd.pull', default=None, ) coreconfigitem('server', 'bundle1.push', default=None, ) coreconfigitem('server', 'bundle1gd.push', default=None, ) coreconfigitem('server', 'bundle2.stream', default=True, alias=[('experimental', 'bundle2.stream')] ) coreconfigitem('server', 'compressionengines', default=list, ) coreconfigitem('server', 'concurrent-push-mode', default='strict', ) coreconfigitem('server', 'disablefullbundle', default=False, ) coreconfigitem('server', 'maxhttpheaderlen', default=1024, ) coreconfigitem('server', 'pullbundle', default=False, ) coreconfigitem('server', 'preferuncompressed', default=False, ) coreconfigitem('server', 'streamunbundle', default=False, ) coreconfigitem('server', 'uncompressed', default=True, ) coreconfigitem('server', 'uncompressedallowsecret', default=False, ) coreconfigitem('server', 'view', default='served', ) coreconfigitem('server', 'validate', default=False, ) coreconfigitem('server', 'zliblevel', default=-1, ) coreconfigitem('server', 'zstdlevel', default=3, ) coreconfigitem('share', 'pool', default=None, ) coreconfigitem('share', 'poolnaming', default='identity', ) +coreconfigitem('shelve','maxbackups', + default=10, +) coreconfigitem('smtp', 'host', default=None, ) coreconfigitem('smtp', 'local_hostname', default=None, ) coreconfigitem('smtp', 'password', default=None, ) coreconfigitem('smtp', 'port', default=dynamicdefault, ) coreconfigitem('smtp', 'tls', default='none', ) coreconfigitem('smtp', 'username', default=None, ) coreconfigitem('sparse', 'missingwarning', default=True, ) coreconfigitem('subrepos', 'allowed', default=dynamicdefault, # to make backporting simpler ) coreconfigitem('subrepos', 'hg:allowed', default=dynamicdefault, ) coreconfigitem('subrepos', 'git:allowed', default=dynamicdefault, ) coreconfigitem('subrepos', 'svn:allowed', default=dynamicdefault, ) coreconfigitem('templates', '.*', default=None, generic=True, ) coreconfigitem('templateconfig', '.*', default=dynamicdefault, generic=True, ) coreconfigitem('trusted', 'groups', default=list, ) coreconfigitem('trusted', 'users', default=list, ) coreconfigitem('ui', '_usedassubrepo', default=False, ) coreconfigitem('ui', 'allowemptycommit', default=False, ) coreconfigitem('ui', 'archivemeta', default=True, ) coreconfigitem('ui', 'askusername', default=False, ) coreconfigitem('ui', 'clonebundlefallback', default=False, ) coreconfigitem('ui', 'clonebundleprefers', default=list, ) coreconfigitem('ui', 'clonebundles', default=True, ) coreconfigitem('ui', 'color', default='auto', ) coreconfigitem('ui', 'commitsubrepos', default=False, ) coreconfigitem('ui', 'debug', default=False, ) coreconfigitem('ui', 'debugger', default=None, ) coreconfigitem('ui', 'editor', default=dynamicdefault, ) coreconfigitem('ui', 'fallbackencoding', default=None, ) coreconfigitem('ui', 'forcecwd', default=None, ) coreconfigitem('ui', 'forcemerge', default=None, ) coreconfigitem('ui', 'formatdebug', default=False, ) coreconfigitem('ui', 'formatjson', default=False, ) coreconfigitem('ui', 'formatted', default=None, ) coreconfigitem('ui', 'graphnodetemplate', default=None, ) coreconfigitem('ui', 'interactive', default=None, ) coreconfigitem('ui', 'interface', default=None, ) coreconfigitem('ui', 'interface.chunkselector', default=None, ) coreconfigitem('ui', 'large-file-limit', default=10000000, ) coreconfigitem('ui', 'logblockedtimes', default=False, ) coreconfigitem('ui', 'logtemplate', default=None, ) coreconfigitem('ui', 'merge', default=None, ) coreconfigitem('ui', 'mergemarkers', default='basic', ) coreconfigitem('ui', 'mergemarkertemplate', default=('{node|short} ' '{ifeq(tags, "tip", "", ' 'ifeq(tags, "", "", "{tags} "))}' '{if(bookmarks, "{bookmarks} ")}' '{ifeq(branch, "default", "", "{branch} ")}' '- {author|user}: {desc|firstline}') ) coreconfigitem('ui', 'message-output', default='stdio', ) coreconfigitem('ui', 'nontty', default=False, ) coreconfigitem('ui', 'origbackuppath', default=None, ) coreconfigitem('ui', 'paginate', default=True, ) coreconfigitem('ui', 'patch', default=None, ) coreconfigitem('ui', 'pre-merge-tool-output-template', default=None, ) coreconfigitem('ui', 'portablefilenames', default='warn', ) coreconfigitem('ui', 'promptecho', default=False, ) coreconfigitem('ui', 'quiet', default=False, ) coreconfigitem('ui', 'quietbookmarkmove', default=False, ) coreconfigitem('ui', 'relative-paths', default='legacy', ) coreconfigitem('ui', 'remotecmd', default='hg', ) coreconfigitem('ui', 'report_untrusted', default=True, ) coreconfigitem('ui', 'rollback', default=True, ) coreconfigitem('ui', 'signal-safe-lock', default=True, ) coreconfigitem('ui', 'slash', default=False, ) coreconfigitem('ui', 'ssh', default='ssh', ) coreconfigitem('ui', 'ssherrorhint', default=None, ) coreconfigitem('ui', 'statuscopies', default=False, ) coreconfigitem('ui', 'strict', default=False, ) coreconfigitem('ui', 'style', default='', ) coreconfigitem('ui', 'supportcontact', default=None, ) coreconfigitem('ui', 'textwidth', default=78, ) coreconfigitem('ui', 'timeout', default='600', ) coreconfigitem('ui', 'timeout.warn', default=0, ) coreconfigitem('ui', 'traceback', default=False, ) coreconfigitem('ui', 'tweakdefaults', default=False, ) coreconfigitem('ui', 'username', alias=[('ui', 'user')] ) coreconfigitem('ui', 'verbose', default=False, ) coreconfigitem('verify', 'skipflags', default=None, ) coreconfigitem('web', 'allowbz2', default=False, ) coreconfigitem('web', 'allowgz', default=False, ) coreconfigitem('web', 'allow-pull', alias=[('web', 'allowpull')], default=True, ) coreconfigitem('web', 'allow-push', alias=[('web', 'allow_push')], default=list, ) coreconfigitem('web', 'allowzip', default=False, ) coreconfigitem('web', 'archivesubrepos', default=False, ) coreconfigitem('web', 'cache', default=True, ) coreconfigitem('web', 'comparisoncontext', default=5, ) coreconfigitem('web', 'contact', default=None, ) coreconfigitem('web', 'deny_push', default=list, ) coreconfigitem('web', 'guessmime', default=False, ) coreconfigitem('web', 'hidden', default=False, ) coreconfigitem('web', 'labels', default=list, ) coreconfigitem('web', 'logoimg', default='hglogo.png', ) coreconfigitem('web', 'logourl', default='https://mercurial-scm.org/', ) coreconfigitem('web', 'accesslog', default='-', ) coreconfigitem('web', 'address', default='', ) coreconfigitem('web', 'allow-archive', alias=[('web', 'allow_archive')], default=list, ) coreconfigitem('web', 'allow_read', default=list, ) coreconfigitem('web', 'baseurl', default=None, ) coreconfigitem('web', 'cacerts', default=None, ) coreconfigitem('web', 'certificate', default=None, ) coreconfigitem('web', 'collapse', default=False, ) coreconfigitem('web', 'csp', default=None, ) coreconfigitem('web', 'deny_read', default=list, ) coreconfigitem('web', 'descend', default=True, ) coreconfigitem('web', 'description', default="", ) coreconfigitem('web', 'encoding', default=lambda: encoding.encoding, ) coreconfigitem('web', 'errorlog', default='-', ) coreconfigitem('web', 'ipv6', default=False, ) coreconfigitem('web', 'maxchanges', default=10, ) coreconfigitem('web', 'maxfiles', default=10, ) coreconfigitem('web', 'maxshortchanges', default=60, ) coreconfigitem('web', 'motd', default='', ) coreconfigitem('web', 'name', default=dynamicdefault, ) coreconfigitem('web', 'port', default=8000, ) coreconfigitem('web', 'prefix', default='', ) coreconfigitem('web', 'push_ssl', default=True, ) coreconfigitem('web', 'refreshinterval', default=20, ) coreconfigitem('web', 'server-header', default=None, ) coreconfigitem('web', 'static', default=None, ) coreconfigitem('web', 'staticurl', default=None, ) coreconfigitem('web', 'stripes', default=1, ) coreconfigitem('web', 'style', default='paper', ) coreconfigitem('web', 'templates', default=None, ) coreconfigitem('web', 'view', default='served', ) coreconfigitem('worker', 'backgroundclose', default=dynamicdefault, ) # Windows defaults to a limit of 512 open files. A buffer of 128 # should give us enough headway. coreconfigitem('worker', 'backgroundclosemaxqueue', default=384, ) coreconfigitem('worker', 'backgroundcloseminfilecount', default=2048, ) coreconfigitem('worker', 'backgroundclosethreadcount', default=4, ) coreconfigitem('worker', 'enabled', default=True, ) coreconfigitem('worker', 'numcpus', default=None, ) # Rebase related configuration moved to core because other extension are doing # strange things. For example, shelve import the extensions to reuse some bit # without formally loading it. coreconfigitem('commands', 'rebase.requiredest', default=False, ) coreconfigitem('experimental', 'rebaseskipobsolete', default=True, ) coreconfigitem('rebase', 'singletransaction', default=False, ) coreconfigitem('rebase', 'experimental.inmemory', default=False, ) diff --git a/hgext/shelve.py b/mercurial/shelve.py rename from hgext/shelve.py rename to mercurial/shelve.py --- a/hgext/shelve.py +++ b/mercurial/shelve.py @@ -1,1126 +1,948 @@ # shelve.py - save/restore working directory state # # Copyright 2013 Facebook, Inc. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """save and restore changes to the working directory The "hg shelve" command saves changes made to the working directory and reverts those changes, resetting the working directory to a clean state. Later on, the "hg unshelve" command restores the changes saved by "hg shelve". Changes can be restored even after updating to a different parent, in which case Mercurial's merge machinery will resolve any conflicts if necessary. You can have more than one shelved change outstanding at a time; each shelved change has a distinct name. For details, see the help for "hg shelve". """ from __future__ import absolute_import import collections import errno import itertools import stat -from mercurial.i18n import _ -from mercurial import ( +from .i18n import _ +from . import ( bookmarks, bundle2, bundlerepo, changegroup, cmdutil, discovery, error, exchange, hg, lock as lockmod, mdiff, merge, node as nodemod, patch, phases, pycompat, - registrar, repair, scmutil, - state as statemod, templatefilters, util, vfs as vfsmod, ) - -from mercurial.utils import ( +from .utils import ( dateutil, stringutil, ) -cmdtable = {} -command = registrar.command(cmdtable) -# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for -# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should -# be specifying the version(s) of Mercurial they are tested with, or -# leave the attribute unspecified. -testedwith = 'ships-with-hg-core' - -configtable = {} -configitem = registrar.configitem(configtable) - -configitem('shelve', 'maxbackups', - default=10, -) - backupdir = 'shelve-backup' shelvedir = 'shelved' shelvefileextensions = ['hg', 'patch', 'shelve'] # universal extension is present in all types of shelves patchextension = 'patch' # we never need the user, so we use a # generic user for all shelve operations shelveuser = 'shelve@localhost' class shelvedfile(object): """Helper for the file storing a single shelve Handles common functions on shelve files (.hg/.patch) using the vfs layer""" def __init__(self, repo, name, filetype=None): self.repo = repo self.name = name self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) self.ui = self.repo.ui if filetype: self.fname = name + '.' + filetype else: self.fname = name def exists(self): return self.vfs.exists(self.fname) def filename(self): return self.vfs.join(self.fname) def backupfilename(self): def gennames(base): yield base base, ext = base.rsplit('.', 1) for i in itertools.count(1): yield '%s-%d.%s' % (base, i, ext) name = self.backupvfs.join(self.fname) for n in gennames(name): if not self.backupvfs.exists(n): return n def movetobackup(self): if not self.backupvfs.isdir(): self.backupvfs.makedir() util.rename(self.filename(), self.backupfilename()) def stat(self): return self.vfs.stat(self.fname) def opener(self, mode='rb'): try: return self.vfs(self.fname, mode) except IOError as err: if err.errno != errno.ENOENT: raise raise error.Abort(_("shelved change '%s' not found") % self.name) def applybundle(self, tr): fp = self.opener() try: targetphase = phases.internal if not phases.supportinternal(self.repo): targetphase = phases.secret gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs) pretip = self.repo['tip'] bundle2.applybundle(self.repo, gen, tr, source='unshelve', url='bundle:' + self.vfs.join(self.fname), targetphase=targetphase) shelvectx = self.repo['tip'] if pretip == shelvectx: shelverev = tr.changes['revduplicates'][-1] shelvectx = self.repo[shelverev] return shelvectx finally: fp.close() def bundlerepo(self): path = self.vfs.join(self.fname) return bundlerepo.instance(self.repo.baseui, 'bundle://%s+%s' % (self.repo.root, path)) def writebundle(self, bases, node): cgversion = changegroup.safeversion(self.repo) if cgversion == '01': btype = 'HG10BZ' compression = None else: btype = 'HG20' compression = 'BZ' repo = self.repo.unfiltered() outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=[node]) cg = changegroup.makechangegroup(repo, outgoing, cgversion, 'shelve') bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs, compression=compression) def writeinfo(self, info): scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info) def readinfo(self): return scmutil.simplekeyvaluefile(self.vfs, self.fname).read() class shelvedstate(object): """Handle persistence during unshelving operations. Handles saving and restoring a shelved state. Ensures that different versions of a shelved state are possible and handles them appropriately. """ _version = 2 _filename = 'shelvedstate' _keep = 'keep' _nokeep = 'nokeep' # colon is essential to differentiate from a real bookmark name _noactivebook = ':no-active-bookmark' @classmethod def _verifyandtransform(cls, d): """Some basic shelvestate syntactic verification and transformation""" try: d['originalwctx'] = nodemod.bin(d['originalwctx']) d['pendingctx'] = nodemod.bin(d['pendingctx']) d['parents'] = [nodemod.bin(h) for h in d['parents'].split(' ')] d['nodestoremove'] = [nodemod.bin(h) for h in d['nodestoremove'].split(' ')] except (ValueError, TypeError, KeyError) as err: raise error.CorruptedState(pycompat.bytestr(err)) @classmethod def _getversion(cls, repo): """Read version information from shelvestate file""" fp = repo.vfs(cls._filename) try: version = int(fp.readline().strip()) except ValueError as err: raise error.CorruptedState(pycompat.bytestr(err)) finally: fp.close() return version @classmethod def _readold(cls, repo): """Read the old position-based version of a shelvestate file""" # Order is important, because old shelvestate file uses it # to detemine values of fields (i.g. name is on the second line, # originalwctx is on the third and so forth). Please do not change. keys = ['version', 'name', 'originalwctx', 'pendingctx', 'parents', 'nodestoremove', 'branchtorestore', 'keep', 'activebook'] # this is executed only seldomly, so it is not a big deal # that we open this file twice fp = repo.vfs(cls._filename) d = {} try: for key in keys: d[key] = fp.readline().strip() finally: fp.close() return d @classmethod def load(cls, repo): version = cls._getversion(repo) if version < cls._version: d = cls._readold(repo) elif version == cls._version: d = scmutil.simplekeyvaluefile( repo.vfs, cls._filename).read(firstlinenonkeyval=True) else: raise error.Abort(_('this version of shelve is incompatible ' 'with the version used in this repo')) cls._verifyandtransform(d) try: obj = cls() obj.name = d['name'] obj.wctx = repo[d['originalwctx']] obj.pendingctx = repo[d['pendingctx']] obj.parents = d['parents'] obj.nodestoremove = d['nodestoremove'] obj.branchtorestore = d.get('branchtorestore', '') obj.keep = d.get('keep') == cls._keep obj.activebookmark = '' if d.get('activebook', '') != cls._noactivebook: obj.activebookmark = d.get('activebook', '') except (error.RepoLookupError, KeyError) as err: raise error.CorruptedState(pycompat.bytestr(err)) return obj @classmethod def save(cls, repo, name, originalwctx, pendingctx, nodestoremove, branchtorestore, keep=False, activebook=''): info = { "name": name, "originalwctx": nodemod.hex(originalwctx.node()), "pendingctx": nodemod.hex(pendingctx.node()), "parents": ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]), "nodestoremove": ' '.join([nodemod.hex(n) for n in nodestoremove]), "branchtorestore": branchtorestore, "keep": cls._keep if keep else cls._nokeep, "activebook": activebook or cls._noactivebook } scmutil.simplekeyvaluefile( repo.vfs, cls._filename).write(info, firstline=("%d" % cls._version)) @classmethod def clear(cls, repo): repo.vfs.unlinkpath(cls._filename, ignoremissing=True) def cleanupoldbackups(repo): vfs = vfsmod.vfs(repo.vfs.join(backupdir)) maxbackups = repo.ui.configint('shelve', 'maxbackups') hgfiles = [f for f in vfs.listdir() if f.endswith('.' + patchextension)] hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles]) if maxbackups > 0 and maxbackups < len(hgfiles): bordermtime = hgfiles[-maxbackups][0] else: bordermtime = None for mtime, f in hgfiles[:len(hgfiles) - maxbackups]: if mtime == bordermtime: # keep it, because timestamp can't decide exact order of backups continue base = f[:-(1 + len(patchextension))] for ext in shelvefileextensions: vfs.tryunlink(base + '.' + ext) def _backupactivebookmark(repo): activebookmark = repo._activebookmark if activebookmark: bookmarks.deactivate(repo) return activebookmark def _restoreactivebookmark(repo, mark): if mark: bookmarks.activate(repo, mark) def _aborttransaction(repo, tr): '''Abort current transaction for shelve/unshelve, but keep dirstate ''' dirstatebackupname = 'dirstate.shelve' repo.dirstate.savebackup(tr, dirstatebackupname) tr.abort() repo.dirstate.restorebackup(None, dirstatebackupname) def getshelvename(repo, parent, opts): """Decide on the name this shelve is going to have""" def gennames(): yield label for i in itertools.count(1): yield '%s-%02d' % (label, i) name = opts.get('name') label = repo._activebookmark or parent.branch() or 'default' # slashes aren't allowed in filenames, therefore we rename it label = label.replace('/', '_') label = label.replace('\\', '_') # filenames must not start with '.' as it should not be hidden if label.startswith('.'): label = label.replace('.', '_', 1) if name: if shelvedfile(repo, name, patchextension).exists(): e = _("a shelved change named '%s' already exists") % name raise error.Abort(e) # ensure we are not creating a subdirectory or a hidden file if '/' in name or '\\' in name: raise error.Abort(_('shelved change names can not contain slashes')) if name.startswith('.'): raise error.Abort(_("shelved change names can not start with '.'")) else: for n in gennames(): if not shelvedfile(repo, n, patchextension).exists(): name = n break return name def mutableancestors(ctx): """return all mutable ancestors for ctx (included) Much faster than the revset ancestors(ctx) & draft()""" seen = {nodemod.nullrev} visit = collections.deque() visit.append(ctx) while visit: ctx = visit.popleft() yield ctx.node() for parent in ctx.parents(): rev = parent.rev() if rev not in seen: seen.add(rev) if parent.mutable(): visit.append(parent) def getcommitfunc(extra, interactive, editor=False): def commitfunc(ui, repo, message, match, opts): hasmq = util.safehasattr(repo, 'mq') if hasmq: saved, repo.mq.checkapplied = repo.mq.checkapplied, False targetphase = phases.internal if not phases.supportinternal(repo): targetphase = phases.secret overrides = {('phases', 'new-commit'): targetphase} try: editor_ = False if editor: editor_ = cmdutil.getcommiteditor(editform='shelve.shelve', **pycompat.strkwargs(opts)) with repo.ui.configoverride(overrides): return repo.commit(message, shelveuser, opts.get('date'), match, editor=editor_, extra=extra) finally: if hasmq: repo.mq.checkapplied = saved def interactivecommitfunc(ui, repo, *pats, **opts): opts = pycompat.byteskwargs(opts) match = scmutil.match(repo['.'], pats, {}) message = opts['message'] return commitfunc(ui, repo, message, match, opts) return interactivecommitfunc if interactive else commitfunc def _nothingtoshelvemessaging(ui, repo, pats, opts): stat = repo.status(match=scmutil.match(repo[None], pats, opts)) if stat.deleted: ui.status(_("nothing changed (%d missing files, see " "'hg status')\n") % len(stat.deleted)) else: ui.status(_("nothing changed\n")) def _shelvecreatedcommit(repo, node, name, match): info = {'node': nodemod.hex(node)} shelvedfile(repo, name, 'shelve').writeinfo(info) bases = list(mutableancestors(repo[node])) shelvedfile(repo, name, 'hg').writebundle(bases, node) with shelvedfile(repo, name, patchextension).opener('wb') as fp: cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True), match=match) def _includeunknownfiles(repo, pats, opts, extra): s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) if s.unknown: extra['shelve_unknown'] = '\0'.join(s.unknown) repo[None].add(s.unknown) def _finishshelve(repo, tr): if phases.supportinternal(repo): tr.close() else: _aborttransaction(repo, tr) def createcmd(ui, repo, pats, opts): """subcommand that creates a new shelve""" with repo.wlock(): cmdutil.checkunfinished(repo) return _docreatecmd(ui, repo, pats, opts) def _docreatecmd(ui, repo, pats, opts): wctx = repo[None] parents = wctx.parents() parent = parents[0] origbranch = wctx.branch() if parent.node() != nodemod.nullid: desc = "changes to: %s" % parent.description().split('\n', 1)[0] else: desc = '(changes in empty repository)' if not opts.get('message'): opts['message'] = desc lock = tr = activebookmark = None try: lock = repo.lock() # use an uncommitted transaction to generate the bundle to avoid # pull races. ensure we don't print the abort message to stderr. tr = repo.transaction('shelve', report=lambda x: None) interactive = opts.get('interactive', False) includeunknown = (opts.get('unknown', False) and not opts.get('addremove', False)) name = getshelvename(repo, parent, opts) activebookmark = _backupactivebookmark(repo) extra = {'internal': 'shelve'} if includeunknown: _includeunknownfiles(repo, pats, opts, extra) if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): # In non-bare shelve we don't store newly created branch # at bundled commit repo.dirstate.setbranch(repo['.'].branch()) commitfunc = getcommitfunc(extra, interactive, editor=True) if not interactive: node = cmdutil.commit(ui, repo, commitfunc, pats, opts) else: node = cmdutil.dorecord(ui, repo, commitfunc, None, False, cmdutil.recordfilter, *pats, **pycompat.strkwargs(opts)) if not node: _nothingtoshelvemessaging(ui, repo, pats, opts) return 1 # Create a matcher so that prefetch doesn't attempt to fetch # the entire repository pointlessly, and as an optimisation # for movedirstate, if needed. match = scmutil.matchfiles(repo, repo[node].files()) _shelvecreatedcommit(repo, node, name, match) if ui.formatted(): desc = stringutil.ellipsis(desc, ui.termwidth()) ui.status(_('shelved as %s\n') % name) if opts['keep']: with repo.dirstate.parentchange(): scmutil.movedirstate(repo, parent, match) else: hg.update(repo, parent.node()) if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts): repo.dirstate.setbranch(origbranch) _finishshelve(repo, tr) finally: _restoreactivebookmark(repo, activebookmark) lockmod.release(tr, lock) def _isbareshelve(pats, opts): return (not pats and not opts.get('interactive', False) and not opts.get('include', False) and not opts.get('exclude', False)) def _iswctxonnewbranch(repo): return repo[None].branch() != repo['.'].branch() def cleanupcmd(ui, repo): """subcommand that deletes all shelves""" with repo.wlock(): for (name, _type) in repo.vfs.readdir(shelvedir): suffix = name.rsplit('.', 1)[-1] if suffix in shelvefileextensions: shelvedfile(repo, name).movetobackup() cleanupoldbackups(repo) def deletecmd(ui, repo, pats): """subcommand that deletes a specific shelve""" if not pats: raise error.Abort(_('no shelved changes specified!')) with repo.wlock(): try: for name in pats: for suffix in shelvefileextensions: shfile = shelvedfile(repo, name, suffix) # patch file is necessary, as it should # be present for any kind of shelve, # but the .hg file is optional as in future we # will add obsolete shelve with does not create a # bundle if shfile.exists() or suffix == patchextension: shfile.movetobackup() cleanupoldbackups(repo) except OSError as err: if err.errno != errno.ENOENT: raise raise error.Abort(_("shelved change '%s' not found") % name) def listshelves(repo): """return all shelves in repo as list of (time, filename)""" try: names = repo.vfs.readdir(shelvedir) except OSError as err: if err.errno != errno.ENOENT: raise return [] info = [] for (name, _type) in names: pfx, sfx = name.rsplit('.', 1) if not pfx or sfx != patchextension: continue st = shelvedfile(repo, name).stat() info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename())) return sorted(info, reverse=True) def listcmd(ui, repo, pats, opts): """subcommand that displays the list of shelves""" pats = set(pats) width = 80 if not ui.plain(): width = ui.termwidth() namelabel = 'shelve.newest' ui.pager('shelve') for mtime, name in listshelves(repo): sname = util.split(name)[1] if pats and sname not in pats: continue ui.write(sname, label=namelabel) namelabel = 'shelve.name' if ui.quiet: ui.write('\n') continue ui.write(' ' * (16 - len(sname))) used = 16 date = dateutil.makedate(mtime) age = '(%s)' % templatefilters.age(date, abbrev=True) ui.write(age, label='shelve.age') ui.write(' ' * (12 - len(age))) used += 12 with open(name + '.' + patchextension, 'rb') as fp: while True: line = fp.readline() if not line: break if not line.startswith('#'): desc = line.rstrip() if ui.formatted(): desc = stringutil.ellipsis(desc, width - used) ui.write(desc) break ui.write('\n') if not (opts['patch'] or opts['stat']): continue difflines = fp.readlines() if opts['patch']: for chunk, label in patch.difflabel(iter, difflines): ui.write(chunk, label=label) if opts['stat']: for chunk, label in patch.diffstatui(difflines, width=width): ui.write(chunk, label=label) def patchcmds(ui, repo, pats, opts): """subcommand that displays shelves""" if len(pats) == 0: shelves = listshelves(repo) if not shelves: raise error.Abort(_("there are no shelves to show")) mtime, name = shelves[0] sname = util.split(name)[1] pats = [sname] for shelfname in pats: if not shelvedfile(repo, shelfname, patchextension).exists(): raise error.Abort(_("cannot find shelf %s") % shelfname) listcmd(ui, repo, pats, opts) def checkparents(repo, state): """check parent while resuming an unshelve""" if state.parents != repo.dirstate.parents(): raise error.Abort(_('working directory parents do not match unshelve ' 'state')) def unshelveabort(ui, repo, state, opts): """subcommand that abort an in-progress unshelve""" with repo.lock(): try: checkparents(repo, state) merge.update(repo, state.pendingctx, branchmerge=False, force=True) if (state.activebookmark and state.activebookmark in repo._bookmarks): bookmarks.activate(repo, state.activebookmark) mergefiles(ui, repo, state.wctx, state.pendingctx) if not phases.supportinternal(repo): repair.strip(ui, repo, state.nodestoremove, backup=False, topic='shelve') finally: shelvedstate.clear(repo) ui.warn(_("unshelve of '%s' aborted\n") % state.name) def mergefiles(ui, repo, wctx, shelvectx): """updates to wctx and merges the changes from shelvectx into the dirstate.""" with ui.configoverride({('ui', 'quiet'): True}): hg.update(repo, wctx.node()) ui.pushbuffer(True) cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents()) ui.popbuffer() def restorebranch(ui, repo, branchtorestore): if branchtorestore and branchtorestore != repo.dirstate.branch(): repo.dirstate.setbranch(branchtorestore) ui.status(_('marked working directory as branch %s\n') % branchtorestore) def unshelvecleanup(ui, repo, name, opts): """remove related files after an unshelve""" if not opts.get('keep'): for filetype in shelvefileextensions: shfile = shelvedfile(repo, name, filetype) if shfile.exists(): shfile.movetobackup() cleanupoldbackups(repo) def unshelvecontinue(ui, repo, state, opts): """subcommand to continue an in-progress unshelve""" # We're finishing off a merge. First parent is our original # parent, second is the temporary "fake" commit we're unshelving. with repo.lock(): checkparents(repo, state) ms = merge.mergestate.read(repo) if list(ms.unresolved()): raise error.Abort( _("unresolved conflicts, can't continue"), hint=_("see 'hg resolve', then 'hg unshelve --continue'")) shelvectx = repo[state.parents[1]] pendingctx = state.pendingctx with repo.dirstate.parentchange(): repo.setparents(state.pendingctx.node(), nodemod.nullid) repo.dirstate.write(repo.currenttransaction()) targetphase = phases.internal if not phases.supportinternal(repo): targetphase = phases.secret overrides = {('phases', 'new-commit'): targetphase} with repo.ui.configoverride(overrides, 'unshelve'): with repo.dirstate.parentchange(): repo.setparents(state.parents[0], nodemod.nullid) newnode = repo.commit(text=shelvectx.description(), extra=shelvectx.extra(), user=shelvectx.user(), date=shelvectx.date()) if newnode is None: # If it ended up being a no-op commit, then the normal # merge state clean-up path doesn't happen, so do it # here. Fix issue5494 merge.mergestate.clean(repo) shelvectx = state.pendingctx msg = _('note: unshelved changes already existed ' 'in the working copy\n') ui.status(msg) else: # only strip the shelvectx if we produced one state.nodestoremove.append(newnode) shelvectx = repo[newnode] hg.updaterepo(repo, pendingctx.node(), overwrite=False) mergefiles(ui, repo, state.wctx, shelvectx) restorebranch(ui, repo, state.branchtorestore) if not phases.supportinternal(repo): repair.strip(ui, repo, state.nodestoremove, backup=False, topic='shelve') _restoreactivebookmark(repo, state.activebookmark) shelvedstate.clear(repo) unshelvecleanup(ui, repo, state.name, opts) ui.status(_("unshelve of '%s' complete\n") % state.name) def _commitworkingcopychanges(ui, repo, opts, tmpwctx): """Temporarily commit working copy changes before moving unshelve commit""" # Store pending changes in a commit and remember added in case a shelve # contains unknown files that are part of the pending change s = repo.status() addedbefore = frozenset(s.added) if not (s.modified or s.added or s.removed): return tmpwctx, addedbefore ui.status(_("temporarily committing pending changes " "(restore with 'hg unshelve --abort')\n")) extra = {'internal': 'shelve'} commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False) tempopts = {} tempopts['message'] = "pending changes temporary commit" tempopts['date'] = opts.get('date') with ui.configoverride({('ui', 'quiet'): True}): node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) tmpwctx = repo[node] return tmpwctx, addedbefore def _unshelverestorecommit(ui, repo, tr, basename): """Recreate commit in the repository during the unshelve""" repo = repo.unfiltered() node = None if shelvedfile(repo, basename, 'shelve').exists(): node = shelvedfile(repo, basename, 'shelve').readinfo()['node'] if node is None or node not in repo: with ui.configoverride({('ui', 'quiet'): True}): shelvectx = shelvedfile(repo, basename, 'hg').applybundle(tr) # We might not strip the unbundled changeset, so we should keep track of # the unshelve node in case we need to reuse it (eg: unshelve --keep) if node is None: info = {'node': nodemod.hex(shelvectx.node())} shelvedfile(repo, basename, 'shelve').writeinfo(info) else: shelvectx = repo[node] return repo, shelvectx def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx, tmpwctx, shelvectx, branchtorestore, activebookmark): """Rebase restored commit from its original location to a destination""" # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. if tmpwctx.node() == shelvectx.p1().node(): return shelvectx overrides = { ('ui', 'forcemerge'): opts.get('tool', ''), ('phases', 'new-commit'): phases.secret, } with repo.ui.configoverride(overrides, 'unshelve'): ui.status(_('rebasing shelved changes\n')) stats = merge.graft(repo, shelvectx, shelvectx.p1(), labels=['shelve', 'working-copy'], keepconflictparent=True) if stats.unresolvedcount: tr.close() nodestoremove = [repo.changelog.node(rev) for rev in pycompat.xrange(oldtiprev, len(repo))] shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove, branchtorestore, opts.get('keep'), activebookmark) raise error.InterventionRequired( _("unresolved conflicts (see 'hg resolve', then " "'hg unshelve --continue')")) with repo.dirstate.parentchange(): repo.setparents(tmpwctx.node(), nodemod.nullid) newnode = repo.commit(text=shelvectx.description(), extra=shelvectx.extra(), user=shelvectx.user(), date=shelvectx.date()) if newnode is None: # If it ended up being a no-op commit, then the normal # merge state clean-up path doesn't happen, so do it # here. Fix issue5494 merge.mergestate.clean(repo) shelvectx = tmpwctx msg = _('note: unshelved changes already existed ' 'in the working copy\n') ui.status(msg) else: shelvectx = repo[newnode] hg.updaterepo(repo, tmpwctx.node(), False) return shelvectx def _forgetunknownfiles(repo, shelvectx, addedbefore): # Forget any files that were unknown before the shelve, unknown before # unshelve started, but are now added. shelveunknown = shelvectx.extra().get('shelve_unknown') if not shelveunknown: return shelveunknown = frozenset(shelveunknown.split('\0')) addedafter = frozenset(repo.status().added) toforget = (addedafter & shelveunknown) - addedbefore repo[None].forget(toforget) def _finishunshelve(repo, oldtiprev, tr, activebookmark): _restoreactivebookmark(repo, activebookmark) # The transaction aborting will strip all the commits for us, # but it doesn't update the inmemory structures, so addchangegroup # hooks still fire and try to operate on the missing commits. # Clean up manually to prevent this. repo.unfiltered().changelog.strip(oldtiprev, tr) _aborttransaction(repo, tr) def _checkunshelveuntrackedproblems(ui, repo, shelvectx): """Check potential problems which may result from working copy having untracked changes.""" wcdeleted = set(repo.status().deleted) shelvetouched = set(shelvectx.files()) intersection = wcdeleted.intersection(shelvetouched) if intersection: m = _("shelved change touches missing files") hint = _("run hg status to see which files are missing") raise error.Abort(m, hint=hint) -@command('unshelve', - [('a', 'abort', None, - _('abort an incomplete unshelve operation')), - ('c', 'continue', None, - _('continue an incomplete unshelve operation')), - ('k', 'keep', None, - _('keep shelve after unshelving')), - ('n', 'name', '', - _('restore shelved change with given name'), _('NAME')), - ('t', 'tool', '', _('specify merge tool')), - ('', 'date', '', - _('set date for temporary commits (DEPRECATED)'), _('DATE'))], - _('hg unshelve [[-n] SHELVED]'), - helpcategory=command.CATEGORY_WORKING_DIRECTORY) -def unshelve(ui, repo, *shelved, **opts): - """restore a shelved change to the working directory - - This command accepts an optional name of a shelved change to - restore. If none is given, the most recent shelved change is used. - - If a shelved change is applied successfully, the bundle that - contains the shelved changes is moved to a backup location - (.hg/shelve-backup). - - Since you can restore a shelved change on top of an arbitrary - commit, it is possible that unshelving will result in a conflict - between your changes and the commits you are unshelving onto. If - this occurs, you must resolve the conflict, then use - ``--continue`` to complete the unshelve operation. (The bundle - will not be moved until you successfully complete the unshelve.) - - (Alternatively, you can use ``--abort`` to abandon an unshelve - that causes a conflict. This reverts the unshelved changes, and - leaves the bundle in place.) - - If bare shelved change (when no files are specified, without interactive, - include and exclude option) was done on newly created branch it would - restore branch information to the working directory. - - After a successful unshelve, the shelved changes are stored in a - backup directory. Only the N most recent backups are kept. N - defaults to 10 but can be overridden using the ``shelve.maxbackups`` - configuration option. - - .. container:: verbose - - Timestamp in seconds is used to decide order of backups. More - than ``maxbackups`` backups are kept, if same timestamp - prevents from deciding exact order of them, for safety. - """ - with repo.wlock(): - return _dounshelve(ui, repo, *shelved, **opts) - def _dounshelve(ui, repo, *shelved, **opts): opts = pycompat.byteskwargs(opts) abortf = opts.get('abort') continuef = opts.get('continue') if not abortf and not continuef: cmdutil.checkunfinished(repo) shelved = list(shelved) if opts.get("name"): shelved.append(opts["name"]) if abortf or continuef: if abortf and continuef: raise error.Abort(_('cannot use both abort and continue')) if shelved: raise error.Abort(_('cannot combine abort/continue with ' 'naming a shelved change')) if abortf and opts.get('tool', False): ui.warn(_('tool option will be ignored\n')) try: state = shelvedstate.load(repo) if opts.get('keep') is None: opts['keep'] = state.keep except IOError as err: if err.errno != errno.ENOENT: raise cmdutil.wrongtooltocontinue(repo, _('unshelve')) except error.CorruptedState as err: ui.debug(pycompat.bytestr(err) + '\n') if continuef: msg = _('corrupted shelved state file') hint = _('please run hg unshelve --abort to abort unshelve ' 'operation') raise error.Abort(msg, hint=hint) elif abortf: msg = _('could not read shelved state file, your working copy ' 'may be in an unexpected state\nplease update to some ' 'commit\n') ui.warn(msg) shelvedstate.clear(repo) return if abortf: return unshelveabort(ui, repo, state, opts) elif continuef: return unshelvecontinue(ui, repo, state, opts) elif len(shelved) > 1: raise error.Abort(_('can only unshelve one change at a time')) elif not shelved: shelved = listshelves(repo) if not shelved: raise error.Abort(_('no shelved changes to apply!')) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) else: basename = shelved[0] if not shelvedfile(repo, basename, patchextension).exists(): raise error.Abort(_("shelved change '%s' not found") % basename) repo = repo.unfiltered() lock = tr = None try: lock = repo.lock() tr = repo.transaction('unshelve', report=lambda x: None) oldtiprev = len(repo) pctx = repo['.'] tmpwctx = pctx # The goal is to have a commit structure like so: # ...-> pctx -> tmpwctx -> shelvectx # where tmpwctx is an optional commit with the user's pending changes # and shelvectx is the unshelved changes. Then we merge it all down # to the original pctx. activebookmark = _backupactivebookmark(repo) tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, tmpwctx) repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename) _checkunshelveuntrackedproblems(ui, repo, shelvectx) branchtorestore = '' if shelvectx.branch() != shelvectx.p1().branch(): branchtorestore = shelvectx.branch() shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx, tmpwctx, shelvectx, branchtorestore, activebookmark) overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'unshelve'): mergefiles(ui, repo, pctx, shelvectx) restorebranch(ui, repo, branchtorestore) _forgetunknownfiles(repo, shelvectx, addedbefore) shelvedstate.clear(repo) _finishunshelve(repo, oldtiprev, tr, activebookmark) unshelvecleanup(ui, repo, basename, opts) finally: if tr: tr.release() lockmod.release(lock) - -@command('shelve', - [('A', 'addremove', None, - _('mark new/missing files as added/removed before shelving')), - ('u', 'unknown', None, - _('store unknown files in the shelve')), - ('', 'cleanup', None, - _('delete all shelved changes')), - ('', 'date', '', - _('shelve with the specified commit date'), _('DATE')), - ('d', 'delete', None, - _('delete the named shelved change(s)')), - ('e', 'edit', False, - _('invoke editor on commit messages')), - ('k', 'keep', False, - _('shelve, but keep changes in the working directory')), - ('l', 'list', None, - _('list current shelves')), - ('m', 'message', '', - _('use text as shelve message'), _('TEXT')), - ('n', 'name', '', - _('use the given name for the shelved commit'), _('NAME')), - ('p', 'patch', None, - _('output patches for changes (provide the names of the shelved ' - 'changes as positional arguments)')), - ('i', 'interactive', None, - _('interactive mode, only works while creating a shelve')), - ('', 'stat', None, - _('output diffstat-style summary of changes (provide the names of ' - 'the shelved changes as positional arguments)') - )] + cmdutil.walkopts, - _('hg shelve [OPTION]... [FILE]...'), - helpcategory=command.CATEGORY_WORKING_DIRECTORY) -def shelvecmd(ui, repo, *pats, **opts): - '''save and set aside changes from the working directory - - Shelving takes files that "hg status" reports as not clean, saves - the modifications to a bundle (a shelved change), and reverts the - files so that their state in the working directory becomes clean. - - To restore these changes to the working directory, using "hg - unshelve"; this will work even if you switch to a different - commit. - - When no files are specified, "hg shelve" saves all not-clean - files. If specific files or directories are named, only changes to - those files are shelved. - - In bare shelve (when no files are specified, without interactive, - include and exclude option), shelving remembers information if the - working directory was on newly created branch, in other words working - directory was on different branch than its first parent. In this - situation unshelving restores branch information to the working directory. - - Each shelved change has a name that makes it easier to find later. - The name of a shelved change defaults to being based on the active - bookmark, or if there is no active bookmark, the current named - branch. To specify a different name, use ``--name``. - - To see a list of existing shelved changes, use the ``--list`` - option. For each shelved change, this will print its name, age, - and description; use ``--patch`` or ``--stat`` for more details. - - To delete specific shelved changes, use ``--delete``. To delete - all shelved changes, use ``--cleanup``. - ''' - opts = pycompat.byteskwargs(opts) - allowables = [ - ('addremove', {'create'}), # 'create' is pseudo action - ('unknown', {'create'}), - ('cleanup', {'cleanup'}), -# ('date', {'create'}), # ignored for passing '--date "0 0"' in tests - ('delete', {'delete'}), - ('edit', {'create'}), - ('keep', {'create'}), - ('list', {'list'}), - ('message', {'create'}), - ('name', {'create'}), - ('patch', {'patch', 'list'}), - ('stat', {'stat', 'list'}), - ] - def checkopt(opt): - if opts.get(opt): - for i, allowable in allowables: - if opts[i] and opt not in allowable: - raise error.Abort(_("options '--%s' and '--%s' may not be " - "used together") % (opt, i)) - return True - if checkopt('cleanup'): - if pats: - raise error.Abort(_("cannot specify names when using '--cleanup'")) - return cleanupcmd(ui, repo) - elif checkopt('delete'): - return deletecmd(ui, repo, pats) - elif checkopt('list'): - return listcmd(ui, repo, pats, opts) - elif checkopt('patch') or checkopt('stat'): - return patchcmds(ui, repo, pats, opts) - else: - return createcmd(ui, repo, pats, opts) - -def extsetup(ui): - statemod.addunfinished( - 'unshelve', fname=shelvedstate._filename, continueflag=True, - cmdmsg=_('unshelve already in progress') - ) - diff --git a/mercurial/state.py b/mercurial/state.py --- a/mercurial/state.py +++ b/mercurial/state.py @@ -1,225 +1,229 @@ # state.py - writing and reading state files in Mercurial # # Copyright 2018 Pulkit Goyal # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """ This file contains class to wrap the state for commands and other related logic. All the data related to the command state is stored as dictionary in the object. The class has methods using which the data can be stored to disk in a file under .hg/ directory. We store the data on disk in cbor, for which we use the CBOR format to encode the data. """ from __future__ import absolute_import from .i18n import _ from . import ( error, util, ) from .utils import ( cborutil, ) class cmdstate(object): """a wrapper class to store the state of commands like `rebase`, `graft`, `histedit`, `shelve` etc. Extensions can also use this to write state files. All the data for the state is stored in the form of key-value pairs in a dictionary. The class object can write all the data to a file in .hg/ directory and can populate the object data reading that file. Uses cbor to serialize and deserialize data while writing and reading from disk. """ def __init__(self, repo, fname): """ repo is the repo object fname is the file name in which data should be stored in .hg directory """ self._repo = repo self.fname = fname def read(self): """read the existing state file and return a dict of data stored""" return self._read() def save(self, version, data): """write all the state data stored to .hg/ file we use third-party library cbor to serialize data to write in the file. """ if not isinstance(version, int): raise error.ProgrammingError("version of state file should be" " an integer") with self._repo.vfs(self.fname, 'wb', atomictemp=True) as fp: fp.write('%d\n' % version) for chunk in cborutil.streamencode(data): fp.write(chunk) def _read(self): """reads the state file and returns a dictionary which contain data in the same format as it was before storing""" with self._repo.vfs(self.fname, 'rb') as fp: try: int(fp.readline()) except ValueError: raise error.CorruptedState("unknown version of state file" " found") return cborutil.decodeall(fp.read())[0] def delete(self): """drop the state file if exists""" util.unlinkpath(self._repo.vfs.join(self.fname), ignoremissing=True) def exists(self): """check whether the state file exists or not""" return self._repo.vfs.exists(self.fname) class _statecheck(object): """a utility class that deals with multistep operations like graft, histedit, bisect, update etc and check whether such commands are in an unfinished conditition or not and return appropriate message and hint. It also has the ability to register and determine the states of any new multistep operation or multistep command extension. """ def __init__(self, opname, fname, clearable, allowcommit, reportonly, continueflag, stopflag, cmdmsg, cmdhint, statushint): self._opname = opname self._fname = fname self._clearable = clearable self._allowcommit = allowcommit self._reportonly = reportonly self._continueflag = continueflag self._stopflag = stopflag self._cmdmsg = cmdmsg self._cmdhint = cmdhint self._statushint = statushint def statusmsg(self): """returns the hint message corresponding to the command for hg status --verbose """ if not self._statushint: hint = (_('To continue: hg %s --continue\n' 'To abort: hg %s --abort') % (self._opname, self._opname)) if self._stopflag: hint = hint + (_('\nTo stop: hg %s --stop') % (self._opname)) return hint return self._statushint def hint(self): """returns the hint message corresponding to an interrupted operation """ if not self._cmdhint: return (_("use 'hg %s --continue' or 'hg %s --abort'") % (self._opname, self._opname)) return self._cmdhint def msg(self): """returns the status message corresponding to the command""" if not self._cmdmsg: return _('%s in progress') % (self._opname) return self._cmdmsg def continuemsg(self): """ returns appropriate continue message corresponding to command""" return _('hg %s --continue') % (self._opname) def isunfinished(self, repo): """determines whether a multi-step operation is in progress or not """ if self._opname == 'merge': return len(repo[None].parents()) > 1 else: return repo.vfs.exists(self._fname) # A list of statecheck objects for multistep operations like graft. _unfinishedstates = [] def addunfinished(opname, fname, clearable=False, allowcommit=False, reportonly=False, continueflag=False, stopflag=False, cmdmsg="", cmdhint="", statushint=""): """this registers a new command or operation to unfinishedstates opname is the name the command or operation fname is the file name in which data should be stored in .hg directory. It is None for merge command. clearable boolean determines whether or not interrupted states can be cleared by running `hg update -C .` which in turn deletes the state file. allowcommit boolean decides whether commit is allowed during interrupted state or not. reportonly flag is used for operations like bisect where we just need to detect the operation using 'hg status --verbose' continueflag is a boolean determines whether or not a command supports `--continue` option or not. stopflag is a boolean that determines whether or not a command supports --stop flag cmdmsg is used to pass a different status message in case standard message of the format "abort: cmdname in progress" is not desired. cmdhint is used to pass a different hint message in case standard message of the format "To continue: hg cmdname --continue To abort: hg cmdname --abort" is not desired. statushint is used to pass a different status message in case standard message of the format ('To continue: hg cmdname --continue' 'To abort: hg cmdname --abort') is not desired """ statecheckobj = _statecheck(opname, fname, clearable, allowcommit, reportonly, continueflag, stopflag, cmdmsg, cmdhint, statushint) if opname == 'merge': _unfinishedstates.append(statecheckobj) else: _unfinishedstates.insert(0, statecheckobj) addunfinished( 'graft', fname='graftstate', clearable=True, stopflag=True, continueflag=True, cmdhint=_("use 'hg graft --continue' or 'hg graft --stop' to stop") ) addunfinished( + 'unshelve', fname='shelvedstate', continueflag=True, + cmdmsg=_('unshelve already in progress') +) +addunfinished( 'update', fname='updatestate', clearable=True, cmdmsg=_('last update was interrupted'), cmdhint=_("use 'hg update' to get a consistent checkout"), statushint=_("To continue: hg update") ) addunfinished( 'bisect', fname='bisect.state', allowcommit=True, reportonly=True, statushint=_('To mark the changeset good: hg bisect --good\n' 'To mark the changeset bad: hg bisect --bad\n' 'To abort: hg bisect --reset\n') ) addunfinished( 'merge', fname=None, clearable=True, allowcommit=True, cmdmsg=_('outstanding uncommitted merge'), statushint=_('To continue: hg commit\n' 'To abort: hg merge --abort'), cmdhint=_("use 'hg commit' or 'hg merge --abort'") ) def getrepostate(repo): # experimental config: commands.status.skipstates skip = set(repo.ui.configlist('commands', 'status.skipstates')) for state in _unfinishedstates: if state._opname in skip: continue if state.isunfinished(repo): return (state._opname, state.statusmsg()) diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -1,2123 +1,2122 @@ # ui.py - user interface bits for mercurial # # Copyright 2005-2007 Matt Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import import collections import contextlib import errno import getpass import inspect import os import re import signal import socket import subprocess import sys import traceback from .i18n import _ from .node import hex from . import ( color, config, configitems, encoding, error, formatter, loggingutil, progress, pycompat, rcutil, scmutil, util, ) from .utils import ( dateutil, procutil, stringutil, ) urlreq = util.urlreq # for use with str.translate(None, _keepalnum), to keep just alphanumerics _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()) # The config knobs that will be altered (if unset) by ui.tweakdefaults. tweakrc = b""" [ui] # The rollback command is dangerous. As a rule, don't use it. rollback = False # Make `hg status` report copy information statuscopies = yes # Prefer curses UIs when available. Revert to plain-text with `text`. interface = curses # Make compatible commands emit cwd-relative paths by default. relative-paths = yes [commands] # Grep working directory by default. grep.all-files = True # Refuse to perform an `hg update` that would cause a file content merge update.check = noconflict # Show conflicts information in `hg status` status.verbose = True [diff] git = 1 showfunc = 1 word-diff = 1 """ samplehgrcs = { 'user': b"""# example user config (see 'hg help config' for more info) [ui] # name and email, e.g. # username = Jane Doe username = # We recommend enabling tweakdefaults to get slight improvements to # the UI over time. Make sure to set HGPLAIN in the environment when # writing scripts! # tweakdefaults = True # uncomment to disable color in command output # (see 'hg help color' for details) # color = never # uncomment to disable command output pagination # (see 'hg help pager' for details) # paginate = never [extensions] # uncomment the lines below to enable some popular extensions # (see 'hg help extensions' for more info) # # histedit = # rebase = -# shelve = # uncommit = """, 'cloned': b"""# example repository config (see 'hg help config' for more info) [paths] default = %s # path aliases to other clones of this repo in URLs or filesystem paths # (see 'hg help config.paths' for more info) # # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork # my-fork = ssh://jdoe@example.net/hg/jdoes-fork # my-clone = /home/jdoe/jdoes-clone [ui] # name and email (local to this repository, optional), e.g. # username = Jane Doe """, 'local': b"""# example repository config (see 'hg help config' for more info) [paths] # path aliases to other clones of this repo in URLs or filesystem paths # (see 'hg help config.paths' for more info) # # default = http://example.com/hg/example-repo # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork # my-fork = ssh://jdoe@example.net/hg/jdoes-fork # my-clone = /home/jdoe/jdoes-clone [ui] # name and email (local to this repository, optional), e.g. # username = Jane Doe """, 'global': b"""# example system-wide hg config (see 'hg help config' for more info) [ui] # uncomment to disable color in command output # (see 'hg help color' for details) # color = never # uncomment to disable command output pagination # (see 'hg help pager' for details) # paginate = never [extensions] # uncomment the lines below to enable some popular extensions # (see 'hg help extensions' for more info) # # blackbox = # churn = """, } def _maybestrurl(maybebytes): return pycompat.rapply(pycompat.strurl, maybebytes) def _maybebytesurl(maybestr): return pycompat.rapply(pycompat.bytesurl, maybestr) class httppasswordmgrdbproxy(object): """Delays loading urllib2 until it's needed.""" def __init__(self): self._mgr = None def _get_mgr(self): if self._mgr is None: self._mgr = urlreq.httppasswordmgrwithdefaultrealm() return self._mgr def add_password(self, realm, uris, user, passwd): return self._get_mgr().add_password( _maybestrurl(realm), _maybestrurl(uris), _maybestrurl(user), _maybestrurl(passwd)) def find_user_password(self, realm, uri): mgr = self._get_mgr() return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))) def _catchterm(*args): raise error.SignalInterrupt # unique object used to detect no default value has been provided when # retrieving configuration value. _unset = object() # _reqexithandlers: callbacks run at the end of a request _reqexithandlers = [] class ui(object): def __init__(self, src=None): """Create a fresh new ui object if no src given Use uimod.ui.load() to create a ui which knows global and user configs. In most cases, you should use ui.copy() to create a copy of an existing ui object. """ # _buffers: used for temporary capture of output self._buffers = [] # 3-tuple describing how each buffer in the stack behaves. # Values are (capture stderr, capture subprocesses, apply labels). self._bufferstates = [] # When a buffer is active, defines whether we are expanding labels. # This exists to prevent an extra list lookup. self._bufferapplylabels = None self.quiet = self.verbose = self.debugflag = self.tracebackflag = False self._reportuntrusted = True self._knownconfig = configitems.coreitems self._ocfg = config.config() # overlay self._tcfg = config.config() # trusted self._ucfg = config.config() # untrusted self._trustusers = set() self._trustgroups = set() self.callhooks = True # Insecure server connections requested. self.insecureconnections = False # Blocked time self.logblockedtimes = False # color mode: see mercurial/color.py for possible value self._colormode = None self._terminfoparams = {} self._styles = {} self._uninterruptible = False if src: self._fout = src._fout self._ferr = src._ferr self._fin = src._fin self._fmsg = src._fmsg self._fmsgout = src._fmsgout self._fmsgerr = src._fmsgerr self._finoutredirected = src._finoutredirected self._loggers = src._loggers.copy() self.pageractive = src.pageractive self._disablepager = src._disablepager self._tweaked = src._tweaked self._tcfg = src._tcfg.copy() self._ucfg = src._ucfg.copy() self._ocfg = src._ocfg.copy() self._trustusers = src._trustusers.copy() self._trustgroups = src._trustgroups.copy() self.environ = src.environ self.callhooks = src.callhooks self.insecureconnections = src.insecureconnections self._colormode = src._colormode self._terminfoparams = src._terminfoparams.copy() self._styles = src._styles.copy() self.fixconfig() self.httppasswordmgrdb = src.httppasswordmgrdb self._blockedtimes = src._blockedtimes else: self._fout = procutil.stdout self._ferr = procutil.stderr self._fin = procutil.stdin self._fmsg = None self._fmsgout = self.fout # configurable self._fmsgerr = self.ferr # configurable self._finoutredirected = False self._loggers = {} self.pageractive = False self._disablepager = False self._tweaked = False # shared read-only environment self.environ = encoding.environ self.httppasswordmgrdb = httppasswordmgrdbproxy() self._blockedtimes = collections.defaultdict(int) allowed = self.configlist('experimental', 'exportableenviron') if '*' in allowed: self._exportableenviron = self.environ else: self._exportableenviron = {} for k in allowed: if k in self.environ: self._exportableenviron[k] = self.environ[k] @classmethod def load(cls): """Create a ui and load global and user configs""" u = cls() # we always trust global config files and environment variables for t, f in rcutil.rccomponents(): if t == 'path': u.readconfig(f, trust=True) elif t == 'items': sections = set() for section, name, value, source in f: # do not set u._ocfg # XXX clean this up once immutable config object is a thing u._tcfg.set(section, name, value, source) u._ucfg.set(section, name, value, source) sections.add(section) for section in sections: u.fixconfig(section=section) else: raise error.ProgrammingError('unknown rctype: %s' % t) u._maybetweakdefaults() return u def _maybetweakdefaults(self): if not self.configbool('ui', 'tweakdefaults'): return if self._tweaked or self.plain('tweakdefaults'): return # Note: it is SUPER IMPORTANT that you set self._tweaked to # True *before* any calls to setconfig(), otherwise you'll get # infinite recursion between setconfig and this method. # # TODO: We should extract an inner method in setconfig() to # avoid this weirdness. self._tweaked = True tmpcfg = config.config() tmpcfg.parse('', tweakrc) for section in tmpcfg: for name, value in tmpcfg.items(section): if not self.hasconfig(section, name): self.setconfig(section, name, value, "") def copy(self): return self.__class__(self) def resetstate(self): """Clear internal state that shouldn't persist across commands""" if self._progbar: self._progbar.resetstate() # reset last-print time of progress bar self.httppasswordmgrdb = httppasswordmgrdbproxy() @contextlib.contextmanager def timeblockedsection(self, key): # this is open-coded below - search for timeblockedsection to find them starttime = util.timer() try: yield finally: self._blockedtimes[key + '_blocked'] += ( (util.timer() - starttime) * 1000) @contextlib.contextmanager def uninterruptible(self): """Mark an operation as unsafe. Most operations on a repository are safe to interrupt, but a few are risky (for example repair.strip). This context manager lets you advise Mercurial that something risky is happening so that control-C etc can be blocked if desired. """ enabled = self.configbool('experimental', 'nointerrupt') if (enabled and self.configbool('experimental', 'nointerrupt-interactiveonly')): enabled = self.interactive() if self._uninterruptible or not enabled: # if nointerrupt support is turned off, the process isn't # interactive, or we're already in an uninterruptible # block, do nothing. yield return def warn(): self.warn(_("shutting down cleanly\n")) self.warn( _("press ^C again to terminate immediately (dangerous)\n")) return True with procutil.uninterruptible(warn): try: self._uninterruptible = True yield finally: self._uninterruptible = False def formatter(self, topic, opts): return formatter.formatter(self, self, topic, opts) def _trusted(self, fp, f): st = util.fstat(fp) if util.isowner(st): return True tusers, tgroups = self._trustusers, self._trustgroups if '*' in tusers or '*' in tgroups: return True user = util.username(st.st_uid) group = util.groupname(st.st_gid) if user in tusers or group in tgroups or user == util.username(): return True if self._reportuntrusted: self.warn(_('not trusting file %s from untrusted ' 'user %s, group %s\n') % (f, user, group)) return False def readconfig(self, filename, root=None, trust=False, sections=None, remap=None): try: fp = open(filename, r'rb') except IOError: if not sections: # ignore unless we were looking for something return raise cfg = config.config() trusted = sections or trust or self._trusted(fp, filename) try: cfg.read(filename, fp, sections=sections, remap=remap) fp.close() except error.ConfigError as inst: if trusted: raise self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst)) if self.plain(): for k in ('debug', 'fallbackencoding', 'quiet', 'slash', 'logtemplate', 'message-output', 'statuscopies', 'style', 'traceback', 'verbose'): if k in cfg['ui']: del cfg['ui'][k] for k, v in cfg.items('defaults'): del cfg['defaults'][k] for k, v in cfg.items('commands'): del cfg['commands'][k] # Don't remove aliases from the configuration if in the exceptionlist if self.plain('alias'): for k, v in cfg.items('alias'): del cfg['alias'][k] if self.plain('revsetalias'): for k, v in cfg.items('revsetalias'): del cfg['revsetalias'][k] if self.plain('templatealias'): for k, v in cfg.items('templatealias'): del cfg['templatealias'][k] if trusted: self._tcfg.update(cfg) self._tcfg.update(self._ocfg) self._ucfg.update(cfg) self._ucfg.update(self._ocfg) if root is None: root = os.path.expanduser('~') self.fixconfig(root=root) def fixconfig(self, root=None, section=None): if section in (None, 'paths'): # expand vars and ~ # translate paths relative to root (or home) into absolute paths root = root or encoding.getcwd() for c in self._tcfg, self._ucfg, self._ocfg: for n, p in c.items('paths'): # Ignore sub-options. if ':' in n: continue if not p: continue if '%%' in p: s = self.configsource('paths', n) or 'none' self.warn(_("(deprecated '%%' in path %s=%s from %s)\n") % (n, p, s)) p = p.replace('%%', '%') p = util.expandpath(p) if not util.hasscheme(p) and not os.path.isabs(p): p = os.path.normpath(os.path.join(root, p)) c.set("paths", n, p) if section in (None, 'ui'): # update ui options self._fmsgout, self._fmsgerr = _selectmsgdests(self) self.debugflag = self.configbool('ui', 'debug') self.verbose = self.debugflag or self.configbool('ui', 'verbose') self.quiet = not self.debugflag and self.configbool('ui', 'quiet') if self.verbose and self.quiet: self.quiet = self.verbose = False self._reportuntrusted = self.debugflag or self.configbool("ui", "report_untrusted") self.tracebackflag = self.configbool('ui', 'traceback') self.logblockedtimes = self.configbool('ui', 'logblockedtimes') if section in (None, 'trusted'): # update trust information self._trustusers.update(self.configlist('trusted', 'users')) self._trustgroups.update(self.configlist('trusted', 'groups')) if section in (None, b'devel', b'ui') and self.debugflag: tracked = set() if self.configbool(b'devel', b'debug.extensions'): tracked.add(b'extension') if tracked: logger = loggingutil.fileobjectlogger(self._ferr, tracked) self.setlogger(b'debug', logger) def backupconfig(self, section, item): return (self._ocfg.backup(section, item), self._tcfg.backup(section, item), self._ucfg.backup(section, item),) def restoreconfig(self, data): self._ocfg.restore(data[0]) self._tcfg.restore(data[1]) self._ucfg.restore(data[2]) def setconfig(self, section, name, value, source=''): for cfg in (self._ocfg, self._tcfg, self._ucfg): cfg.set(section, name, value, source) self.fixconfig(section=section) self._maybetweakdefaults() def _data(self, untrusted): return untrusted and self._ucfg or self._tcfg def configsource(self, section, name, untrusted=False): return self._data(untrusted).source(section, name) def config(self, section, name, default=_unset, untrusted=False): """return the plain string version of a config""" value = self._config(section, name, default=default, untrusted=untrusted) if value is _unset: return None return value def _config(self, section, name, default=_unset, untrusted=False): value = itemdefault = default item = self._knownconfig.get(section, {}).get(name) alternates = [(section, name)] if item is not None: alternates.extend(item.alias) if callable(item.default): itemdefault = item.default() else: itemdefault = item.default else: msg = ("accessing unregistered config item: '%s.%s'") msg %= (section, name) self.develwarn(msg, 2, 'warn-config-unknown') if default is _unset: if item is None: value = default elif item.default is configitems.dynamicdefault: value = None msg = "config item requires an explicit default value: '%s.%s'" msg %= (section, name) self.develwarn(msg, 2, 'warn-config-default') else: value = itemdefault elif (item is not None and item.default is not configitems.dynamicdefault and default != itemdefault): msg = ("specifying a mismatched default value for a registered " "config item: '%s.%s' '%s'") msg %= (section, name, pycompat.bytestr(default)) self.develwarn(msg, 2, 'warn-config-default') for s, n in alternates: candidate = self._data(untrusted).get(s, n, None) if candidate is not None: value = candidate break if self.debugflag and not untrusted and self._reportuntrusted: for s, n in alternates: uvalue = self._ucfg.get(s, n) if uvalue is not None and uvalue != value: self.debug("ignoring untrusted configuration option " "%s.%s = %s\n" % (s, n, uvalue)) return value def configsuboptions(self, section, name, default=_unset, untrusted=False): """Get a config option and all sub-options. Some config options have sub-options that are declared with the format "key:opt = value". This method is used to return the main option and all its declared sub-options. Returns a 2-tuple of ``(option, sub-options)``, where `sub-options`` is a dict of defined sub-options where keys and values are strings. """ main = self.config(section, name, default, untrusted=untrusted) data = self._data(untrusted) sub = {} prefix = '%s:' % name for k, v in data.items(section): if k.startswith(prefix): sub[k[len(prefix):]] = v if self.debugflag and not untrusted and self._reportuntrusted: for k, v in sub.items(): uvalue = self._ucfg.get(section, '%s:%s' % (name, k)) if uvalue is not None and uvalue != v: self.debug('ignoring untrusted configuration option ' '%s:%s.%s = %s\n' % (section, name, k, uvalue)) return main, sub def configpath(self, section, name, default=_unset, untrusted=False): 'get a path config item, expanded relative to repo root or config file' v = self.config(section, name, default, untrusted) if v is None: return None if not os.path.isabs(v) or "://" not in v: src = self.configsource(section, name, untrusted) if ':' in src: base = os.path.dirname(src.rsplit(':')[0]) v = os.path.join(base, os.path.expanduser(v)) return v def configbool(self, section, name, default=_unset, untrusted=False): """parse a configuration element as a boolean >>> u = ui(); s = b'foo' >>> u.setconfig(s, b'true', b'yes') >>> u.configbool(s, b'true') True >>> u.setconfig(s, b'false', b'no') >>> u.configbool(s, b'false') False >>> u.configbool(s, b'unknown') False >>> u.configbool(s, b'unknown', True) True >>> u.setconfig(s, b'invalid', b'somevalue') >>> u.configbool(s, b'invalid') Traceback (most recent call last): ... ConfigError: foo.invalid is not a boolean ('somevalue') """ v = self._config(section, name, default, untrusted=untrusted) if v is None: return v if v is _unset: if default is _unset: return False return default if isinstance(v, bool): return v b = stringutil.parsebool(v) if b is None: raise error.ConfigError(_("%s.%s is not a boolean ('%s')") % (section, name, v)) return b def configwith(self, convert, section, name, default=_unset, desc=None, untrusted=False): """parse a configuration element with a conversion function >>> u = ui(); s = b'foo' >>> u.setconfig(s, b'float1', b'42') >>> u.configwith(float, s, b'float1') 42.0 >>> u.setconfig(s, b'float2', b'-4.25') >>> u.configwith(float, s, b'float2') -4.25 >>> u.configwith(float, s, b'unknown', 7) 7.0 >>> u.setconfig(s, b'invalid', b'somevalue') >>> u.configwith(float, s, b'invalid') Traceback (most recent call last): ... ConfigError: foo.invalid is not a valid float ('somevalue') >>> u.configwith(float, s, b'invalid', desc=b'womble') Traceback (most recent call last): ... ConfigError: foo.invalid is not a valid womble ('somevalue') """ v = self.config(section, name, default, untrusted) if v is None: return v # do not attempt to convert None try: return convert(v) except (ValueError, error.ParseError): if desc is None: desc = pycompat.sysbytes(convert.__name__) raise error.ConfigError(_("%s.%s is not a valid %s ('%s')") % (section, name, desc, v)) def configint(self, section, name, default=_unset, untrusted=False): """parse a configuration element as an integer >>> u = ui(); s = b'foo' >>> u.setconfig(s, b'int1', b'42') >>> u.configint(s, b'int1') 42 >>> u.setconfig(s, b'int2', b'-42') >>> u.configint(s, b'int2') -42 >>> u.configint(s, b'unknown', 7) 7 >>> u.setconfig(s, b'invalid', b'somevalue') >>> u.configint(s, b'invalid') Traceback (most recent call last): ... ConfigError: foo.invalid is not a valid integer ('somevalue') """ return self.configwith(int, section, name, default, 'integer', untrusted) def configbytes(self, section, name, default=_unset, untrusted=False): """parse a configuration element as a quantity in bytes Units can be specified as b (bytes), k or kb (kilobytes), m or mb (megabytes), g or gb (gigabytes). >>> u = ui(); s = b'foo' >>> u.setconfig(s, b'val1', b'42') >>> u.configbytes(s, b'val1') 42 >>> u.setconfig(s, b'val2', b'42.5 kb') >>> u.configbytes(s, b'val2') 43520 >>> u.configbytes(s, b'unknown', b'7 MB') 7340032 >>> u.setconfig(s, b'invalid', b'somevalue') >>> u.configbytes(s, b'invalid') Traceback (most recent call last): ... ConfigError: foo.invalid is not a byte quantity ('somevalue') """ value = self._config(section, name, default, untrusted) if value is _unset: if default is _unset: default = 0 value = default if not isinstance(value, bytes): return value try: return util.sizetoint(value) except error.ParseError: raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')") % (section, name, value)) def configlist(self, section, name, default=_unset, untrusted=False): """parse a configuration element as a list of comma/space separated strings >>> u = ui(); s = b'foo' >>> u.setconfig(s, b'list1', b'this,is "a small" ,test') >>> u.configlist(s, b'list1') ['this', 'is', 'a small', 'test'] >>> u.setconfig(s, b'list2', b'this, is "a small" , test ') >>> u.configlist(s, b'list2') ['this', 'is', 'a small', 'test'] """ # default is not always a list v = self.configwith(config.parselist, section, name, default, 'list', untrusted) if isinstance(v, bytes): return config.parselist(v) elif v is None: return [] return v def configdate(self, section, name, default=_unset, untrusted=False): """parse a configuration element as a tuple of ints >>> u = ui(); s = b'foo' >>> u.setconfig(s, b'date', b'0 0') >>> u.configdate(s, b'date') (0, 0) """ if self.config(section, name, default, untrusted): return self.configwith(dateutil.parsedate, section, name, default, 'date', untrusted) if default is _unset: return None return default def hasconfig(self, section, name, untrusted=False): return self._data(untrusted).hasitem(section, name) def has_section(self, section, untrusted=False): '''tell whether section exists in config.''' return section in self._data(untrusted) def configitems(self, section, untrusted=False, ignoresub=False): items = self._data(untrusted).items(section) if ignoresub: items = [i for i in items if ':' not in i[0]] if self.debugflag and not untrusted and self._reportuntrusted: for k, v in self._ucfg.items(section): if self._tcfg.get(section, k) != v: self.debug("ignoring untrusted configuration option " "%s.%s = %s\n" % (section, k, v)) return items def walkconfig(self, untrusted=False): cfg = self._data(untrusted) for section in cfg.sections(): for name, value in self.configitems(section, untrusted): yield section, name, value def plain(self, feature=None): '''is plain mode active? Plain mode means that all configuration variables which affect the behavior and output of Mercurial should be ignored. Additionally, the output should be stable, reproducible and suitable for use in scripts or applications. The only way to trigger plain mode is by setting either the `HGPLAIN' or `HGPLAINEXCEPT' environment variables. The return value can either be - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT - False if feature is disabled by default and not included in HGPLAIN - True otherwise ''' if ('HGPLAIN' not in encoding.environ and 'HGPLAINEXCEPT' not in encoding.environ): return False exceptions = encoding.environ.get('HGPLAINEXCEPT', '').strip().split(',') # TODO: add support for HGPLAIN=+feature,-feature syntax if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','): exceptions.append('strictflags') if feature and exceptions: return feature not in exceptions return True def username(self, acceptempty=False): """Return default username to be used in commits. Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL and stop searching if one of these is set. If not found and acceptempty is True, returns None. If not found and ui.askusername is True, ask the user, else use ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname". If no username could be found, raise an Abort error. """ user = encoding.environ.get("HGUSER") if user is None: user = self.config("ui", "username") if user is not None: user = os.path.expandvars(user) if user is None: user = encoding.environ.get("EMAIL") if user is None and acceptempty: return user if user is None and self.configbool("ui", "askusername"): user = self.prompt(_("enter a commit username:"), default=None) if user is None and not self.interactive(): try: user = '%s@%s' % (procutil.getuser(), encoding.strtolocal(socket.getfqdn())) self.warn(_("no username found, using '%s' instead\n") % user) except KeyError: pass if not user: raise error.Abort(_('no username supplied'), hint=_("use 'hg config --edit' " 'to set your username')) if "\n" in user: raise error.Abort(_("username %r contains a newline\n") % pycompat.bytestr(user)) return user def shortuser(self, user): """Return a short representation of a user name or email address.""" if not self.verbose: user = stringutil.shortuser(user) return user def expandpath(self, loc, default=None): """Return repository location relative to cwd or from [paths]""" try: p = self.paths.getpath(loc) if p: return p.rawloc except error.RepoError: pass if default: try: p = self.paths.getpath(default) if p: return p.rawloc except error.RepoError: pass return loc @util.propertycache def paths(self): return paths(self) @property def fout(self): return self._fout @fout.setter def fout(self, f): self._fout = f self._fmsgout, self._fmsgerr = _selectmsgdests(self) @property def ferr(self): return self._ferr @ferr.setter def ferr(self, f): self._ferr = f self._fmsgout, self._fmsgerr = _selectmsgdests(self) @property def fin(self): return self._fin @fin.setter def fin(self, f): self._fin = f @property def fmsg(self): """Stream dedicated for status/error messages; may be None if fout/ferr are used""" return self._fmsg @fmsg.setter def fmsg(self, f): self._fmsg = f self._fmsgout, self._fmsgerr = _selectmsgdests(self) def pushbuffer(self, error=False, subproc=False, labeled=False): """install a buffer to capture standard output of the ui object If error is True, the error output will be captured too. If subproc is True, output from subprocesses (typically hooks) will be captured too. If labeled is True, any labels associated with buffered output will be handled. By default, this has no effect on the output returned, but extensions and GUI tools may handle this argument and returned styled output. If output is being buffered so it can be captured and parsed or processed, labeled should not be set to True. """ self._buffers.append([]) self._bufferstates.append((error, subproc, labeled)) self._bufferapplylabels = labeled def popbuffer(self): '''pop the last buffer and return the buffered output''' self._bufferstates.pop() if self._bufferstates: self._bufferapplylabels = self._bufferstates[-1][2] else: self._bufferapplylabels = None return "".join(self._buffers.pop()) def _isbuffered(self, dest): if dest is self._fout: return bool(self._buffers) if dest is self._ferr: return bool(self._bufferstates and self._bufferstates[-1][0]) return False def canwritewithoutlabels(self): '''check if write skips the label''' if self._buffers and not self._bufferapplylabels: return True return self._colormode is None def canbatchlabeledwrites(self): '''check if write calls with labels are batchable''' # Windows color printing is special, see ``write``. return self._colormode != 'win32' def write(self, *args, **opts): '''write args to output By default, this method simply writes to the buffer or stdout. Color mode can be set on the UI class to have the output decorated with color modifier before being written to stdout. The color used is controlled by an optional keyword argument, "label". This should be a string containing label names separated by space. Label names take the form of "topic.type". For example, ui.debug() issues a label of "ui.debug". When labeling output for a specific command, a label of "cmdname.type" is recommended. For example, status issues a label of "status.modified" for modified files. ''' dest = self._fout # inlined _write() for speed if self._buffers: label = opts.get(r'label', '') if label and self._bufferapplylabels: self._buffers[-1].extend(self.label(a, label) for a in args) else: self._buffers[-1].extend(args) return # inliend _writenobuf() for speed self._progclear() msg = b''.join(args) # opencode timeblockedsection because this is a critical path starttime = util.timer() try: if self._colormode == 'win32': # windows color printing is its own can of crab, defer to # the color module and that is it. color.win32print(self, dest.write, msg, **opts) else: if self._colormode is not None: label = opts.get(r'label', '') msg = self.label(msg, label) dest.write(msg) except IOError as err: raise error.StdioError(err) finally: self._blockedtimes['stdio_blocked'] += ( (util.timer() - starttime) * 1000) def write_err(self, *args, **opts): self._write(self._ferr, *args, **opts) def _write(self, dest, *args, **opts): # update write() as well if you touch this code if self._isbuffered(dest): label = opts.get(r'label', '') if label and self._bufferapplylabels: self._buffers[-1].extend(self.label(a, label) for a in args) else: self._buffers[-1].extend(args) else: self._writenobuf(dest, *args, **opts) def _writenobuf(self, dest, *args, **opts): # update write() as well if you touch this code self._progclear() msg = b''.join(args) # opencode timeblockedsection because this is a critical path starttime = util.timer() try: if dest is self._ferr and not getattr(self._fout, 'closed', False): self._fout.flush() if getattr(dest, 'structured', False): # channel for machine-readable output with metadata, where # no extra colorization is necessary. dest.write(msg, **opts) elif self._colormode == 'win32': # windows color printing is its own can of crab, defer to # the color module and that is it. color.win32print(self, dest.write, msg, **opts) else: if self._colormode is not None: label = opts.get(r'label', '') msg = self.label(msg, label) dest.write(msg) # stderr may be buffered under win32 when redirected to files, # including stdout. if dest is self._ferr and not getattr(self._ferr, 'closed', False): dest.flush() except IOError as err: if (dest is self._ferr and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)): # no way to report the error, so ignore it return raise error.StdioError(err) finally: self._blockedtimes['stdio_blocked'] += ( (util.timer() - starttime) * 1000) def _writemsg(self, dest, *args, **opts): _writemsgwith(self._write, dest, *args, **opts) def _writemsgnobuf(self, dest, *args, **opts): _writemsgwith(self._writenobuf, dest, *args, **opts) def flush(self): # opencode timeblockedsection because this is a critical path starttime = util.timer() try: try: self._fout.flush() except IOError as err: if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): raise error.StdioError(err) finally: try: self._ferr.flush() except IOError as err: if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): raise error.StdioError(err) finally: self._blockedtimes['stdio_blocked'] += ( (util.timer() - starttime) * 1000) def _isatty(self, fh): if self.configbool('ui', 'nontty'): return False return procutil.isatty(fh) def protectfinout(self): """Duplicate ui streams and redirect original if they are stdio Returns (fin, fout) which point to the original ui fds, but may be copy of them. The returned streams can be considered "owned" in that print(), exec(), etc. never reach to them. """ if self._finoutredirected: # if already redirected, protectstdio() would just create another # nullfd pair, which is equivalent to returning self._fin/_fout. return self._fin, self._fout fin, fout = procutil.protectstdio(self._fin, self._fout) self._finoutredirected = (fin, fout) != (self._fin, self._fout) return fin, fout def restorefinout(self, fin, fout): """Restore ui streams from possibly duplicated (fin, fout)""" if (fin, fout) == (self._fin, self._fout): return procutil.restorestdio(self._fin, self._fout, fin, fout) # protectfinout() won't create more than one duplicated streams, # so we can just turn the redirection flag off. self._finoutredirected = False @contextlib.contextmanager def protectedfinout(self): """Run code block with protected standard streams""" fin, fout = self.protectfinout() try: yield fin, fout finally: self.restorefinout(fin, fout) def disablepager(self): self._disablepager = True def pager(self, command): """Start a pager for subsequent command output. Commands which produce a long stream of output should call this function to activate the user's preferred pagination mechanism (which may be no pager). Calling this function precludes any future use of interactive functionality, such as prompting the user or activating curses. Args: command: The full, non-aliased name of the command. That is, "log" not "history, "summary" not "summ", etc. """ if (self._disablepager or self.pageractive): # how pager should do is already determined return if not command.startswith('internal-always-') and ( # explicit --pager=on (= 'internal-always-' prefix) should # take precedence over disabling factors below command in self.configlist('pager', 'ignore') or not self.configbool('ui', 'paginate') or not self.configbool('pager', 'attend-' + command, True) or encoding.environ.get('TERM') == 'dumb' # TODO: if we want to allow HGPLAINEXCEPT=pager, # formatted() will need some adjustment. or not self.formatted() or self.plain() or self._buffers # TODO: expose debugger-enabled on the UI object or '--debugger' in pycompat.sysargv): # We only want to paginate if the ui appears to be # interactive, the user didn't say HGPLAIN or # HGPLAINEXCEPT=pager, and the user didn't specify --debug. return pagercmd = self.config('pager', 'pager', rcutil.fallbackpager) if not pagercmd: return pagerenv = {} for name, value in rcutil.defaultpagerenv().items(): if name not in encoding.environ: pagerenv[name] = value self.debug('starting pager for command %s\n' % stringutil.pprint(command)) self.flush() wasformatted = self.formatted() if util.safehasattr(signal, "SIGPIPE"): signal.signal(signal.SIGPIPE, _catchterm) if self._runpager(pagercmd, pagerenv): self.pageractive = True # Preserve the formatted-ness of the UI. This is important # because we mess with stdout, which might confuse # auto-detection of things being formatted. self.setconfig('ui', 'formatted', wasformatted, 'pager') self.setconfig('ui', 'interactive', False, 'pager') # If pagermode differs from color.mode, reconfigure color now that # pageractive is set. cm = self._colormode if cm != self.config('color', 'pagermode', cm): color.setup(self) else: # If the pager can't be spawned in dispatch when --pager=on is # given, don't try again when the command runs, to avoid a duplicate # warning about a missing pager command. self.disablepager() def _runpager(self, command, env=None): """Actually start the pager and set up file descriptors. This is separate in part so that extensions (like chg) can override how a pager is invoked. """ if command == 'cat': # Save ourselves some work. return False # If the command doesn't contain any of these characters, we # assume it's a binary and exec it directly. This means for # simple pager command configurations, we can degrade # gracefully and tell the user about their broken pager. shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%") if pycompat.iswindows and not shell: # Window's built-in `more` cannot be invoked with shell=False, but # its `more.com` can. Hide this implementation detail from the # user so we can also get sane bad PAGER behavior. MSYS has # `more.exe`, so do a cmd.exe style resolution of the executable to # determine which one to use. fullcmd = procutil.findexe(command) if not fullcmd: self.warn(_("missing pager command '%s', skipping pager\n") % command) return False command = fullcmd try: pager = subprocess.Popen( procutil.tonativestr(command), shell=shell, bufsize=-1, close_fds=procutil.closefds, stdin=subprocess.PIPE, stdout=procutil.stdout, stderr=procutil.stderr, env=procutil.tonativeenv(procutil.shellenviron(env))) except OSError as e: if e.errno == errno.ENOENT and not shell: self.warn(_("missing pager command '%s', skipping pager\n") % command) return False raise # back up original file descriptors stdoutfd = os.dup(procutil.stdout.fileno()) stderrfd = os.dup(procutil.stderr.fileno()) os.dup2(pager.stdin.fileno(), procutil.stdout.fileno()) if self._isatty(procutil.stderr): os.dup2(pager.stdin.fileno(), procutil.stderr.fileno()) @self.atexit def killpager(): if util.safehasattr(signal, "SIGINT"): signal.signal(signal.SIGINT, signal.SIG_IGN) # restore original fds, closing pager.stdin copies in the process os.dup2(stdoutfd, procutil.stdout.fileno()) os.dup2(stderrfd, procutil.stderr.fileno()) pager.stdin.close() pager.wait() return True @property def _exithandlers(self): return _reqexithandlers def atexit(self, func, *args, **kwargs): '''register a function to run after dispatching a request Handlers do not stay registered across request boundaries.''' self._exithandlers.append((func, args, kwargs)) return func def interface(self, feature): """what interface to use for interactive console features? The interface is controlled by the value of `ui.interface` but also by the value of feature-specific configuration. For example: ui.interface.histedit = text ui.interface.chunkselector = curses Here the features are "histedit" and "chunkselector". The configuration above means that the default interfaces for commands is curses, the interface for histedit is text and the interface for selecting chunk is crecord (the best curses interface available). Consider the following example: ui.interface = curses ui.interface.histedit = text Then histedit will use the text interface and chunkselector will use the default curses interface (crecord at the moment). """ alldefaults = frozenset(["text", "curses"]) featureinterfaces = { "chunkselector": [ "text", "curses", ], "histedit": [ "text", "curses", ], } # Feature-specific interface if feature not in featureinterfaces.keys(): # Programming error, not user error raise ValueError("Unknown feature requested %s" % feature) availableinterfaces = frozenset(featureinterfaces[feature]) if alldefaults > availableinterfaces: # Programming error, not user error. We need a use case to # define the right thing to do here. raise ValueError( "Feature %s does not handle all default interfaces" % feature) if self.plain() or encoding.environ.get('TERM') == 'dumb': return "text" # Default interface for all the features defaultinterface = "text" i = self.config("ui", "interface") if i in alldefaults: defaultinterface = i choseninterface = defaultinterface f = self.config("ui", "interface.%s" % feature) if f in availableinterfaces: choseninterface = f if i is not None and defaultinterface != i: if f is not None: self.warn(_("invalid value for ui.interface: %s\n") % (i,)) else: self.warn(_("invalid value for ui.interface: %s (using %s)\n") % (i, choseninterface)) if f is not None and choseninterface != f: self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") % (feature, f, choseninterface)) return choseninterface def interactive(self): '''is interactive input allowed? An interactive session is a session where input can be reasonably read from `sys.stdin'. If this function returns false, any attempt to read from stdin should fail with an error, unless a sensible default has been specified. Interactiveness is triggered by the value of the `ui.interactive' configuration variable or - if it is unset - when `sys.stdin' points to a terminal device. This function refers to input only; for output, see `ui.formatted()'. ''' i = self.configbool("ui", "interactive") if i is None: # some environments replace stdin without implementing isatty # usually those are non-interactive return self._isatty(self._fin) return i def termwidth(self): '''how wide is the terminal in columns? ''' if 'COLUMNS' in encoding.environ: try: return int(encoding.environ['COLUMNS']) except ValueError: pass return scmutil.termsize(self)[0] def formatted(self): '''should formatted output be used? It is often desirable to format the output to suite the output medium. Examples of this are truncating long lines or colorizing messages. However, this is not often not desirable when piping output into other utilities, e.g. `grep'. Formatted output is triggered by the value of the `ui.formatted' configuration variable or - if it is unset - when `sys.stdout' points to a terminal device. Please note that `ui.formatted' should be considered an implementation detail; it is not intended for use outside Mercurial or its extensions. This function refers to output only; for input, see `ui.interactive()'. This function always returns false when in plain mode, see `ui.plain()'. ''' if self.plain(): return False i = self.configbool("ui", "formatted") if i is None: # some environments replace stdout without implementing isatty # usually those are non-interactive return self._isatty(self._fout) return i def _readline(self, prompt=' ', promptopts=None): # Replacing stdin/stdout temporarily is a hard problem on Python 3 # because they have to be text streams with *no buffering*. Instead, # we use rawinput() only if call_readline() will be invoked by # PyOS_Readline(), so no I/O will be made at Python layer. usereadline = (self._isatty(self._fin) and self._isatty(self._fout) and procutil.isstdin(self._fin) and procutil.isstdout(self._fout)) if usereadline: try: # magically add command line editing support, where # available import readline # force demandimport to really load the module readline.read_history_file # windows sometimes raises something other than ImportError except Exception: usereadline = False if self._colormode == 'win32' or not usereadline: if not promptopts: promptopts = {} self._writemsgnobuf(self._fmsgout, prompt, type='prompt', **promptopts) self.flush() prompt = ' ' else: prompt = self.label(prompt, 'ui.prompt') + ' ' # prompt ' ' must exist; otherwise readline may delete entire line # - http://bugs.python.org/issue12833 with self.timeblockedsection('stdio'): if usereadline: line = encoding.strtolocal(pycompat.rawinput(prompt)) # When stdin is in binary mode on Windows, it can cause # raw_input() to emit an extra trailing carriage return if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'): line = line[:-1] else: self._fout.write(pycompat.bytestr(prompt)) self._fout.flush() line = self._fin.readline() if not line: raise EOFError line = line.rstrip(pycompat.oslinesep) return line def prompt(self, msg, default="y"): """Prompt user with msg, read response. If ui is not interactive, the default is returned. """ return self._prompt(msg, default=default) def _prompt(self, msg, **opts): default = opts[r'default'] if not self.interactive(): self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts) self._writemsg(self._fmsgout, default or '', "\n", type='promptecho') return default try: r = self._readline(prompt=msg, promptopts=opts) if not r: r = default if self.configbool('ui', 'promptecho'): self._writemsg(self._fmsgout, r, "\n", type='promptecho') return r except EOFError: raise error.ResponseExpected() @staticmethod def extractchoices(prompt): """Extract prompt message and list of choices from specified prompt. This returns tuple "(message, choices)", and "choices" is the list of tuple "(response character, text without &)". >>> ui.extractchoices(b"awake? $$ &Yes $$ &No") ('awake? ', [('y', 'Yes'), ('n', 'No')]) >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No") ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')]) >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o") ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')]) """ # Sadly, the prompt string may have been built with a filename # containing "$$" so let's try to find the first valid-looking # prompt to start parsing. Sadly, we also can't rely on # choices containing spaces, ASCII, or basically anything # except an ampersand followed by a character. m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt) msg = m.group(1) choices = [p.strip(' ') for p in m.group(2).split('$$')] def choicetuple(s): ampidx = s.index('&') return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1) return (msg, [choicetuple(s) for s in choices]) def promptchoice(self, prompt, default=0): """Prompt user with a message, read response, and ensure it matches one of the provided choices. The prompt is formatted as follows: "would you like fries with that (Yn)? $$ &Yes $$ &No" The index of the choice is returned. Responses are case insensitive. If ui is not interactive, the default is returned. """ msg, choices = self.extractchoices(prompt) resps = [r for r, t in choices] while True: r = self._prompt(msg, default=resps[default], choices=choices) if r.lower() in resps: return resps.index(r.lower()) # TODO: shouldn't it be a warning? self._writemsg(self._fmsgout, _("unrecognized response\n")) def getpass(self, prompt=None, default=None): if not self.interactive(): return default try: self._writemsg(self._fmsgerr, prompt or _('password: '), type='prompt', password=True) # disable getpass() only if explicitly specified. it's still valid # to interact with tty even if fin is not a tty. with self.timeblockedsection('stdio'): if self.configbool('ui', 'nontty'): l = self._fin.readline() if not l: raise EOFError return l.rstrip('\n') else: return getpass.getpass(r'') except EOFError: raise error.ResponseExpected() def status(self, *msg, **opts): '''write status message to output (if ui.quiet is False) This adds an output label of "ui.status". ''' if not self.quiet: self._writemsg(self._fmsgout, type='status', *msg, **opts) def warn(self, *msg, **opts): '''write warning message to output (stderr) This adds an output label of "ui.warning". ''' self._writemsg(self._fmsgerr, type='warning', *msg, **opts) def error(self, *msg, **opts): '''write error message to output (stderr) This adds an output label of "ui.error". ''' self._writemsg(self._fmsgerr, type='error', *msg, **opts) def note(self, *msg, **opts): '''write note to output (if ui.verbose is True) This adds an output label of "ui.note". ''' if self.verbose: self._writemsg(self._fmsgout, type='note', *msg, **opts) def debug(self, *msg, **opts): '''write debug message to output (if ui.debugflag is True) This adds an output label of "ui.debug". ''' if self.debugflag: self._writemsg(self._fmsgout, type='debug', *msg, **opts) self.log(b'debug', b'%s', b''.join(msg)) def edit(self, text, user, extra=None, editform=None, pending=None, repopath=None, action=None): if action is None: self.develwarn('action is None but will soon be a required ' 'parameter to ui.edit()') extra_defaults = { 'prefix': 'editor', 'suffix': '.txt', } if extra is not None: if extra.get('suffix') is not None: self.develwarn('extra.suffix is not None but will soon be ' 'ignored by ui.edit()') extra_defaults.update(extra) extra = extra_defaults if action == 'diff': suffix = '.diff' elif action: suffix = '.%s.hg.txt' % action else: suffix = extra['suffix'] rdir = None if self.configbool('experimental', 'editortmpinhg'): rdir = repopath (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-', suffix=suffix, dir=rdir) try: f = os.fdopen(fd, r'wb') f.write(util.tonativeeol(text)) f.close() environ = {'HGUSER': user} if 'transplant_source' in extra: environ.update({'HGREVISION': hex(extra['transplant_source'])}) for label in ('intermediate-source', 'source', 'rebase_source'): if label in extra: environ.update({'HGREVISION': extra[label]}) break if editform: environ.update({'HGEDITFORM': editform}) if pending: environ.update({'HG_PENDING': pending}) editor = self.geteditor() self.system("%s \"%s\"" % (editor, name), environ=environ, onerr=error.Abort, errprefix=_("edit failed"), blockedtag='editor') f = open(name, r'rb') t = util.fromnativeeol(f.read()) f.close() finally: os.unlink(name) return t def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None, blockedtag=None): '''execute shell command with appropriate output stream. command output will be redirected if fout is not stdout. if command fails and onerr is None, return status, else raise onerr object as exception. ''' if blockedtag is None: # Long cmds tend to be because of an absolute path on cmd. Keep # the tail end instead cmdsuffix = cmd.translate(None, _keepalnum)[-85:] blockedtag = 'unknown_system_' + cmdsuffix out = self._fout if any(s[1] for s in self._bufferstates): out = self with self.timeblockedsection(blockedtag): rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out) if rc and onerr: errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]), procutil.explainexit(rc)) if errprefix: errmsg = '%s: %s' % (errprefix, errmsg) raise onerr(errmsg) return rc def _runsystem(self, cmd, environ, cwd, out): """actually execute the given shell command (can be overridden by extensions like chg)""" return procutil.system(cmd, environ=environ, cwd=cwd, out=out) def traceback(self, exc=None, force=False): '''print exception traceback if traceback printing enabled or forced. only to call in exception handler. returns true if traceback printed.''' if self.tracebackflag or force: if exc is None: exc = sys.exc_info() cause = getattr(exc[1], 'cause', None) if cause is not None: causetb = traceback.format_tb(cause[2]) exctb = traceback.format_tb(exc[2]) exconly = traceback.format_exception_only(cause[0], cause[1]) # exclude frame where 'exc' was chained and rethrown from exctb self.write_err('Traceback (most recent call last):\n', ''.join(exctb[:-1]), ''.join(causetb), ''.join(exconly)) else: output = traceback.format_exception(exc[0], exc[1], exc[2]) self.write_err(encoding.strtolocal(r''.join(output))) return self.tracebackflag or force def geteditor(self): '''return editor to use''' if pycompat.sysplatform == 'plan9': # vi is the MIPS instruction simulator on Plan 9. We # instead default to E to plumb commit messages to # avoid confusion. editor = 'E' else: editor = 'vi' return (encoding.environ.get("HGEDITOR") or self.config("ui", "editor", editor)) @util.propertycache def _progbar(self): """setup the progbar singleton to the ui object""" if (self.quiet or self.debugflag or self.configbool('progress', 'disable') or not progress.shouldprint(self)): return None return getprogbar(self) def _progclear(self): """clear progress bar output if any. use it before any output""" if not haveprogbar(): # nothing loaded yet return if self._progbar is not None and self._progbar.printed: self._progbar.clear() def progress(self, topic, pos, item="", unit="", total=None): '''show a progress message By default a textual progress bar will be displayed if an operation takes too long. 'topic' is the current operation, 'item' is a non-numeric marker of the current position (i.e. the currently in-process file), 'pos' is the current numeric position (i.e. revision, bytes, etc.), unit is a corresponding unit label, and total is the highest expected pos. Multiple nested topics may be active at a time. All topics should be marked closed by setting pos to None at termination. ''' self.deprecwarn("use ui.makeprogress() instead of ui.progress()", "5.1") progress = self.makeprogress(topic, unit, total) if pos is not None: progress.update(pos, item=item) else: progress.complete() def makeprogress(self, topic, unit="", total=None): """Create a progress helper for the specified topic""" if getattr(self._fmsgerr, 'structured', False): # channel for machine-readable output with metadata, just send # raw information # TODO: consider porting some useful information (e.g. estimated # time) from progbar. we might want to support update delay to # reduce the cost of transferring progress messages. def updatebar(topic, pos, item, unit, total): self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos, item=item, unit=unit, total=total) elif self._progbar is not None: updatebar = self._progbar.progress else: def updatebar(topic, pos, item, unit, total): pass return scmutil.progress(self, updatebar, topic, unit, total) def getlogger(self, name): """Returns a logger of the given name; or None if not registered""" return self._loggers.get(name) def setlogger(self, name, logger): """Install logger which can be identified later by the given name More than one loggers can be registered. Use extension or module name to uniquely identify the logger instance. """ self._loggers[name] = logger def log(self, event, msgfmt, *msgargs, **opts): '''hook for logging facility extensions event should be a readily-identifiable subsystem, which will allow filtering. msgfmt should be a newline-terminated format string to log, and *msgargs are %-formatted into it. **opts currently has no defined meanings. ''' if not self._loggers: return activeloggers = [l for l in self._loggers.itervalues() if l.tracked(event)] if not activeloggers: return msg = msgfmt % msgargs opts = pycompat.byteskwargs(opts) # guard against recursion from e.g. ui.debug() registeredloggers = self._loggers self._loggers = {} try: for logger in activeloggers: logger.log(self, event, msg, opts) finally: self._loggers = registeredloggers def label(self, msg, label): '''style msg based on supplied label If some color mode is enabled, this will add the necessary control characters to apply such color. In addition, 'debug' color mode adds markup showing which label affects a piece of text. ui.write(s, 'label') is equivalent to ui.write(ui.label(s, 'label')). ''' if self._colormode is not None: return color.colorlabel(self, msg, label) return msg def develwarn(self, msg, stacklevel=1, config=None): """issue a developer warning message Use 'stacklevel' to report the offender some layers further up in the stack. """ if not self.configbool('devel', 'all-warnings'): if config is None or not self.configbool('devel', config): return msg = 'devel-warn: ' + msg stacklevel += 1 # get in develwarn if self.tracebackflag: util.debugstacktrace(msg, stacklevel, self._ferr, self._fout) self.log('develwarn', '%s at:\n%s' % (msg, ''.join(util.getstackframes(stacklevel)))) else: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) fname, lineno, fmsg = calframe[stacklevel][1:4] fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg) self.write_err('%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg)) self.log('develwarn', '%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg) curframe = calframe = None # avoid cycles def deprecwarn(self, msg, version, stacklevel=2): """issue a deprecation warning - msg: message explaining what is deprecated and how to upgrade, - version: last version where the API will be supported, """ if not (self.configbool('devel', 'all-warnings') or self.configbool('devel', 'deprec-warn')): return msg += ("\n(compatibility will be dropped after Mercurial-%s," " update your code.)") % version self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn') def exportableenviron(self): """The environment variables that are safe to export, e.g. through hgweb. """ return self._exportableenviron @contextlib.contextmanager def configoverride(self, overrides, source=""): """Context manager for temporary config overrides `overrides` must be a dict of the following structure: {(section, name) : value}""" backups = {} try: for (section, name), value in overrides.items(): backups[(section, name)] = self.backupconfig(section, name) self.setconfig(section, name, value, source) yield finally: for __, backup in backups.items(): self.restoreconfig(backup) # just restoring ui.quiet config to the previous value is not enough # as it does not update ui.quiet class member if ('ui', 'quiet') in overrides: self.fixconfig(section='ui') class paths(dict): """Represents a collection of paths and their configs. Data is initially derived from ui instances and the config files they have loaded. """ def __init__(self, ui): dict.__init__(self) for name, loc in ui.configitems('paths', ignoresub=True): # No location is the same as not existing. if not loc: continue loc, sub = ui.configsuboptions('paths', name) self[name] = path(ui, name, rawloc=loc, suboptions=sub) def getpath(self, name, default=None): """Return a ``path`` from a string, falling back to default. ``name`` can be a named path or locations. Locations are filesystem paths or URIs. Returns None if ``name`` is not a registered path, a URI, or a local path to a repo. """ # Only fall back to default if no path was requested. if name is None: if not default: default = () elif not isinstance(default, (tuple, list)): default = (default,) for k in default: try: return self[k] except KeyError: continue return None # Most likely empty string. # This may need to raise in the future. if not name: return None try: return self[name] except KeyError: # Try to resolve as a local path or URI. try: # We don't pass sub-options in, so no need to pass ui instance. return path(None, None, rawloc=name) except ValueError: raise error.RepoError(_('repository %s does not exist') % name) _pathsuboptions = {} def pathsuboption(option, attr): """Decorator used to declare a path sub-option. Arguments are the sub-option name and the attribute it should set on ``path`` instances. The decorated function will receive as arguments a ``ui`` instance, ``path`` instance, and the string value of this option from the config. The function should return the value that will be set on the ``path`` instance. This decorator can be used to perform additional verification of sub-options and to change the type of sub-options. """ def register(func): _pathsuboptions[option] = (attr, func) return func return register @pathsuboption('pushurl', 'pushloc') def pushurlpathoption(ui, path, value): u = util.url(value) # Actually require a URL. if not u.scheme: ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name) return None # Don't support the #foo syntax in the push URL to declare branch to # push. if u.fragment: ui.warn(_('("#fragment" in paths.%s:pushurl not supported; ' 'ignoring)\n') % path.name) u.fragment = None return bytes(u) @pathsuboption('pushrev', 'pushrev') def pushrevpathoption(ui, path, value): return value class path(object): """Represents an individual path and its configuration.""" def __init__(self, ui, name, rawloc=None, suboptions=None): """Construct a path from its config options. ``ui`` is the ``ui`` instance the path is coming from. ``name`` is the symbolic name of the path. ``rawloc`` is the raw location, as defined in the config. ``pushloc`` is the raw locations pushes should be made to. If ``name`` is not defined, we require that the location be a) a local filesystem path with a .hg directory or b) a URL. If not, ``ValueError`` is raised. """ if not rawloc: raise ValueError('rawloc must be defined') # Locations may define branches via syntax #. u = util.url(rawloc) branch = None if u.fragment: branch = u.fragment u.fragment = None self.url = u self.branch = branch self.name = name self.rawloc = rawloc self.loc = '%s' % u # When given a raw location but not a symbolic name, validate the # location is valid. if not name and not u.scheme and not self._isvalidlocalpath(self.loc): raise ValueError('location is not a URL or path to a local ' 'repo: %s' % rawloc) suboptions = suboptions or {} # Now process the sub-options. If a sub-option is registered, its # attribute will always be present. The value will be None if there # was no valid sub-option. for suboption, (attr, func) in _pathsuboptions.iteritems(): if suboption not in suboptions: setattr(self, attr, None) continue value = func(ui, self, suboptions[suboption]) setattr(self, attr, value) def _isvalidlocalpath(self, path): """Returns True if the given path is a potentially valid repository. This is its own function so that extensions can change the definition of 'valid' in this case (like when pulling from a git repo into a hg one).""" try: return os.path.isdir(os.path.join(path, '.hg')) # Python 2 may return TypeError. Python 3, ValueError. except (TypeError, ValueError): return False @property def suboptions(self): """Return sub-options and their values for this path. This is intended to be used for presentation purposes. """ d = {} for subopt, (attr, _func) in _pathsuboptions.iteritems(): value = getattr(self, attr) if value is not None: d[subopt] = value return d # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created _progresssingleton = None def getprogbar(ui): global _progresssingleton if _progresssingleton is None: # passing 'ui' object to the singleton is fishy, # this is how the extension used to work but feel free to rework it. _progresssingleton = progress.progbar(ui) return _progresssingleton def haveprogbar(): return _progresssingleton is not None def _selectmsgdests(ui): name = ui.config(b'ui', b'message-output') if name == b'channel': if ui.fmsg: return ui.fmsg, ui.fmsg else: # fall back to ferr if channel isn't ready so that status/error # messages can be printed return ui.ferr, ui.ferr if name == b'stdio': return ui.fout, ui.ferr if name == b'stderr': return ui.ferr, ui.ferr raise error.Abort(b'invalid ui.message-output destination: %s' % name) def _writemsgwith(write, dest, *args, **opts): """Write ui message with the given ui._write*() function The specified message type is translated to 'ui.' label if the dest isn't a structured channel, so that the message will be colorized. """ # TODO: maybe change 'type' to a mandatory option if r'type' in opts and not getattr(dest, 'structured', False): opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type') write(dest, *args, **opts) diff --git a/relnotes/next b/relnotes/next --- a/relnotes/next +++ b/relnotes/next @@ -1,70 +1,72 @@ == New Features == * New config `commands.commit.post-status` shows status after successful commit. * `hg root` now has templating support, including support for showing where a repo share's source is. See `hg help -v root` for details. == New Experimental Features == * New config `experimental.log.topo` makes `hg log -G` use topological sorting. This is especially useful for aliases since it lets the alias accept an `-r` option while still using topological sorting with or without the `-r` (unlike if you use the `sort(..., topo)` revset). == Bug Fixes == * issue4292: "hg log and {files} {file_adds} {file_mods} {file_dels} in template show wrong files on merged revision". See details in "Backwards Compatibility Changes". == Backwards Compatibility Changes == * Removed (experimental) support for log graph lines mixing parent/grandparent styles. Setting e.g. `experimental.graphstyle.parent = !` and `experimental.graphstyle.grandparent = 3.` would use `!` for the first three lines of the graph and then `.`. This is no longer supported. * If `ui.origbackuppath` had been (incorrectly) configured to point to a file, we will now replace that file by a directory and put backups in that directory. This is similar to how we would previously replace files *in* the configured directory by subdirectories. * Template keyword `{file_mods}`, `{file_adds}`, and `{file_dels}` have changed behavior on merge commits. They used to be relative to the first parent, but they now consider both parents. `{file_adds}` shows files that exists in the commit but did not exist in either parent. `{file_dels}` shows files that do not exist in the commit but existed in either parent. `{file_mods}` show the remaining files from `{files}` that were not in the other two sets. == Internal API Changes == * Matchers are no longer iterable. Use `match.files()` instead. * `match.visitdir()` and `match.visitchildrenset()` now expect the empty string instead of '.' to indicate the root directory. * `util.dirs()` and `util.finddirs()` now include an entry for the root directory (empty string). + * shelve is no longer an extension now. it will be turned on by default. + * New API to manage unfinished operations: Earlier there were distinct APIs which dealt with unfinished states and separate lists maintaining them that are `cmdutil.afterresolvestates`, `cmdutil.unfinishedstates` and `cmdutil.STATES`. Now these have been unified to a single API which handles the various states and their utilities. This API has been added to `state.py`. Now instead of adding to these 3 lists independently a state for a new operation can be registered using `addunfinished()` in `state` module. * `cmdutil.checkunfinished()` now includes detection for merge too. diff --git a/tests/test-bookflow.t b/tests/test-bookflow.t --- a/tests/test-bookflow.t +++ b/tests/test-bookflow.t @@ -1,292 +1,292 @@ initialize $ make_changes() { > d=`pwd` > [ ! -z $1 ] && cd $1 > echo "test `basename \`pwd\``" >> test > hg commit -Am"${2:-test}" > r=$? > cd $d > return $r > } $ ls -1a . .. $ hg init a $ cd a $ echo 'test' > test; hg commit -Am'test' adding test clone to b $ mkdir ../b $ cd ../b $ hg clone ../a . updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo "[extensions]" >> .hg/hgrc $ echo "bookflow=" >> .hg/hgrc $ hg branch X abort: creating named branches is disabled and you should use bookmarks (see 'hg help bookflow') [255] $ hg bookmark X $ hg bookmarks * X 0:* (glob) $ hg bookmark X abort: bookmark X already exists, to move use the --rev option [255] $ make_changes $ hg push ../a -q $ hg bookmarks \* X 1:* (glob) change a $ cd ../a $ hg up 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 'test' >> test; hg commit -Am'test' pull in b $ cd ../b $ hg pull -u pulling from $TESTTMP/a searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files new changesets * (glob) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (leaving bookmark X) $ hg status $ hg bookmarks X 1:* (glob) check protection of @ bookmark $ hg bookmark @ $ hg bookmarks \* @ 2:* (glob) X 1:* (glob) $ make_changes abort: cannot commit, bookmark @ is protected [255] $ hg status M test $ hg bookmarks \* @ 2:* (glob) X 1:* (glob) $ hg --config bookflow.protect= commit -Am"Updated test" $ hg bookmarks \* @ 3:* (glob) X 1:* (glob) check requirement for an active bookmark $ hg bookmark -i $ hg bookmarks @ 3:* (glob) X 1:* (glob) $ make_changes abort: cannot commit without an active bookmark [255] $ hg revert test $ rm test.orig $ hg status make the bookmark move by updating it on a, and then pulling # add a commit to a $ cd ../a $ hg bookmark X $ hg bookmarks \* X 2:* (glob) $ make_changes $ hg bookmarks * X 3:81af7977fdb9 # go back to b, and check out X $ cd ../b $ hg up X 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (activating bookmark X) $ hg bookmarks @ 3:* (glob) \* X 1:* (glob) # pull, this should move the bookmark forward, because it was changed remotely $ hg pull -u | grep "updating to active bookmark X" updating to active bookmark X $ hg bookmarks @ 3:* (glob) * X 4:81af7977fdb9 the bookmark should not move if it diverged from remote $ hg -R ../a status $ hg -R ../b status $ make_changes ../a $ make_changes ../b $ hg -R ../a status $ hg -R ../b status $ hg -R ../a bookmarks * X 4:238292f60a57 $ hg -R ../b bookmarks @ 3:* (glob) * X 5:096f7e86892d $ cd ../b $ # make sure we cannot push after bookmarks diverged $ hg push -B X | grep abort abort: push creates new remote head * with bookmark 'X'! (glob) (pull and merge or see 'hg help push' for details about pushing new heads) [1] $ hg pull -u | grep divergent divergent bookmark X stored as X@default 1 other divergent bookmarks for "X" $ hg bookmarks @ 3:* (glob) * X 5:096f7e86892d X@default 6:238292f60a57 $ hg id -in 096f7e86892d 5 $ make_changes $ hg status $ hg bookmarks @ 3:* (glob) * X 7:227f941aeb07 X@default 6:238292f60a57 now merge with the remote bookmark $ hg merge X@default --tool :local -q $ hg status M test $ hg commit -m"Merged with X@default" $ hg bookmarks @ 3:* (glob) * X 8:26fed9bb3219 $ hg push -B X | grep bookmark pushing to $TESTTMP/a (?) updating bookmark X $ cd ../a $ hg up -q $ hg bookmarks * X 7:26fed9bb3219 test hg pull when there is more than one descendant $ cd ../a $ hg bookmark Z $ hg bookmark Y $ make_changes . YY $ hg up Z -q $ make_changes . ZZ created new head $ hg bookmarks X 7:26fed9bb3219 Y 8:131e663dbd2a * Z 9:b74a4149df25 $ hg log -r 'p1(Y)' -r 'p1(Z)' -T '{rev}\n' # prove that Y and Z share the same parent 7 $ hg log -r 'Y%Z' -T '{rev}\n' # revs in Y but not in Z 8 $ hg log -r 'Z%Y' -T '{rev}\n' # revs in Z but not in Y 9 $ cd ../b $ hg pull -uq $ hg id b74a4149df25 tip Z $ hg bookmarks | grep \* # no active bookmark [1] test shelving $ cd ../a $ echo anotherfile > anotherfile # this change should not conflict $ hg add anotherfile $ hg commit -m"Change in a" $ cd ../b $ hg up Z | grep Z (activating bookmark Z) $ hg book | grep \* # make sure active bookmark \* Z 10:* (glob) $ echo "test b" >> test $ hg diff --stat test | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) - $ hg --config extensions.shelve= shelve + $ hg shelve shelved as Z 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg pull -uq - $ hg --trace --config extensions.shelve= unshelve + $ hg unshelve unshelving change 'Z' rebasing shelved changes $ hg diff --stat test | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) make the bookmark move by updating it on a, and then pulling with a local change # add a commit to a $ cd ../a $ hg up -C X |fgrep "activating bookmark X" (activating bookmark X) # go back to b, and check out X $ cd ../b $ hg up -C X |fgrep "activating bookmark X" (activating bookmark X) # update and push from a $ make_changes ../a created new head $ echo "more" >> test $ hg pull -u 2>&1 | fgrep -v TESTTMP| fgrep -v "searching for changes" | fgrep -v adding pulling from $TESTTMP/a added 1 changesets with 0 changes to 0 files (+1 heads) updating bookmark X new changesets * (glob) updating to active bookmark X merging test warning: conflicts while merging test! (edit, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges $ hg update -Cq $ rm test.orig make sure that commits aren't possible if working directory is not pointing to active bookmark $ hg -R ../a status $ hg -R ../b status $ hg -R ../a id -i 36a6e592ec06 $ hg -R ../a book | grep X \* X \d+:36a6e592ec06 (re) $ hg -R ../b id -i 36a6e592ec06 $ hg -R ../b book | grep X \* X \d+:36a6e592ec06 (re) $ make_changes ../a $ hg -R ../a book | grep X \* X \d+:f73a71c992b8 (re) $ cd ../b $ hg pull 2>&1 | grep -v add | grep -v pulling | grep -v searching | grep -v changeset updating bookmark X (run 'hg update' to get a working copy) working directory out of sync with active bookmark, run 'hg up X' $ hg id -i # we're still on the old commit 36a6e592ec06 $ hg book | grep X # while the bookmark moved \* X \d+:f73a71c992b8 (re) $ make_changes abort: cannot commit, working directory out of sync with active bookmark (run 'hg up X') [255] $ hg up -Cq -r . # cleanup local changes $ hg status $ hg id -i # we're still on the old commit 36a6e592ec06 $ hg up X -q $ hg id -i # now we're on X f73a71c992b8 $ hg book | grep X \* X \d+:f73a71c992b8 (re) diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -1,414 +1,418 @@ Show all commands except debug commands $ hg debugcomplete add addremove annotate archive backout bisect bookmarks branch branches bundle cat clone commit config copy diff export files forget graft grep heads help identify import incoming init locate log manifest merge outgoing parents paths phase pull push recover remove rename resolve revert rollback root serve + shelve status summary tag tags tip unbundle + unshelve update verify version Show all commands that start with "a" $ hg debugcomplete a add addremove annotate archive Do not show debug commands if there are other candidates $ hg debugcomplete d diff Show debug commands if there are no other candidates $ hg debugcomplete debug debugancestor debugapplystreamclonebundle debugbuilddag debugbundle debugcapabilities debugcheckstate debugcolor debugcommands debugcomplete debugconfig debugcreatestreamclonebundle debugdag debugdata debugdate debugdeltachain debugdirstate debugdiscovery debugdownload debugextensions debugfileset debugformat debugfsinfo debuggetbundle debugignore debugindex debugindexdot debugindexstats debuginstall debugknown debuglabelcomplete debuglocks debugmanifestfulltextcache debugmergestate debugnamecomplete debugobsolete debugp1copies debugp2copies debugpathcomplete debugpathcopies debugpeer debugpickmergetool debugpushkey debugpvec debugrebuilddirstate debugrebuildfncache debugrename debugrevlog debugrevlogindex debugrevspec debugserve debugsetparents debugssl debugsub debugsuccessorssets debugtemplate debuguigetpass debuguiprompt debugupdatecaches debugupgraderepo debugwalk debugwhyunstable debugwireargs debugwireproto Do not show the alias of a debug command if there are other candidates (this should hide rawcommit) $ hg debugcomplete r recover remove rename resolve revert rollback root Show the alias of a debug command if there are no other candidates $ hg debugcomplete rawc Show the global options $ hg debugcomplete --options | sort --color --config --cwd --debug --debugger --encoding --encodingmode --help --hidden --noninteractive --pager --profile --quiet --repository --time --traceback --verbose --version -R -h -q -v -y Show the options for the "serve" command $ hg debugcomplete --options serve | sort --accesslog --address --certificate --cmdserver --color --config --cwd --daemon --daemon-postexec --debug --debugger --encoding --encodingmode --errorlog --help --hidden --ipv6 --name --noninteractive --pager --pid-file --port --prefix --print-url --profile --quiet --repository --stdio --style --subrepos --templates --time --traceback --verbose --version --web-conf -6 -A -E -R -S -a -d -h -n -p -q -t -v -y Show an error if we use --options with an ambiguous abbreviation $ hg debugcomplete --options s hg: command 's' is ambiguous: - serve showconfig status summary + serve shelve showconfig status summary [255] Show all commands + options $ hg debugcommands add: include, exclude, subrepos, dry-run addremove: similarity, subrepos, include, exclude, dry-run annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template archive: no-decode, prefix, rev, type, subrepos, include, exclude backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user bisect: reset, good, bad, skip, extend, command, noupdate bookmarks: force, rev, delete, rename, inactive, list, template branch: force, clean, rev branches: active, closed, rev, template bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure cat: output, rev, decode, include, exclude, template clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos config: untrusted, edit, local, global, template copy: after, force, include, exclude, dry-run debugancestor: debugapplystreamclonebundle: debugbuilddag: mergeable-file, overwritten-file, new-file debugbundle: all, part-type, spec debugcapabilities: debugcheckstate: debugcolor: style debugcommands: debugcomplete: options debugcreatestreamclonebundle: debugdag: tags, branches, dots, spaces debugdata: changelog, manifest, dir debugdate: extended debugdeltachain: changelog, manifest, dir, template debugdirstate: nodates, dates, datesort debugdiscovery: old, nonheads, rev, seed, ssh, remotecmd, insecure debugdownload: output debugextensions: template debugfileset: rev, all-files, show-matcher, show-stage debugformat: template debugfsinfo: debuggetbundle: head, common, type debugignore: debugindex: changelog, manifest, dir, template debugindexdot: changelog, manifest, dir debugindexstats: debuginstall: template debugknown: debuglabelcomplete: debuglocks: force-lock, force-wlock, set-lock, set-wlock debugmanifestfulltextcache: clear, add debugmergestate: debugnamecomplete: debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template debugp1copies: rev debugp2copies: rev debugpathcomplete: full, normal, added, removed debugpathcopies: include, exclude debugpeer: debugpickmergetool: rev, changedelete, include, exclude, tool debugpushkey: debugpvec: debugrebuilddirstate: rev, minimal debugrebuildfncache: debugrename: rev debugrevlog: changelog, manifest, dir, dump debugrevlogindex: changelog, manifest, dir, format debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized debugserve: sshstdio, logiofd, logiofile debugsetparents: debugssl: debugsub: rev debugsuccessorssets: closest debugtemplate: rev, define debuguigetpass: prompt debuguiprompt: prompt debugupdatecaches: debugupgraderepo: optimize, run, backup debugwalk: include, exclude debugwhyunstable: debugwireargs: three, four, five, ssh, remotecmd, insecure debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos export: bookmark, output, switch-parent, rev, text, git, binary, nodates, template files: rev, print0, include, exclude, template, subrepos forget: interactive, include, exclude, dry-run graft: rev, base, continue, stop, abort, edit, log, no-commit, force, currentdate, currentuser, date, user, tool, dry-run grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, all-files, user, date, template, include, exclude heads: rev, topo, active, closed, style, template help: extension, command, keyword, system identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos init: ssh, remotecmd, insecure locate: rev, print0, fullpath, include, exclude log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude manifest: rev, all, template merge: force, rev, preview, abort, tool outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos parents: rev, style, template paths: template phase: public, draft, secret, force, rev pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure push: force, rev, bookmark, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure recover: verify remove: after, force, subrepos, include, exclude, dry-run rename: after, force, include, exclude, dry-run resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template revert: all, date, rev, no-backup, interactive, include, exclude, dry-run rollback: dry-run, force root: template serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, print-url, subrepos + shelve: addremove, unknown, cleanup, date, delete, edit, keep, list, message, name, patch, interactive, stat, include, exclude status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template summary: remote tag: force, local, rev, remove, edit, message, date, user tags: template tip: patch, git, style, template unbundle: update + unshelve: abort, continue, keep, name, tool, date update: clean, check, merge, date, rev, tool verify: full version: template $ hg init a $ cd a $ echo fee > fee $ hg ci -q -Amfee $ hg tag fee $ mkdir fie $ echo dead > fie/dead $ echo live > fie/live $ hg bookmark fo $ hg branch -q fie $ hg ci -q -Amfie $ echo fo > fo $ hg branch -qf default $ hg ci -q -Amfo $ echo Fum > Fum $ hg ci -q -AmFum $ hg bookmark Fum Test debugpathcomplete $ hg debugpathcomplete f fee fie fo $ hg debugpathcomplete -f f fee fie/dead fie/live fo $ hg rm Fum $ hg debugpathcomplete -r F Fum Test debugnamecomplete $ hg debugnamecomplete Fum default fee fie fo tip $ hg debugnamecomplete f fee fie fo Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still used for completions in some shells. $ hg debuglabelcomplete Fum default fee fie fo tip $ hg debuglabelcomplete f fee fie fo diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t --- a/tests/test-copytrace-heuristics.t +++ b/tests/test-copytrace-heuristics.t @@ -1,721 +1,720 @@ Test for the heuristic copytracing algorithm ============================================ $ cat >> $TESTTMP/copytrace.sh << '__EOF__' > initclient() { > cat >> $1/.hg/hgrc < [experimental] > copytrace = heuristics > copytrace.sourcecommitlimit = -1 > EOF > } > __EOF__ $ . "$TESTTMP/copytrace.sh" $ cat >> $HGRCPATH << EOF > [extensions] > rebase= - > shelve= > [alias] > l = log -G -T 'rev: {rev}\ndesc: {desc}\n' > pl = log -G -T 'rev: {rev}, phase: {phase}\ndesc: {desc}\n' > EOF NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to prevent the full copytrace algorithm to run and test the heuristic algorithm without complexing the test cases with public and draft commits. Check filename heuristics (same dirname and same basename) ---------------------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ mkdir dir $ echo a > dir/file.txt $ hg addremove adding a adding dir/file.txt $ hg ci -m initial $ hg mv a b $ hg mv -q dir dir2 $ hg ci -m 'mv a b, mv dir/ dir2/' $ hg up -q 0 $ echo b > a $ echo b > dir/file.txt $ hg ci -qm 'mod a, mod dir/file.txt' $ hg l @ rev: 2 | desc: mod a, mod dir/file.txt | o rev: 1 |/ desc: mv a b, mv dir/ dir2/ o rev: 0 desc: initial $ hg rebase -s . -d 1 rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip) merging b and a to b merging dir2/file.txt and dir/file.txt to dir2/file.txt saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg $ cd .. $ rm -rf repo Make sure filename heuristics do not when they are not related -------------------------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo 'somecontent' > a $ hg add a $ hg ci -m initial $ hg rm a $ echo 'completelydifferentcontext' > b $ hg add b $ hg ci -m 'rm a, add b' $ hg up -q 0 $ printf 'somecontent\nmoarcontent' > a $ hg ci -qm 'mode a' $ hg l @ rev: 2 | desc: mode a | o rev: 1 |/ desc: rm a, add b o rev: 0 desc: initial $ hg rebase -s . -d 1 rebasing 2:d526312210b9 "mode a" (tip) file 'a' was deleted in local [dest] but was modified in other [source]. What do you want to do? use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u unresolved conflicts (see hg resolve, then hg rebase --continue) [1] $ cd .. $ rm -rf repo Test when lca didn't modified the file that was moved ----------------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo 'somecontent' > a $ hg add a $ hg ci -m initial $ echo c > c $ hg add c $ hg ci -m randomcommit $ hg mv a b $ hg ci -m 'mv a b' $ hg up -q 1 $ echo b > a $ hg ci -qm 'mod a' $ hg pl @ rev: 3, phase: draft | desc: mod a | o rev: 2, phase: draft |/ desc: mv a b o rev: 1, phase: draft | desc: randomcommit o rev: 0, phase: draft desc: initial $ hg rebase -s . -d 2 rebasing 3:9d5cf99c3d9f "mod a" (tip) merging b and a to b saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg $ cd .. $ rm -rf repo Rebase "backwards" ------------------ $ hg init repo $ initclient repo $ cd repo $ echo 'somecontent' > a $ hg add a $ hg ci -m initial $ echo c > c $ hg add c $ hg ci -m randomcommit $ hg mv a b $ hg ci -m 'mv a b' $ hg up -q 2 $ echo b > b $ hg ci -qm 'mod b' $ hg l @ rev: 3 | desc: mod b o rev: 2 | desc: mv a b o rev: 1 | desc: randomcommit o rev: 0 desc: initial $ hg rebase -s . -d 0 rebasing 3:fbe97126b396 "mod b" (tip) merging a and b to a saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg $ cd .. $ rm -rf repo Check a few potential move candidates ------------------------------------- $ hg init repo $ initclient repo $ cd repo $ mkdir dir $ echo a > dir/a $ hg add dir/a $ hg ci -qm initial $ hg mv dir/a dir/b $ hg ci -qm 'mv dir/a dir/b' $ mkdir dir2 $ echo b > dir2/a $ hg add dir2/a $ hg ci -qm 'create dir2/a' $ hg up -q 0 $ echo b > dir/a $ hg ci -qm 'mod dir/a' $ hg l @ rev: 3 | desc: mod dir/a | o rev: 2 | | desc: create dir2/a | o rev: 1 |/ desc: mv dir/a dir/b o rev: 0 desc: initial $ hg rebase -s . -d 2 rebasing 3:6b2f4cece40f "mod dir/a" (tip) merging dir/b and dir/a to dir/b saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg $ cd .. $ rm -rf repo Test the copytrace.movecandidateslimit with many move candidates ---------------------------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ hg mv a foo $ echo a > b $ echo a > c $ echo a > d $ echo a > e $ echo a > f $ echo a > g $ hg add b $ hg add c $ hg add d $ hg add e $ hg add f $ hg add g $ hg ci -m 'mv a foo, add many files' $ hg up -q ".^" $ echo b > a $ hg ci -m 'mod a' created new head $ hg l @ rev: 2 | desc: mod a | o rev: 1 |/ desc: mv a foo, add many files o rev: 0 desc: initial With small limit $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0 rebasing 2:ef716627c70b "mod a" (tip) skipping copytracing for 'a', more candidates than the limit: 7 file 'a' was deleted in local [dest] but was modified in other [source]. What do you want to do? use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u unresolved conflicts (see hg resolve, then hg rebase --continue) [1] $ hg rebase --abort rebase aborted With default limit which is 100 $ hg rebase -s 2 -d 1 rebasing 2:ef716627c70b "mod a" (tip) merging foo and a to foo saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg $ cd .. $ rm -rf repo Move file in one branch and delete it in another ----------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ hg mv a b $ hg ci -m 'mv a b' $ hg up -q ".^" $ hg rm a $ hg ci -m 'del a' created new head $ hg pl @ rev: 2, phase: draft | desc: del a | o rev: 1, phase: draft |/ desc: mv a b o rev: 0, phase: draft desc: initial $ hg rebase -s 1 -d 2 rebasing 1:472e38d57782 "mv a b" saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062 $ ls b $ cd .. $ rm -rf repo Move a directory in draft branch -------------------------------- $ hg init repo $ initclient repo $ cd repo $ mkdir dir $ echo a > dir/a $ hg add dir/a $ hg ci -qm initial $ echo b > dir/a $ hg ci -qm 'mod dir/a' $ hg up -q ".^" $ hg mv -q dir/ dir2 $ hg ci -qm 'mv dir/ dir2/' $ hg l @ rev: 2 | desc: mv dir/ dir2/ | o rev: 1 |/ desc: mod dir/a o rev: 0 desc: initial $ hg rebase -s . -d 1 rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip) merging dir/a and dir2/a to dir2/a saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg $ cd .. $ rm -rf server $ rm -rf repo Move file twice and rebase mod on top of moves ---------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ hg mv a b $ hg ci -m 'mv a b' $ hg mv b c $ hg ci -m 'mv b c' $ hg up -q 0 $ echo c > a $ hg ci -m 'mod a' created new head $ hg l @ rev: 3 | desc: mod a | o rev: 2 | | desc: mv b c | o rev: 1 |/ desc: mv a b o rev: 0 desc: initial $ hg rebase -s . -d 2 rebasing 3:d41316942216 "mod a" (tip) merging c and a to c saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg $ cd .. $ rm -rf repo Move file twice and rebase moves on top of mods ----------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ hg mv a b $ hg ci -m 'mv a b' $ hg mv b c $ hg ci -m 'mv b c' $ hg up -q 0 $ echo c > a $ hg ci -m 'mod a' created new head $ hg l @ rev: 3 | desc: mod a | o rev: 2 | | desc: mv b c | o rev: 1 |/ desc: mv a b o rev: 0 desc: initial $ hg rebase -s 1 -d . rebasing 1:472e38d57782 "mv a b" merging a and b to b rebasing 2:d3efd280421d "mv b c" merging b and c to c saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg $ cd .. $ rm -rf repo Move one file and add another file in the same folder in one branch, modify file in another branch -------------------------------------------------------------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ hg mv a b $ hg ci -m 'mv a b' $ echo c > c $ hg add c $ hg ci -m 'add c' $ hg up -q 0 $ echo b > a $ hg ci -m 'mod a' created new head $ hg l @ rev: 3 | desc: mod a | o rev: 2 | | desc: add c | o rev: 1 |/ desc: mv a b o rev: 0 desc: initial $ hg rebase -s . -d 2 rebasing 3:ef716627c70b "mod a" (tip) merging b and a to b saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg $ ls b c $ cat b b $ rm -rf repo Merge test ---------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ echo b > a $ hg ci -m 'modify a' $ hg up -q 0 $ hg mv a b $ hg ci -m 'mv a b' created new head $ hg up -q 2 $ hg l @ rev: 2 | desc: mv a b | o rev: 1 |/ desc: modify a o rev: 0 desc: initial $ hg merge 1 merging b and a to b 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m merge $ ls b $ cd .. $ rm -rf repo Copy and move file ------------------ $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ hg cp a c $ hg mv a b $ hg ci -m 'cp a c, mv a b' $ hg up -q 0 $ echo b > a $ hg ci -m 'mod a' created new head $ hg l @ rev: 2 | desc: mod a | o rev: 1 |/ desc: cp a c, mv a b o rev: 0 desc: initial $ hg rebase -s . -d 1 rebasing 2:ef716627c70b "mod a" (tip) merging b and a to b merging c and a to c saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg $ ls b c $ cat b b $ cat c b $ cd .. $ rm -rf repo Do a merge commit with many consequent moves in one branch ---------------------------------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ echo b > a $ hg ci -qm 'mod a' $ hg up -q ".^" $ hg mv a b $ hg ci -qm 'mv a b' $ hg mv b c $ hg ci -qm 'mv b c' $ hg up -q 1 $ hg l o rev: 3 | desc: mv b c o rev: 2 | desc: mv a b | @ rev: 1 |/ desc: mod a o rev: 0 desc: initial $ hg merge 3 merging a and c to c 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -qm 'merge' $ hg pl @ rev: 4, phase: draft |\ desc: merge | o rev: 3, phase: draft | | desc: mv b c | o rev: 2, phase: draft | | desc: mv a b o | rev: 1, phase: draft |/ desc: mod a o rev: 0, phase: draft desc: initial $ ls c $ cd .. $ rm -rf repo Test shelve/unshelve ------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg add a $ hg ci -m initial $ echo b > a $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg mv a b $ hg ci -m 'mv a b' $ hg l @ rev: 1 | desc: mv a b o rev: 0 desc: initial $ hg unshelve unshelving change 'default' rebasing shelved changes merging b and a to b $ ls b $ cat b b $ cd .. $ rm -rf repo Test full copytrace ability on draft branch ------------------------------------------- File directory and base name changed in same move $ hg init repo $ initclient repo $ mkdir repo/dir1 $ cd repo/dir1 $ echo a > a $ hg add a $ hg ci -qm initial $ cd .. $ hg mv -q dir1 dir2 $ hg mv dir2/a dir2/b $ hg ci -qm 'mv a b; mv dir1 dir2' $ hg up -q '.^' $ cd dir1 $ echo b >> a $ cd .. $ hg ci -qm 'mod a' $ hg pl @ rev: 2, phase: draft | desc: mod a | o rev: 1, phase: draft |/ desc: mv a b; mv dir1 dir2 o rev: 0, phase: draft desc: initial $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100 rebasing 2:6207d2d318e7 "mod a" (tip) merging dir2/b and dir1/a to dir2/b saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg $ cat dir2/b a b $ cd .. $ rm -rf repo Move directory in one merge parent, while adding file to original directory in other merge parent. File moved on rebase. $ hg init repo $ initclient repo $ mkdir repo/dir1 $ cd repo/dir1 $ echo dummy > dummy $ hg add dummy $ cd .. $ hg ci -qm initial $ cd dir1 $ echo a > a $ hg add a $ cd .. $ hg ci -qm 'hg add dir1/a' $ hg up -q '.^' $ hg mv -q dir1 dir2 $ hg ci -qm 'mv dir1 dir2' $ hg pl @ rev: 2, phase: draft | desc: mv dir1 dir2 | o rev: 1, phase: draft |/ desc: hg add dir1/a o rev: 0, phase: draft desc: initial $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100 rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip) saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg $ ls dir2 a dummy $ rm -rf repo Testing the sourcecommitlimit config ----------------------------------- $ hg init repo $ initclient repo $ cd repo $ echo a > a $ hg ci -Aqm "added a" $ echo "more things" >> a $ hg ci -qm "added more things to a" $ hg up 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo b > b $ hg ci -Aqm "added b" $ mkdir foo $ hg mv a foo/bar $ hg ci -m "Moved a to foo/bar" $ hg pl @ rev: 3, phase: draft | desc: Moved a to foo/bar o rev: 2, phase: draft | desc: added b | o rev: 1, phase: draft |/ desc: added more things to a o rev: 0, phase: draft desc: added a When the sourcecommitlimit is small and we have more drafts, we use heuristics only $ hg rebase -s 1 -d . rebasing 1:8b6e13696c38 "added more things to a" file 'a' was deleted in local [dest] but was modified in other [source]. What do you want to do? use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u unresolved conflicts (see hg resolve, then hg rebase --continue) [1] But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do fullcopytracing $ hg rebase --abort rebase aborted $ hg rebase -s 1 -d . --config experimental.copytrace.sourcecommitlimit=100 rebasing 1:8b6e13696c38 "added more things to a" merging foo/bar and a to foo/bar saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg $ cd .. $ rm -rf repo diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t --- a/tests/test-globalopts.t +++ b/tests/test-globalopts.t @@ -1,558 +1,562 @@ $ hg init a $ cd a $ echo a > a $ hg ci -A -d'1 0' -m a adding a $ cd .. $ hg init b $ cd b $ echo b > b $ hg ci -A -d'1 0' -m b adding b $ cd .. $ hg clone a c updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd c $ cat >> .hg/hgrc < [paths] > relative = ../a > EOF $ hg pull -f ../b pulling from ../b searching for changes warning: repository is unrelated requesting all changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) new changesets b6c483daf290 (run 'hg heads' to see heads, 'hg merge' to merge) $ hg merge 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ cd .. Testing -R/--repository: $ hg -R a tip changeset: 0:8580ff50825a tag: tip user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: a $ hg --repository b tip changeset: 0:b6c483daf290 tag: tip user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: b -R with a URL: $ hg -R file:a identify 8580ff50825a tip $ hg -R file://localhost/`pwd`/a/ identify 8580ff50825a tip -R with path aliases: $ cd c $ hg -R default identify 8580ff50825a tip $ hg -R relative identify 8580ff50825a tip $ echo '[paths]' >> $HGRCPATH $ echo 'relativetohome = a' >> $HGRCPATH $ HOME=`pwd`/../ hg -R relativetohome identify 8580ff50825a tip $ cd .. #if no-outer-repo Implicit -R: $ hg ann a/a 0: a $ hg ann a/a a/a 0: a $ hg ann a/a b/b abort: no repository found in '$TESTTMP' (.hg not found)! [255] $ hg -R b ann a/a abort: a/a not under root '$TESTTMP/b' (consider using '--cwd b') [255] $ hg log abort: no repository found in '$TESTTMP' (.hg not found)! [255] #endif Abbreviation of long option: $ hg --repo c tip changeset: 1:b6c483daf290 tag: tip parent: -1:000000000000 user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: b earlygetopt with duplicate options (36d23de02da1): $ hg --cwd a --cwd b --cwd c tip changeset: 1:b6c483daf290 tag: tip parent: -1:000000000000 user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: b $ hg --repo c --repository b -R a tip changeset: 0:8580ff50825a tag: tip user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: a earlygetopt short option without following space: $ hg -q -Rb tip 0:b6c483daf290 earlygetopt with illegal abbreviations: $ hg --confi "foo.bar=baz" abort: option --config may not be abbreviated! [255] $ hg --cw a tip abort: option --cwd may not be abbreviated! [255] $ hg --rep a tip abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo! [255] $ hg --repositor a tip abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo! [255] $ hg -qR a tip abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo! [255] $ hg -qRa tip abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo! [255] Testing --cwd: $ hg --cwd a parents changeset: 0:8580ff50825a tag: tip user: test date: Thu Jan 01 00:00:01 1970 +0000 summary: a Testing -y/--noninteractive - just be sure it is parsed: $ hg --cwd a tip -q --noninteractive 0:8580ff50825a $ hg --cwd a tip -q -y 0:8580ff50825a Testing -q/--quiet: $ hg -R a -q tip 0:8580ff50825a $ hg -R b -q tip 0:b6c483daf290 $ hg -R c --quiet parents 0:8580ff50825a 1:b6c483daf290 Testing -v/--verbose: $ hg --cwd c head -v changeset: 1:b6c483daf290 tag: tip parent: -1:000000000000 user: test date: Thu Jan 01 00:00:01 1970 +0000 files: b description: b changeset: 0:8580ff50825a user: test date: Thu Jan 01 00:00:01 1970 +0000 files: a description: a $ hg --cwd b tip --verbose changeset: 0:b6c483daf290 tag: tip user: test date: Thu Jan 01 00:00:01 1970 +0000 files: b description: b Testing --config: $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo quuxfoo $ hg --cwd c --config '' tip -q abort: malformed --config option: '' (use --config section.name=value) [255] $ hg --cwd c --config a.b tip -q abort: malformed --config option: 'a.b' (use --config section.name=value) [255] $ hg --cwd c --config a tip -q abort: malformed --config option: 'a' (use --config section.name=value) [255] $ hg --cwd c --config a.= tip -q abort: malformed --config option: 'a.=' (use --config section.name=value) [255] $ hg --cwd c --config .b= tip -q abort: malformed --config option: '.b=' (use --config section.name=value) [255] Testing --debug: $ hg --cwd c log --debug changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a tag: tip phase: public parent: -1:0000000000000000000000000000000000000000 parent: -1:0000000000000000000000000000000000000000 manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49 user: test date: Thu Jan 01 00:00:01 1970 +0000 files+: b extra: branch=default description: b changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab phase: public parent: -1:0000000000000000000000000000000000000000 parent: -1:0000000000000000000000000000000000000000 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 user: test date: Thu Jan 01 00:00:01 1970 +0000 files+: a extra: branch=default description: a Testing --traceback: #if no-chg $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback' Traceback (most recent call last): Traceback (most recent call last): (py3 !) #else Traceback for '--config' errors not supported with chg. $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback' [1] #endif Testing --time: $ hg --cwd a --time id 8580ff50825a tip time: real * (glob) Testing --version: $ hg --version -q Mercurial Distributed SCM * (glob) hide outer repo $ hg init Testing -h/--help: #if no-extraextensions $ hg -h Mercurial Distributed SCM list of commands: Repository creation: clone make a copy of an existing repository init create a new repository in the given directory Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) grep search revision history for a pattern in specified files Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory + shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state + unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories Miscellaneous: deprecated Deprecated Features internals Technical implementation topics scripting Using Mercurial from scripts and automation (use 'hg help -v' to show built-in aliases and global options) $ hg --help Mercurial Distributed SCM list of commands: Repository creation: clone make a copy of an existing repository init create a new repository in the given directory Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) grep search revision history for a pattern in specified files Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory + shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state + unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories Miscellaneous: deprecated Deprecated Features internals Technical implementation topics scripting Using Mercurial from scripts and automation (use 'hg help -v' to show built-in aliases and global options) #endif Not tested: --debugger diff --git a/tests/test-help-hide.t b/tests/test-help-hide.t --- a/tests/test-help-hide.t +++ b/tests/test-help-hide.t @@ -1,255 +1,259 @@ Test hiding some commands (which also happens to hide an entire category). $ hg --config help.hidden-command.clone=true \ > --config help.hidden-command.init=true help Mercurial Distributed SCM list of commands: Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) grep search revision history for a pattern in specified files Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory + shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state + unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories Miscellaneous: deprecated Deprecated Features internals Technical implementation topics scripting Using Mercurial from scripts and automation (use 'hg help -v' to show built-in aliases and global options) Test hiding some topics. $ hg --config help.hidden-topic.deprecated=true \ > --config help.hidden-topic.internals=true \ > --config help.hidden-topic.scripting=true help Mercurial Distributed SCM list of commands: Repository creation: clone make a copy of an existing repository init create a new repository in the given directory Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) grep search revision history for a pattern in specified files Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory + shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state + unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories (use 'hg help -v' to show built-in aliases and global options) diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -1,3858 +1,3875 @@ Short help: $ hg Mercurial Distributed SCM basic commands: add add the specified files on the next commit annotate show changeset information by line for each file clone make a copy of an existing repository commit commit the specified files or all outstanding changes diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit init create a new repository in the given directory log show revision history of entire repository or files merge merge another revision into working directory pull pull changes from the specified source push push changes to the specified destination remove remove the specified files on the next commit serve start stand-alone webserver status show changed files in the working directory summary summarize working directory state update update working directory (or switch revisions) (use 'hg help' for the full list of commands or 'hg -v' for details) $ hg -q add add the specified files on the next commit annotate show changeset information by line for each file clone make a copy of an existing repository commit commit the specified files or all outstanding changes diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit init create a new repository in the given directory log show revision history of entire repository or files merge merge another revision into working directory pull pull changes from the specified source push push changes to the specified destination remove remove the specified files on the next commit serve start stand-alone webserver status show changed files in the working directory summary summarize working directory state update update working directory (or switch revisions) Extra extensions will be printed in help output in a non-reliable order since the extension is unknown. #if no-extraextensions $ hg help Mercurial Distributed SCM list of commands: Repository creation: clone make a copy of an existing repository init create a new repository in the given directory Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) grep search revision history for a pattern in specified files Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory + shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state + unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories Miscellaneous: deprecated Deprecated Features internals Technical implementation topics scripting Using Mercurial from scripts and automation (use 'hg help -v' to show built-in aliases and global options) $ hg -q help Repository creation: clone make a copy of an existing repository init create a new repository in the given directory Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) grep search revision history for a pattern in specified files Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory + shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state + unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories Miscellaneous: deprecated Deprecated Features internals Technical implementation topics scripting Using Mercurial from scripts and automation Test extension help: $ hg help extensions --config extensions.rebase= --config extensions.children= Using Additional Features """"""""""""""""""""""""" Mercurial has the ability to add new features through the use of extensions. Extensions may add new commands, add options to existing commands, change the default behavior of commands, or implement hooks. To enable the "foo" extension, either shipped with Mercurial or in the Python search path, create an entry for it in your configuration file, like this: [extensions] foo = You may also specify the full path to an extension: [extensions] myfeature = ~/.hgext/myfeature.py See 'hg help config' for more information on configuration files. Extensions are not loaded by default for a variety of reasons: they can increase startup overhead; they may be meant for advanced usage only; they may provide potentially dangerous abilities (such as letting you destroy or modify history); they might not be ready for prime time; or they may alter some usual behaviors of stock Mercurial. It is thus up to the user to activate extensions as needed. To explicitly disable an extension enabled in a configuration file of broader scope, prepend its path with !: [extensions] # disabling extension bar residing in /path/to/extension/bar.py bar = !/path/to/extension/bar.py # ditto, but no path was supplied for extension baz baz = ! enabled extensions: children command to display child changesets (DEPRECATED) rebase command to move sets of revisions to a different ancestor disabled extensions: acl hooks for controlling repository access blackbox log repository events to a blackbox for debugging bugzilla hooks for integrating with the Bugzilla bug tracker censor erase file content at a given revision churn command to display statistics about repository history clonebundles advertise pre-generated bundles to seed clones closehead close arbitrary heads without checking them out first convert import revisions from foreign VCS repositories into Mercurial eol automatically manage newlines in repository files extdiff command to allow external programs to compare revisions factotum http authentication with factotum githelp try mapping git commands to Mercurial commands gpg commands to sign and verify changesets hgk browse the repository in a graphical way highlight syntax highlighting for hgweb (requires Pygments) histedit interactive history editing keyword expand keywords in tracked files largefiles track large binary files mq manage a stack of patches notify hooks for sending email push notifications patchbomb command to send changesets as (a series of) patch emails purge command to delete untracked files from the working directory relink recreates hardlinks between repository clones schemes extend schemes with shortcuts to repository swarms share share a common history between several working directories - shelve save and restore changes to the working directory strip strip changesets and their descendants from history transplant command to transplant changesets from another branch win32mbcs allow the use of MBCS paths with problematic encodings zeroconf discover and advertise repositories on the local network #endif Verify that deprecated extensions are included if --verbose: $ hg -v help extensions | grep children children command to display child changesets (DEPRECATED) Verify that extension keywords appear in help templates $ hg help --config extensions.transplant= templating|grep transplant > /dev/null Test short command list with verbose option $ hg -v help shortlist Mercurial Distributed SCM basic commands: add add the specified files on the next commit annotate, blame show changeset information by line for each file clone make a copy of an existing repository commit, ci commit the specified files or all outstanding changes diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit init create a new repository in the given directory log, history show revision history of entire repository or files merge merge another revision into working directory pull pull changes from the specified source push push changes to the specified destination remove, rm remove the specified files on the next commit serve start stand-alone webserver status, st show changed files in the working directory summary, sum summarize working directory state update, up, checkout, co update working directory (or switch revisions) global options ([+] can be repeated): -R --repository REPO repository root directory or name of overlay bundle file --cwd DIR change working directory -y --noninteractive do not prompt, automatically pick the first choice for all prompts -q --quiet suppress output -v --verbose enable additional output --color TYPE when to colorize (boolean, always, auto, never, or debug) --config CONFIG [+] set/override config option (use 'section.name=value') --debug enable debugging output --debugger start debugger --encoding ENCODE set the charset encoding (default: ascii) --encodingmode MODE set the charset encoding mode (default: strict) --traceback always print a traceback on exception --time time how long the command takes --profile print command execution profile --version output version information and exit -h --help display help and exit --hidden consider hidden changesets --pager TYPE when to paginate (boolean, always, auto, or never) (default: auto) (use 'hg help' for the full list of commands) $ hg add -h hg add [OPTION]... [FILE]... add the specified files on the next commit Schedule files to be version controlled and added to the repository. The files will be added to the repository at the next commit. To undo an add before that, see 'hg forget'. If no names are given, add all files to the repository (except files matching ".hgignore"). Returns 0 if all files are successfully added. options ([+] can be repeated): -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns -S --subrepos recurse into subrepositories -n --dry-run do not perform actions, just print output (some details hidden, use --verbose to show complete help) Verbose help for add $ hg add -hv hg add [OPTION]... [FILE]... add the specified files on the next commit Schedule files to be version controlled and added to the repository. The files will be added to the repository at the next commit. To undo an add before that, see 'hg forget'. If no names are given, add all files to the repository (except files matching ".hgignore"). Examples: - New (unknown) files are added automatically by 'hg add': $ ls foo.c $ hg status ? foo.c $ hg add adding foo.c $ hg status A foo.c - Specific files to be added can be specified: $ ls bar.c foo.c $ hg status ? bar.c ? foo.c $ hg add bar.c $ hg status A bar.c ? foo.c Returns 0 if all files are successfully added. options ([+] can be repeated): -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns -S --subrepos recurse into subrepositories -n --dry-run do not perform actions, just print output global options ([+] can be repeated): -R --repository REPO repository root directory or name of overlay bundle file --cwd DIR change working directory -y --noninteractive do not prompt, automatically pick the first choice for all prompts -q --quiet suppress output -v --verbose enable additional output --color TYPE when to colorize (boolean, always, auto, never, or debug) --config CONFIG [+] set/override config option (use 'section.name=value') --debug enable debugging output --debugger start debugger --encoding ENCODE set the charset encoding (default: ascii) --encodingmode MODE set the charset encoding mode (default: strict) --traceback always print a traceback on exception --time time how long the command takes --profile print command execution profile --version output version information and exit -h --help display help and exit --hidden consider hidden changesets --pager TYPE when to paginate (boolean, always, auto, or never) (default: auto) Test the textwidth config option $ hg root -h --config ui.textwidth=50 hg root print the root (top) of the current working directory Print the root directory of the current repository. Returns 0 on success. options: -T --template TEMPLATE display with template (some details hidden, use --verbose to show complete help) Test help option with version option $ hg add -h --version Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) Copyright (C) 2005-* Matt Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ hg add --skjdfks hg add: option --skjdfks not recognized hg add [OPTION]... [FILE]... add the specified files on the next commit options ([+] can be repeated): -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns -S --subrepos recurse into subrepositories -n --dry-run do not perform actions, just print output (use 'hg add -h' to show more help) [255] Test ambiguous command help $ hg help ad list of commands: add add the specified files on the next commit addremove add all new files, delete all missing files (use 'hg help -v ad' to show built-in aliases and global options) Test command without options $ hg help verify hg verify verify the integrity of the repository Verify the integrity of the current repository. This will perform an extensive check of the repository's integrity, validating the hashes and checksums of each entry in the changelog, manifest, and tracked files, as well as the integrity of their crosslinks and indices. Please see https://mercurial-scm.org/wiki/RepositoryCorruption for more information about recovery from corruption of the repository. Returns 0 on success, 1 if errors are encountered. options: (some details hidden, use --verbose to show complete help) $ hg help diff hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]... diff repository (or selected files) Show differences between revisions for the specified files. Differences between files are shown using the unified diff format. Note: 'hg diff' may generate unexpected results for merges, as it will default to comparing against the working directory's first parent changeset if no revisions are specified. When two revision arguments are given, then changes are shown between those revisions. If only one revision is specified then that revision is compared to the working directory, and, when no revisions are specified, the working directory files are compared to its first parent. Alternatively you can specify -c/--change with a revision to see the changes in that changeset relative to its first parent. Without the -a/--text option, diff will avoid generating diffs of files it detects as binary. With -a, diff will generate a diff anyway, probably with undesirable results. Use the -g/--git option to generate diffs in the git extended diff format. For more information, read 'hg help diffs'. Returns 0 on success. options ([+] can be repeated): -r --rev REV [+] revision -c --change REV change made by revision -a --text treat all files as text -g --git use git extended diff format --binary generate binary diffs in git mode (default) --nodates omit dates from diff headers --noprefix omit a/ and b/ prefixes from filenames -p --show-function show which function each change is in --reverse produce a diff that undoes the changes -w --ignore-all-space ignore white space when comparing lines -b --ignore-space-change ignore changes in the amount of white space -B --ignore-blank-lines ignore changes whose lines are all blank -Z --ignore-space-at-eol ignore changes in whitespace at EOL -U --unified NUM number of lines of context to show --stat output diffstat-style summary of changes --root DIR produce diffs relative to subdirectory -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns -S --subrepos recurse into subrepositories (some details hidden, use --verbose to show complete help) $ hg help status hg status [OPTION]... [FILE]... aliases: st show changed files in the working directory Show status of files in the repository. If names are given, only files that match are shown. Files that are clean or ignored or the source of a copy/move operation, are not listed unless -c/--clean, -i/--ignored, -C/--copies or -A/--all are given. Unless options described with "show only ..." are given, the options -mardu are used. Option -q/--quiet hides untracked (unknown and ignored) files unless explicitly requested with -u/--unknown or -i/--ignored. Note: 'hg status' may appear to disagree with diff if permissions have changed or a merge has occurred. The standard diff format does not report permission changes and diff only reports changes relative to one merge parent. If one revision is given, it is used as the base revision. If two revisions are given, the differences between them are shown. The --change option can also be used as a shortcut to list the changed files of a revision from its first parent. The codes used to show the status of files are: M = modified A = added R = removed C = clean ! = missing (deleted by non-hg command, but still tracked) ? = not tracked I = ignored = origin of the previous file (with --copies) Returns 0 on success. options ([+] can be repeated): -A --all show status of all files -m --modified show only modified files -a --added show only added files -r --removed show only removed files -d --deleted show only deleted (but tracked) files -c --clean show only files without changes -u --unknown show only unknown (not tracked) files -i --ignored show only ignored files -n --no-status hide status prefix -C --copies show source of copied files -0 --print0 end filenames with NUL, for use with xargs --rev REV [+] show difference from revision --change REV list the changed files of a revision -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns -S --subrepos recurse into subrepositories -T --template TEMPLATE display with template (some details hidden, use --verbose to show complete help) $ hg -q help status hg status [OPTION]... [FILE]... show changed files in the working directory $ hg help foo abort: no such help topic: foo (try 'hg help --keyword foo') [255] $ hg skjdfks hg: unknown command 'skjdfks' (use 'hg help' for a list of commands) [255] Typoed command gives suggestion $ hg puls hg: unknown command 'puls' (did you mean one of pull, push?) [255] Not enabled extension gets suggested $ hg rebase hg: unknown command 'rebase' 'rebase' is provided by the following extension: rebase command to move sets of revisions to a different ancestor (use 'hg help extensions' for information on enabling extensions) [255] Disabled extension gets suggested $ hg --config extensions.rebase=! rebase hg: unknown command 'rebase' 'rebase' is provided by the following extension: rebase command to move sets of revisions to a different ancestor (use 'hg help extensions' for information on enabling extensions) [255] Make sure that we don't run afoul of the help system thinking that this is a section and erroring out weirdly. $ hg .log hg: unknown command '.log' (did you mean log?) [255] $ hg log. hg: unknown command 'log.' (did you mean log?) [255] $ hg pu.lh hg: unknown command 'pu.lh' (did you mean one of pull, push?) [255] $ cat > helpext.py < import os > from mercurial import commands, fancyopts, registrar > > def func(arg): > return '%sfoo' % arg > class customopt(fancyopts.customopt): > def newstate(self, oldstate, newparam, abort): > return '%sbar' % oldstate > cmdtable = {} > command = registrar.command(cmdtable) > > @command(b'nohelp', > [(b'', b'longdesc', 3, b'x'*67), > (b'n', b'', None, b'normal desc'), > (b'', b'newline', b'', b'line1\nline2'), > (b'', b'default-off', False, b'enable X'), > (b'', b'default-on', True, b'enable Y'), > (b'', b'callableopt', func, b'adds foo'), > (b'', b'customopt', customopt(''), b'adds bar'), > (b'', b'customopt-withdefault', customopt('foo'), b'adds bar')], > b'hg nohelp', > norepo=True) > @command(b'debugoptADV', [(b'', b'aopt', None, b'option is (ADVANCED)')]) > @command(b'debugoptDEP', [(b'', b'dopt', None, b'option is (DEPRECATED)')]) > @command(b'debugoptEXP', [(b'', b'eopt', None, b'option is (EXPERIMENTAL)')]) > def nohelp(ui, *args, **kwargs): > pass > > @command(b'hashelp', [], b'hg hashelp', norepo=True) > def hashelp(ui, *args, **kwargs): > """Extension command's help""" > > def uisetup(ui): > ui.setconfig(b'alias', b'shellalias', b'!echo hi', b'helpext') > ui.setconfig(b'alias', b'hgalias', b'summary', b'helpext') > ui.setconfig(b'alias', b'hgalias:doc', b'My doc', b'helpext') > ui.setconfig(b'alias', b'hgalias:category', b'navigation', b'helpext') > ui.setconfig(b'alias', b'hgaliasnodoc', b'summary', b'helpext') > > EOF $ echo '[extensions]' >> $HGRCPATH $ echo "helpext = `pwd`/helpext.py" >> $HGRCPATH Test for aliases $ hg help | grep hgalias hgalias My doc $ hg help hgalias hg hgalias [--remote] alias for: hg summary My doc defined by: helpext options: --remote check for push and pull (some details hidden, use --verbose to show complete help) $ hg help hgaliasnodoc hg hgaliasnodoc [--remote] alias for: hg summary summarize working directory state This generates a brief summary of the working directory state, including parents, branch, commit status, phase and available updates. With the --remote option, this will check the default paths for incoming and outgoing changes. This can be time-consuming. Returns 0 on success. defined by: helpext options: --remote check for push and pull (some details hidden, use --verbose to show complete help) $ hg help shellalias hg shellalias shell alias for: echo hi (no help text available) defined by: helpext (some details hidden, use --verbose to show complete help) Test command with no help text $ hg help nohelp hg nohelp (no help text available) options: --longdesc VALUE xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxx (default: 3) -n -- normal desc --newline VALUE line1 line2 --default-off enable X --[no-]default-on enable Y (default: on) --callableopt VALUE adds foo --customopt VALUE adds bar --customopt-withdefault VALUE adds bar (default: foo) (some details hidden, use --verbose to show complete help) Test that default list of commands includes extension commands that have help, but not those that don't, except in verbose mode, when a keyword is passed, or when help about the extension is requested. #if no-extraextensions $ hg help | grep hashelp hashelp Extension command's help $ hg help | grep nohelp [1] $ hg help -v | grep nohelp nohelp (no help text available) $ hg help -k nohelp Commands: nohelp hg nohelp Extension Commands: nohelp (no help text available) $ hg help helpext helpext extension - no help text available list of commands: hashelp Extension command's help nohelp (no help text available) (use 'hg help -v helpext' to show built-in aliases and global options) #endif Test list of internal help commands $ hg help debug debug commands (internal and unsupported): debugancestor find the ancestor revision of two revisions in a given index debugapplystreamclonebundle apply a stream clone bundle file debugbuilddag builds a repo with a given DAG from scratch in the current empty repo debugbundle lists the contents of a bundle debugcapabilities lists the capabilities of a remote peer debugcheckstate validate the correctness of the current dirstate debugcolor show available color, effects or style debugcommands list all available commands and options debugcomplete returns the completion list associated with the given command debugcreatestreamclonebundle create a stream clone bundle file debugdag format the changelog or an index DAG as a concise textual description debugdata dump the contents of a data file revision debugdate parse and display a date debugdeltachain dump information about delta chains in a revlog debugdirstate show the contents of the current dirstate debugdiscovery runs the changeset discovery protocol in isolation debugdownload download a resource using Mercurial logic and config debugextensions show information about active extensions debugfileset parse and apply a fileset specification debugformat display format information about the current repository debugfsinfo show information detected about current filesystem debuggetbundle retrieves a bundle from a repo debugignore display the combined ignore pattern and information about ignored files debugindex dump index data for a storage primitive debugindexdot dump an index DAG as a graphviz dot file debugindexstats show stats related to the changelog index debuginstall test Mercurial installation debugknown test whether node ids are known to a repo debuglocks show or modify state of locks debugmanifestfulltextcache show, clear or amend the contents of the manifest fulltext cache debugmergestate print merge state debugnamecomplete complete "names" - tags, open branch names, bookmark names debugobsolete create arbitrary obsolete marker debugoptADV (no help text available) debugoptDEP (no help text available) debugoptEXP (no help text available) debugp1copies dump copy information compared to p1 debugp2copies dump copy information compared to p2 debugpathcomplete complete part or all of a tracked path debugpathcopies show copies between two revisions debugpeer establish a connection to a peer repository debugpickmergetool examine which merge tool is chosen for specified file debugpushkey access the pushkey key/value protocol debugpvec (no help text available) debugrebuilddirstate rebuild the dirstate as it would look like for the given revision debugrebuildfncache rebuild the fncache file debugrename dump rename information debugrevlog show data and statistics about a revlog debugrevlogindex dump the contents of a revlog index debugrevspec parse and apply a revision specification debugserve run a server with advanced settings debugsetparents manually set the parents of the current working directory debugssl test a secure connection to a server debugsub (no help text available) debugsuccessorssets show set of successors for revision debugtemplate parse and apply a template debuguigetpass show prompt to type password debuguiprompt show plain prompt debugupdatecaches warm all known caches in the repository debugupgraderepo upgrade a repository to use different features debugwalk show how files match on given patterns debugwhyunstable explain instabilities of a changeset debugwireargs (no help text available) debugwireproto send wire protocol commands to a server (use 'hg help -v debug' to show built-in aliases and global options) internals topic renders index of available sub-topics $ hg help internals Technical implementation topics """"""""""""""""""""""""""""""" To access a subtopic, use "hg help internals.{subtopic-name}" bundle2 Bundle2 bundles Bundles cbor CBOR censor Censor changegroups Changegroups config Config Registrar extensions Extension API mergestate Mergestate requirements Repository Requirements revlogs Revision Logs wireprotocol Wire Protocol wireprotocolrpc Wire Protocol RPC wireprotocolv2 Wire Protocol Version 2 sub-topics can be accessed $ hg help internals.changegroups Changegroups """""""""""" Changegroups are representations of repository revlog data, specifically the changelog data, root/flat manifest data, treemanifest data, and filelogs. There are 3 versions of changegroups: "1", "2", and "3". From a high- level, versions "1" and "2" are almost exactly the same, with the only difference being an additional item in the *delta header*. Version "3" adds support for storage flags in the *delta header* and optionally exchanging treemanifests (enabled by setting an option on the "changegroup" part in the bundle2). Changegroups when not exchanging treemanifests consist of 3 logical segments: +---------------------------------+ | | | | | changeset | manifest | filelogs | | | | | | | | | +---------------------------------+ When exchanging treemanifests, there are 4 logical segments: +-------------------------------------------------+ | | | | | | changeset | root | treemanifests | filelogs | | | manifest | | | | | | | | +-------------------------------------------------+ The principle building block of each segment is a *chunk*. A *chunk* is a framed piece of data: +---------------------------------------+ | | | | length | data | | (4 bytes) | ( bytes) | | | | +---------------------------------------+ All integers are big-endian signed integers. Each chunk starts with a 32-bit integer indicating the length of the entire chunk (including the length field itself). There is a special case chunk that has a value of 0 for the length ("0x00000000"). We call this an *empty chunk*. Delta Groups ============ A *delta group* expresses the content of a revlog as a series of deltas, or patches against previous revisions. Delta groups consist of 0 or more *chunks* followed by the *empty chunk* to signal the end of the delta group: +------------------------------------------------------------------------+ | | | | | | | chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 | | (4 bytes) | (various) | (4 bytes) | (various) | (4 bytes) | | | | | | | +------------------------------------------------------------------------+ Each *chunk*'s data consists of the following: +---------------------------------------+ | | | | delta header | delta data | | (various by version) | (various) | | | | +---------------------------------------+ The *delta data* is a series of *delta*s that describe a diff from an existing entry (either that the recipient already has, or previously specified in the bundle/changegroup). The *delta header* is different between versions "1", "2", and "3" of the changegroup format. Version 1 (headerlen=80): +------------------------------------------------------+ | | | | | | node | p1 node | p2 node | link node | | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | | | | | | +------------------------------------------------------+ Version 2 (headerlen=100): +------------------------------------------------------------------+ | | | | | | | node | p1 node | p2 node | base node | link node | | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | | | | | | | +------------------------------------------------------------------+ Version 3 (headerlen=102): +------------------------------------------------------------------------------+ | | | | | | | | node | p1 node | p2 node | base node | link node | flags | | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) | | | | | | | | +------------------------------------------------------------------------------+ The *delta data* consists of "chunklen - 4 - headerlen" bytes, which contain a series of *delta*s, densely packed (no separators). These deltas describe a diff from an existing entry (either that the recipient already has, or previously specified in the bundle/changegroup). The format is described more fully in "hg help internals.bdiff", but briefly: +---------------------------------------------------------------+ | | | | | | start offset | end offset | new length | content | | (4 bytes) | (4 bytes) | (4 bytes) | ( bytes) | | | | | | +---------------------------------------------------------------+ Please note that the length field in the delta data does *not* include itself. In version 1, the delta is always applied against the previous node from the changegroup or the first parent if this is the first entry in the changegroup. In version 2 and up, the delta base node is encoded in the entry in the changegroup. This allows the delta to be expressed against any parent, which can result in smaller deltas and more efficient encoding of data. The *flags* field holds bitwise flags affecting the processing of revision data. The following flags are defined: 32768 Censored revision. The revision's fulltext has been replaced by censor metadata. May only occur on file revisions. 16384 Ellipsis revision. Revision hash does not match data (likely due to rewritten parents). 8192 Externally stored. The revision fulltext contains "key:value" "\n" delimited metadata defining an object stored elsewhere. Used by the LFS extension. For historical reasons, the integer values are identical to revlog version 1 per-revision storage flags and correspond to bits being set in this 2-byte field. Bits were allocated starting from the most-significant bit, hence the reverse ordering and allocation of these flags. Changeset Segment ================= The *changeset segment* consists of a single *delta group* holding changelog data. The *empty chunk* at the end of the *delta group* denotes the boundary to the *manifest segment*. Manifest Segment ================ The *manifest segment* consists of a single *delta group* holding manifest data. If treemanifests are in use, it contains only the manifest for the root directory of the repository. Otherwise, it contains the entire manifest data. The *empty chunk* at the end of the *delta group* denotes the boundary to the next segment (either the *treemanifests segment* or the *filelogs segment*, depending on version and the request options). Treemanifests Segment --------------------- The *treemanifests segment* only exists in changegroup version "3", and only if the 'treemanifest' param is part of the bundle2 changegroup part (it is not possible to use changegroup version 3 outside of bundle2). Aside from the filenames in the *treemanifests segment* containing a trailing "/" character, it behaves identically to the *filelogs segment* (see below). The final sub-segment is followed by an *empty chunk* (logically, a sub-segment with filename size 0). This denotes the boundary to the *filelogs segment*. Filelogs Segment ================ The *filelogs segment* consists of multiple sub-segments, each corresponding to an individual file whose data is being described: +--------------------------------------------------+ | | | | | | | filelog0 | filelog1 | filelog2 | ... | 0x0 | | | | | | (4 bytes) | | | | | | | +--------------------------------------------------+ The final filelog sub-segment is followed by an *empty chunk* (logically, a sub-segment with filename size 0). This denotes the end of the segment and of the overall changegroup. Each filelog sub-segment consists of the following: +------------------------------------------------------+ | | | | | filename length | filename | delta group | | (4 bytes) | ( bytes) | (various) | | | | | +------------------------------------------------------+ That is, a *chunk* consisting of the filename (not terminated or padded) followed by N chunks constituting the *delta group* for this file. The *empty chunk* at the end of each *delta group* denotes the boundary to the next filelog sub-segment. non-existent subtopics print an error $ hg help internals.foo abort: no such help topic: internals.foo (try 'hg help --keyword foo') [255] test advanced, deprecated and experimental options are hidden in command help $ hg help debugoptADV hg debugoptADV (no help text available) options: (some details hidden, use --verbose to show complete help) $ hg help debugoptDEP hg debugoptDEP (no help text available) options: (some details hidden, use --verbose to show complete help) $ hg help debugoptEXP hg debugoptEXP (no help text available) options: (some details hidden, use --verbose to show complete help) test advanced, deprecated and experimental options are shown with -v $ hg help -v debugoptADV | grep aopt --aopt option is (ADVANCED) $ hg help -v debugoptDEP | grep dopt --dopt option is (DEPRECATED) $ hg help -v debugoptEXP | grep eopt --eopt option is (EXPERIMENTAL) #if gettext test deprecated option is hidden with translation with untranslated description (use many globy for not failing on changed transaction) $ LANGUAGE=sv hg help debugoptDEP hg debugoptDEP (*) (glob) options: (some details hidden, use --verbose to show complete help) #endif Test commands that collide with topics (issue4240) $ hg config -hq hg config [-u] [NAME]... show combined config settings from all hgrc files $ hg showconfig -hq hg config [-u] [NAME]... show combined config settings from all hgrc files Test a help topic $ hg help dates Date Formats """""""""""" Some commands allow the user to specify a date, e.g.: - backout, commit, import, tag: Specify the commit date. - log, revert, update: Select revision(s) by date. Many date formats are valid. Here are some examples: - "Wed Dec 6 13:18:29 2006" (local timezone assumed) - "Dec 6 13:18 -0600" (year assumed, time offset provided) - "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000) - "Dec 6" (midnight) - "13:18" (today assumed) - "3:39" (3:39AM assumed) - "3:39pm" (15:39) - "2006-12-06 13:18:29" (ISO 8601 format) - "2006-12-6 13:18" - "2006-12-6" - "12-6" - "12/6" - "12/6/6" (Dec 6 2006) - "today" (midnight) - "yesterday" (midnight) - "now" - right now Lastly, there is Mercurial's internal format: - "1165411109 0" (Wed Dec 6 13:18:29 2006 UTC) This is the internal representation format for dates. The first number is the number of seconds since the epoch (1970-01-01 00:00 UTC). The second is the offset of the local timezone, in seconds west of UTC (negative if the timezone is east of UTC). The log command also accepts date ranges: - "DATE" - on or after a given date/time - "DATE to DATE" - a date range, inclusive - "-DAYS" - within a given number of days of today Test repeated config section name $ hg help config.host "http_proxy.host" Host name and (optional) port of the proxy server, for example "myproxy:8000". "smtp.host" Host name of mail server, e.g. "mail.example.com". Test section name with dot $ hg help config.ui.username "ui.username" The committer of a changeset created when running "commit". Typically a person's name and email address, e.g. "Fred Widget ". Environment variables in the username are expanded. (default: "$EMAIL" or "username@hostname". If the username in hgrc is empty, e.g. if the system admin set "username =" in the system hgrc, it has to be specified manually or in a different hgrc file) $ hg help config.annotate.git abort: help section not found: config.annotate.git [255] $ hg help config.update.check "commands.update.check" Determines what level of checking 'hg update' will perform before moving to a destination revision. Valid values are "abort", "none", "linear", and "noconflict". "abort" always fails if the working directory has uncommitted changes. "none" performs no checking, and may result in a merge with uncommitted changes. "linear" allows any update as long as it follows a straight line in the revision history, and may trigger a merge with uncommitted changes. "noconflict" will allow any update which would not trigger a merge with uncommitted changes, if any are present. (default: "linear") $ hg help config.commands.update.check "commands.update.check" Determines what level of checking 'hg update' will perform before moving to a destination revision. Valid values are "abort", "none", "linear", and "noconflict". "abort" always fails if the working directory has uncommitted changes. "none" performs no checking, and may result in a merge with uncommitted changes. "linear" allows any update as long as it follows a straight line in the revision history, and may trigger a merge with uncommitted changes. "noconflict" will allow any update which would not trigger a merge with uncommitted changes, if any are present. (default: "linear") $ hg help config.ommands.update.check abort: help section not found: config.ommands.update.check [255] Unrelated trailing paragraphs shouldn't be included $ hg help config.extramsg | grep '^$' Test capitalized section name $ hg help scripting.HGPLAIN > /dev/null Help subsection: $ hg help config.charsets |grep "Email example:" > /dev/null [1] Show nested definitions ("profiling.type"[break]"ls"[break]"stat"[break]) $ hg help config.type | egrep '^$'|wc -l \s*3 (re) $ hg help config.profiling.type.ls "profiling.type.ls" Use Python's built-in instrumenting profiler. This profiler works on all platforms, but each line number it reports is the first line of a function. This restriction makes it difficult to identify the expensive parts of a non-trivial function. Separate sections from subsections $ hg help config.format | egrep '^ ("|-)|^\s*$' | uniq "format" -------- "usegeneraldelta" "dotencode" "usefncache" "usestore" "sparse-revlog" "revlog-compression" "bookmarks-in-store" "profiling" ----------- "format" "progress" ---------- "format" Last item in help config.*: $ hg help config.`hg help config|grep '^ "'| \ > tail -1|sed 's![ "]*!!g'`| \ > grep 'hg help -c config' > /dev/null [1] note to use help -c for general hg help config: $ hg help config |grep 'hg help -c config' > /dev/null Test templating help $ hg help templating | egrep '(desc|diffstat|firstline|nonempty) ' desc String. The text of the changeset description. diffstat String. Statistics of changes with the following format: firstline Any text. Returns the first line of text. nonempty Any text. Returns '(none)' if the string is empty. Test deprecated items $ hg help -v templating | grep currentbookmark currentbookmark $ hg help templating | (grep currentbookmark || true) Test help hooks $ cat > helphook1.py < from mercurial import help > > def rewrite(ui, topic, doc): > return doc + b'\nhelphook1\n' > > def extsetup(ui): > help.addtopichook(b'revisions', rewrite) > EOF $ cat > helphook2.py < from mercurial import help > > def rewrite(ui, topic, doc): > return doc + b'\nhelphook2\n' > > def extsetup(ui): > help.addtopichook(b'revisions', rewrite) > EOF $ echo '[extensions]' >> $HGRCPATH $ echo "helphook1 = `pwd`/helphook1.py" >> $HGRCPATH $ echo "helphook2 = `pwd`/helphook2.py" >> $HGRCPATH $ hg help revsets | grep helphook helphook1 helphook2 help -c should only show debug --debug $ hg help -c --debug|egrep debug|wc -l|egrep '^\s*0\s*$' [1] help -c should only show deprecated for -v $ hg help -c -v|egrep DEPRECATED|wc -l|egrep '^\s*0\s*$' [1] Test -s / --system $ hg help config.files -s windows |grep 'etc/mercurial' | \ > wc -l | sed -e 's/ //g' 0 $ hg help config.files --system unix | grep 'USER' | \ > wc -l | sed -e 's/ //g' 0 Test -e / -c / -k combinations $ hg help -c|egrep '^[A-Z].*:|^ debug' Commands: $ hg help -e|egrep '^[A-Z].*:|^ debug' Extensions: $ hg help -k|egrep '^[A-Z].*:|^ debug' Topics: Commands: Extensions: Extension Commands: $ hg help -c schemes abort: no such help topic: schemes (try 'hg help --keyword schemes') [255] $ hg help -e schemes |head -1 schemes extension - extend schemes with shortcuts to repository swarms $ hg help -c -k dates |egrep '^(Topics|Extensions|Commands):' Commands: $ hg help -e -k a |egrep '^(Topics|Extensions|Commands):' Extensions: $ hg help -e -c -k date |egrep '^(Topics|Extensions|Commands):' Extensions: Commands: $ hg help -c commit > /dev/null $ hg help -e -c commit > /dev/null $ hg help -e commit abort: no such help topic: commit (try 'hg help --keyword commit') [255] Test keyword search help $ cat > prefixedname.py < '''matched against word "clone" > ''' > EOF $ echo '[extensions]' >> $HGRCPATH $ echo "dot.dot.prefixedname = `pwd`/prefixedname.py" >> $HGRCPATH $ hg help -k clone Topics: config Configuration Files extensions Using Additional Features glossary Glossary phases Working with Phases subrepos Subrepositories urls URL Paths Commands: bookmarks create a new bookmark or list existing bookmarks clone make a copy of an existing repository paths show aliases for remote repositories pull pull changes from the specified source update update working directory (or switch revisions) Extensions: clonebundles advertise pre-generated bundles to seed clones narrow create clones which fetch history data for subset of files (EXPERIMENTAL) prefixedname matched against word "clone" relink recreates hardlinks between repository clones Extension Commands: qclone clone main and patch repository at same time Test unfound topic $ hg help nonexistingtopicthatwillneverexisteverever abort: no such help topic: nonexistingtopicthatwillneverexisteverever (try 'hg help --keyword nonexistingtopicthatwillneverexisteverever') [255] Test unfound keyword $ hg help --keyword nonexistingwordthatwillneverexisteverever abort: no matches (try 'hg help' for a list of topics) [255] Test omit indicating for help $ cat > addverboseitems.py < r'''extension to test omit indicating. > > This paragraph is never omitted (for extension) > > .. container:: verbose > > This paragraph is omitted, > if :hg:\`help\` is invoked without \`\`-v\`\` (for extension) > > This paragraph is never omitted, too (for extension) > ''' > from __future__ import absolute_import > from mercurial import commands, help > testtopic = br"""This paragraph is never omitted (for topic). > > .. container:: verbose > > This paragraph is omitted, > if :hg:\`help\` is invoked without \`\`-v\`\` (for topic) > > This paragraph is never omitted, too (for topic) > """ > def extsetup(ui): > help.helptable.append(([b"topic-containing-verbose"], > b"This is the topic to test omit indicating.", > lambda ui: testtopic)) > EOF $ echo '[extensions]' >> $HGRCPATH $ echo "addverboseitems = `pwd`/addverboseitems.py" >> $HGRCPATH $ hg help addverboseitems addverboseitems extension - extension to test omit indicating. This paragraph is never omitted (for extension) This paragraph is never omitted, too (for extension) (some details hidden, use --verbose to show complete help) no commands defined $ hg help -v addverboseitems addverboseitems extension - extension to test omit indicating. This paragraph is never omitted (for extension) This paragraph is omitted, if 'hg help' is invoked without "-v" (for extension) This paragraph is never omitted, too (for extension) no commands defined $ hg help topic-containing-verbose This is the topic to test omit indicating. """""""""""""""""""""""""""""""""""""""""" This paragraph is never omitted (for topic). This paragraph is never omitted, too (for topic) (some details hidden, use --verbose to show complete help) $ hg help -v topic-containing-verbose This is the topic to test omit indicating. """""""""""""""""""""""""""""""""""""""""" This paragraph is never omitted (for topic). This paragraph is omitted, if 'hg help' is invoked without "-v" (for topic) This paragraph is never omitted, too (for topic) Test section lookup $ hg help revset.merge "merge()" Changeset is a merge changeset. $ hg help glossary.dag DAG The repository of changesets of a distributed version control system (DVCS) can be described as a directed acyclic graph (DAG), consisting of nodes and edges, where nodes correspond to changesets and edges imply a parent -> child relation. This graph can be visualized by graphical tools such as 'hg log --graph'. In Mercurial, the DAG is limited by the requirement for children to have at most two parents. $ hg help hgrc.paths "paths" ------- Assigns symbolic names and behavior to repositories. Options are symbolic names defining the URL or directory that is the location of the repository. Example: [paths] my_server = https://example.com/my_repo local_path = /home/me/repo These symbolic names can be used from the command line. To pull from "my_server": 'hg pull my_server'. To push to "local_path": 'hg push local_path'. Options containing colons (":") denote sub-options that can influence behavior for that specific path. Example: [paths] my_server = https://example.com/my_path my_server:pushurl = ssh://example.com/my_path The following sub-options can be defined: "pushurl" The URL to use for push operations. If not defined, the location defined by the path's main entry is used. "pushrev" A revset defining which revisions to push by default. When 'hg push' is executed without a "-r" argument, the revset defined by this sub-option is evaluated to determine what to push. For example, a value of "." will push the working directory's revision by default. Revsets specifying bookmarks will not result in the bookmark being pushed. The following special named paths exist: "default" The URL or directory to use when no source or remote is specified. 'hg clone' will automatically define this path to the location the repository was cloned from. "default-push" (deprecated) The URL or directory for the default 'hg push' location. "default:pushurl" should be used instead. $ hg help glossary.mcguffin abort: help section not found: glossary.mcguffin [255] $ hg help glossary.mc.guffin abort: help section not found: glossary.mc.guffin [255] $ hg help template.files files List of strings. All files modified, added, or removed by this changeset. files(pattern) All files of the current changeset matching the pattern. See 'hg help patterns'. Test section lookup by translated message str.lower() instead of encoding.lower(str) on translated message might make message meaningless, because some encoding uses 0x41(A) - 0x5a(Z) as the second or later byte of multi-byte character. For example, "\x8bL\x98^" (translation of "record" in ja_JP.cp932) contains 0x4c (L). str.lower() replaces 0x4c(L) by 0x6c(l) and this replacement makes message meaningless. This tests that section lookup by translated string isn't broken by such str.lower(). $ "$PYTHON" < def escape(s): > return b''.join(b'\\u%x' % ord(uc) for uc in s.decode('cp932')) > # translation of "record" in ja_JP.cp932 > upper = b"\x8bL\x98^" > # str.lower()-ed section name should be treated as different one > lower = b"\x8bl\x98^" > with open('ambiguous.py', 'wb') as fp: > fp.write(b"""# ambiguous section names in ja_JP.cp932 > u'''summary of extension > > %s > ---- > > Upper name should show only this message > > %s > ---- > > Lower name should show only this message > > subsequent section > ------------------ > > This should be hidden at 'hg help ambiguous' with section name. > ''' > """ % (escape(upper), escape(lower))) > EOF $ cat >> $HGRCPATH < [extensions] > ambiguous = ./ambiguous.py > EOF $ "$PYTHON" < from mercurial import pycompat > upper = b"\x8bL\x98^" > pycompat.stdout.write(b"hg --encoding cp932 help -e ambiguous.%s\n" % upper) > EOF \x8bL\x98^ (esc) ---- Upper name should show only this message $ "$PYTHON" < from mercurial import pycompat > lower = b"\x8bl\x98^" > pycompat.stdout.write(b"hg --encoding cp932 help -e ambiguous.%s\n" % lower) > EOF \x8bl\x98^ (esc) ---- Lower name should show only this message $ cat >> $HGRCPATH < [extensions] > ambiguous = ! > EOF Show help content of disabled extensions $ cat >> $HGRCPATH < [extensions] > ambiguous = !./ambiguous.py > EOF $ hg help -e ambiguous ambiguous extension - (no help text available) (use 'hg help extensions' for information on enabling extensions) Test dynamic list of merge tools only shows up once $ hg help merge-tools Merge Tools """"""""""" To merge files Mercurial uses merge tools. A merge tool combines two different versions of a file into a merged file. Merge tools are given the two files and the greatest common ancestor of the two file versions, so they can determine the changes made on both branches. Merge tools are used both for 'hg resolve', 'hg merge', 'hg update', 'hg backout' and in several extensions. Usually, the merge tool tries to automatically reconcile the files by combining all non-overlapping changes that occurred separately in the two different evolutions of the same initial base file. Furthermore, some interactive merge programs make it easier to manually resolve conflicting merges, either in a graphical way, or by inserting some conflict markers. Mercurial does not include any interactive merge programs but relies on external tools for that. Available merge tools ===================== External merge tools and their properties are configured in the merge- tools configuration section - see hgrc(5) - but they can often just be named by their executable. A merge tool is generally usable if its executable can be found on the system and if it can handle the merge. The executable is found if it is an absolute or relative executable path or the name of an application in the executable search path. The tool is assumed to be able to handle the merge if it can handle symlinks if the file is a symlink, if it can handle binary files if the file is binary, and if a GUI is available if the tool requires a GUI. There are some internal merge tools which can be used. The internal merge tools are: ":dump" Creates three versions of the files to merge, containing the contents of local, other and base. These files can then be used to perform a merge manually. If the file to be merged is named "a.txt", these files will accordingly be named "a.txt.local", "a.txt.other" and "a.txt.base" and they will be placed in the same directory as "a.txt". This implies premerge. Therefore, files aren't dumped, if premerge runs successfully. Use :forcedump to forcibly write files out. (actual capabilities: binary, symlink) ":fail" Rather than attempting to merge files that were modified on both branches, it marks them as unresolved. The resolve command must be used to resolve these conflicts. (actual capabilities: binary, symlink) ":forcedump" Creates three versions of the files as same as :dump, but omits premerge. (actual capabilities: binary, symlink) ":local" Uses the local 'p1()' version of files as the merged version. (actual capabilities: binary, symlink) ":merge" Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in the partially merged file. Markers will have two sections, one for each side of merge. ":merge-local" Like :merge, but resolve all conflicts non-interactively in favor of the local 'p1()' changes. ":merge-other" Like :merge, but resolve all conflicts non-interactively in favor of the other 'p2()' changes. ":merge3" Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in the partially merged file. Marker will have three sections, one from each side of the merge and one for the base content. ":other" Uses the other 'p2()' version of files as the merged version. (actual capabilities: binary, symlink) ":prompt" Asks the user which of the local 'p1()' or the other 'p2()' version to keep as the merged version. (actual capabilities: binary, symlink) ":tagmerge" Uses the internal tag merge algorithm (experimental). ":union" Uses the internal non-interactive simple merge algorithm for merging files. It will use both left and right sides for conflict regions. No markers are inserted. Internal tools are always available and do not require a GUI but will by default not handle symlinks or binary files. See next section for detail about "actual capabilities" described above. Choosing a merge tool ===================== Mercurial uses these rules when deciding which merge tool to use: 1. If a tool has been specified with the --tool option to merge or resolve, it is used. If it is the name of a tool in the merge-tools configuration, its configuration is used. Otherwise the specified tool must be executable by the shell. 2. If the "HGMERGE" environment variable is present, its value is used and must be executable by the shell. 3. If the filename of the file to be merged matches any of the patterns in the merge-patterns configuration section, the first usable merge tool corresponding to a matching pattern is used. 4. If ui.merge is set it will be considered next. If the value is not the name of a configured tool, the specified value is used and must be executable by the shell. Otherwise the named tool is used if it is usable. 5. If any usable merge tools are present in the merge-tools configuration section, the one with the highest priority is used. 6. If a program named "hgmerge" can be found on the system, it is used - but it will by default not be used for symlinks and binary files. 7. If the file to be merged is not binary and is not a symlink, then internal ":merge" is used. 8. Otherwise, ":prompt" is used. For historical reason, Mercurial treats merge tools as below while examining rules above. step specified via binary symlink ---------------------------------- 1. --tool o/o o/o 2. HGMERGE o/o o/o 3. merge-patterns o/o(*) x/?(*) 4. ui.merge x/?(*) x/?(*) Each capability column indicates Mercurial behavior for internal/external merge tools at examining each rule. - "o": "assume that a tool has capability" - "x": "assume that a tool does not have capability" - "?": "check actual capability of a tool" If "merge.strict-capability-check" configuration is true, Mercurial checks capabilities of merge tools strictly in (*) cases above (= each capability column becomes "?/?"). It is false by default for backward compatibility. Note: After selecting a merge program, Mercurial will by default attempt to merge the files using a simple merge algorithm first. Only if it doesn't succeed because of conflicting changes will Mercurial actually execute the merge program. Whether to use the simple merge algorithm first can be controlled by the premerge setting of the merge tool. Premerge is enabled by default unless the file is binary or a symlink. See the merge-tools and ui sections of hgrc(5) for details on the configuration of merge tools. Compression engines listed in `hg help bundlespec` $ hg help bundlespec | grep gzip "v1" bundles can only use the "gzip", "bzip2", and "none" compression An algorithm that produces smaller bundles than "gzip". This engine will likely produce smaller bundles than "gzip" but will be "gzip" better compression than "gzip". It also frequently yields better (?) Test usage of section marks in help documents $ cd "$TESTDIR"/../doc $ "$PYTHON" check-seclevel.py $ cd $TESTTMP #if serve Test the help pages in hgweb. Dish up an empty repo; serve it cold. $ hg init "$TESTTMP/test" $ hg serve -R "$TESTTMP/test" -n test -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid >> $DAEMON_PIDS $ get-with-headers.py $LOCALIP:$HGPORT "help" 200 Script output follows Help: Index
+ +

Topics

bundlespec Bundle File Formats
color Colorizing Outputs
config Configuration Files
dates Date Formats
deprecated Deprecated Features
diffs Diff Formats
environment Environment Variables
extensions Using Additional Features
filesets Specifying File Sets
flags Command-line flags
glossary Glossary
hgignore Syntax for Mercurial Ignore Files
hgweb Configuring hgweb
internals Technical implementation topics
merge-tools Merge Tools
pager Pager Support
patterns File Name Patterns
phases Working with Phases
revisions Specifying Revisions
scripting Using Mercurial from scripts and automation
subrepos Subrepositories
templating Template Usage
urls URL Paths
topic-containing-verbose This is the topic to test omit indicating.

Main Commands

add add the specified files on the next commit
annotate show changeset information by line for each file
clone make a copy of an existing repository
commit commit the specified files or all outstanding changes
diff diff repository (or selected files)
export dump the header and diffs for one or more changesets
forget forget the specified files on the next commit
init create a new repository in the given directory
log show revision history of entire repository or files
merge merge another revision into working directory
pull pull changes from the specified source
push push changes to the specified destination
remove remove the specified files on the next commit
serve start stand-alone webserver
status show changed files in the working directory
summary summarize working directory state
update update working directory (or switch revisions)

Other Commands

addremove add all new files, delete all missing files
archive create an unversioned archive of a repository revision
backout reverse effect of earlier changeset
bisect subdivision search of changesets
bookmarks create a new bookmark or list existing bookmarks
branch set or show the current branch name
branches list repository named branches
bundle create a bundle file
cat output the current or given revision of files
config show combined config settings from all hgrc files
copy mark files as copied for the next commit
files list tracked files
graft copy changes from other branches onto the current branch
grep search revision history for a pattern in specified files
hashelp Extension command's help
heads show branch heads
help show help for a given topic or a help overview
hgalias My doc
hgaliasnodoc summarize working directory state
identify identify the working directory or specified revision
import import an ordered set of patches
incoming show new changesets found in source
manifest output the current or given revision of the project manifest
nohelp (no help text available)
outgoing show changesets not found in the destination
paths show aliases for remote repositories
phase set or show the current phase name
recover roll back an interrupted transaction
rename rename files; equivalent of copy + remove
resolve redo merges or set/view the merge status of files
revert restore files to their checkout state
root print the root (top) of the current working directory
shellalias (no help text available)
+ + shelve + + + save and set aside changes from the working directory +
tag add one or more tags for the current or given revision
tags list repository tags
unbundle apply one or more bundle files
+ + unshelve + + + restore a shelved change to the working directory +
verify verify the integrity of the repository
version output version and copyright information
$ get-with-headers.py $LOCALIP:$HGPORT "help/add" 200 Script output follows Help: add

Help: add

hg add [OPTION]... [FILE]...

add the specified files on the next commit

Schedule files to be version controlled and added to the repository.

The files will be added to the repository at the next commit. To undo an add before that, see 'hg forget'.

If no names are given, add all files to the repository (except files matching ".hgignore").

Examples:

  • New (unknown) files are added automatically by 'hg add':
       \$ ls (re)
       foo.c
       \$ hg status (re)
       ? foo.c
       \$ hg add (re)
       adding foo.c
       \$ hg status (re)
       A foo.c
       
  • Specific files to be added can be specified:
       \$ ls (re)
       bar.c  foo.c
       \$ hg status (re)
       ? bar.c
       ? foo.c
       \$ hg add bar.c (re)
       \$ hg status (re)
       A bar.c
       ? foo.c
       

Returns 0 if all files are successfully added.

options ([+] can be repeated):

-I --include PATTERN [+] include names matching the given patterns
-X --exclude PATTERN [+] exclude names matching the given patterns
-S --subrepos recurse into subrepositories
-n --dry-run do not perform actions, just print output

global options ([+] can be repeated):

-R --repository REPO repository root directory or name of overlay bundle file
--cwd DIR change working directory
-y --noninteractive do not prompt, automatically pick the first choice for all prompts
-q --quiet suppress output
-v --verbose enable additional output
--color TYPE when to colorize (boolean, always, auto, never, or debug)
--config CONFIG [+] set/override config option (use 'section.name=value')
--debug enable debugging output
--debugger start debugger
--encoding ENCODE set the charset encoding (default: ascii)
--encodingmode MODE set the charset encoding mode (default: strict)
--traceback always print a traceback on exception
--time time how long the command takes
--profile print command execution profile
--version output version information and exit
-h --help display help and exit
--hidden consider hidden changesets
--pager TYPE when to paginate (boolean, always, auto, or never) (default: auto)
$ get-with-headers.py $LOCALIP:$HGPORT "help/remove" 200 Script output follows Help: remove

Help: remove

hg remove [OPTION]... FILE...

aliases: rm

remove the specified files on the next commit

Schedule the indicated files for removal from the current branch.

This command schedules the files to be removed at the next commit. To undo a remove before that, see 'hg revert'. To undo added files, see 'hg forget'.

-A/--after can be used to remove only files that have already been deleted, -f/--force can be used to force deletion, and -Af can be used to remove files from the next revision without deleting them from the working directory.

The following table details the behavior of remove for different file states (columns) and option combinations (rows). The file states are Added [A], Clean [C], Modified [M] and Missing [!] (as reported by 'hg status'). The actions are Warn, Remove (from branch) and Delete (from disk):

opt/state A C M !
none W RD W R
-f R RD RD R
-A W W W R
-Af R R R R

Note:

'hg remove' never deletes files in Added [A] state from the working directory, not even if "--force" is specified.

Returns 0 on success, 1 if any warnings encountered.

options ([+] can be repeated):

-A --after record delete for missing files
-f --force forget added files, delete modified files
-S --subrepos recurse into subrepositories
-I --include PATTERN [+] include names matching the given patterns
-X --exclude PATTERN [+] exclude names matching the given patterns
-n --dry-run do not perform actions, just print output

global options ([+] can be repeated):

-R --repository REPO repository root directory or name of overlay bundle file
--cwd DIR change working directory
-y --noninteractive do not prompt, automatically pick the first choice for all prompts
-q --quiet suppress output
-v --verbose enable additional output
--color TYPE when to colorize (boolean, always, auto, never, or debug)
--config CONFIG [+] set/override config option (use 'section.name=value')
--debug enable debugging output
--debugger start debugger
--encoding ENCODE set the charset encoding (default: ascii)
--encodingmode MODE set the charset encoding mode (default: strict)
--traceback always print a traceback on exception
--time time how long the command takes
--profile print command execution profile
--version output version information and exit
-h --help display help and exit
--hidden consider hidden changesets
--pager TYPE when to paginate (boolean, always, auto, or never) (default: auto)
$ get-with-headers.py $LOCALIP:$HGPORT "help/dates" 200 Script output follows Help: dates

Help: dates

Date Formats

Some commands allow the user to specify a date, e.g.:

  • backout, commit, import, tag: Specify the commit date.
  • log, revert, update: Select revision(s) by date.

Many date formats are valid. Here are some examples:

  • "Wed Dec 6 13:18:29 2006" (local timezone assumed)
  • "Dec 6 13:18 -0600" (year assumed, time offset provided)
  • "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
  • "Dec 6" (midnight)
  • "13:18" (today assumed)
  • "3:39" (3:39AM assumed)
  • "3:39pm" (15:39)
  • "2006-12-06 13:18:29" (ISO 8601 format)
  • "2006-12-6 13:18"
  • "2006-12-6"
  • "12-6"
  • "12/6"
  • "12/6/6" (Dec 6 2006)
  • "today" (midnight)
  • "yesterday" (midnight)
  • "now" - right now

Lastly, there is Mercurial's internal format:

  • "1165411109 0" (Wed Dec 6 13:18:29 2006 UTC)

This is the internal representation format for dates. The first number is the number of seconds since the epoch (1970-01-01 00:00 UTC). The second is the offset of the local timezone, in seconds west of UTC (negative if the timezone is east of UTC).

The log command also accepts date ranges:

  • "<DATE" - at or before a given date/time
  • ">DATE" - on or after a given date/time
  • "DATE to DATE" - a date range, inclusive
  • "-DAYS" - within a given number of days of today
$ get-with-headers.py $LOCALIP:$HGPORT "help/pager" 200 Script output follows Help: pager

Help: pager

Pager Support

Some Mercurial commands can produce a lot of output, and Mercurial will attempt to use a pager to make those commands more pleasant.

To set the pager that should be used, set the application variable:

   [pager]
   pager = less -FRX
   

If no pager is set in the user or repository configuration, Mercurial uses the environment variable $PAGER. If $PAGER is not set, pager.pager from the default or system configuration is used. If none of these are set, a default pager will be used, typically 'less' on Unix and 'more' on Windows.

You can disable the pager for certain commands by adding them to the pager.ignore list:

   [pager]
   ignore = version, help, update
   

To ignore global commands like 'hg version' or 'hg help', you have to specify them in your user configuration file.

To control whether the pager is used at all for an individual command, you can use --pager=<value>:

  • use as needed: 'auto'.
  • require the pager: 'yes' or 'on'.
  • suppress the pager: 'no' or 'off' (any unrecognized value will also work).

To globally turn off all attempts to use a pager, set:

   [ui]
   paginate = never
   

which will prevent the pager from running.

Sub-topic indexes rendered properly $ get-with-headers.py $LOCALIP:$HGPORT "help/internals" 200 Script output follows Help: internals

Topics

bundle2 Bundle2
bundles Bundles
cbor CBOR
censor Censor
changegroups Changegroups
config Config Registrar
extensions Extension API
mergestate Mergestate
requirements Repository Requirements
revlogs Revision Logs
wireprotocol Wire Protocol
wireprotocolrpc Wire Protocol RPC
wireprotocolv2 Wire Protocol Version 2
Sub-topic topics rendered properly $ get-with-headers.py $LOCALIP:$HGPORT "help/internals.changegroups" 200 Script output follows Help: internals.changegroups

Help: internals.changegroups

Changegroups

Changegroups are representations of repository revlog data, specifically the changelog data, root/flat manifest data, treemanifest data, and filelogs.

There are 3 versions of changegroups: "1", "2", and "3". From a high-level, versions "1" and "2" are almost exactly the same, with the only difference being an additional item in the *delta header*. Version "3" adds support for storage flags in the *delta header* and optionally exchanging treemanifests (enabled by setting an option on the "changegroup" part in the bundle2).

Changegroups when not exchanging treemanifests consist of 3 logical segments:

   +---------------------------------+
   |           |          |          |
   | changeset | manifest | filelogs |
   |           |          |          |
   |           |          |          |
   +---------------------------------+
   

When exchanging treemanifests, there are 4 logical segments:

   +-------------------------------------------------+
   |           |          |               |          |
   | changeset |   root   | treemanifests | filelogs |
   |           | manifest |               |          |
   |           |          |               |          |
   +-------------------------------------------------+
   

The principle building block of each segment is a *chunk*. A *chunk* is a framed piece of data:

   +---------------------------------------+
   |           |                           |
   |  length   |           data            |
   | (4 bytes) |   (<length - 4> bytes)    |
   |           |                           |
   +---------------------------------------+
   

All integers are big-endian signed integers. Each chunk starts with a 32-bit integer indicating the length of the entire chunk (including the length field itself).

There is a special case chunk that has a value of 0 for the length ("0x00000000"). We call this an *empty chunk*.

Delta Groups

A *delta group* expresses the content of a revlog as a series of deltas, or patches against previous revisions.

Delta groups consist of 0 or more *chunks* followed by the *empty chunk* to signal the end of the delta group:

   +------------------------------------------------------------------------+
   |                |             |               |             |           |
   | chunk0 length  | chunk0 data | chunk1 length | chunk1 data |    0x0    |
   |   (4 bytes)    |  (various)  |   (4 bytes)   |  (various)  | (4 bytes) |
   |                |             |               |             |           |
   +------------------------------------------------------------------------+
   

Each *chunk*'s data consists of the following:

   +---------------------------------------+
   |                        |              |
   |     delta header       |  delta data  |
   |  (various by version)  |  (various)   |
   |                        |              |
   +---------------------------------------+
   

The *delta data* is a series of *delta*s that describe a diff from an existing entry (either that the recipient already has, or previously specified in the bundle/changegroup).

The *delta header* is different between versions "1", "2", and "3" of the changegroup format.

Version 1 (headerlen=80):

   +------------------------------------------------------+
   |            |             |             |             |
   |    node    |   p1 node   |   p2 node   |  link node  |
   | (20 bytes) |  (20 bytes) |  (20 bytes) |  (20 bytes) |
   |            |             |             |             |
   +------------------------------------------------------+
   

Version 2 (headerlen=100):

   +------------------------------------------------------------------+
   |            |             |             |            |            |
   |    node    |   p1 node   |   p2 node   | base node  | link node  |
   | (20 bytes) |  (20 bytes) |  (20 bytes) | (20 bytes) | (20 bytes) |
   |            |             |             |            |            |
   +------------------------------------------------------------------+
   

Version 3 (headerlen=102):

   +------------------------------------------------------------------------------+
   |            |             |             |            |            |           |
   |    node    |   p1 node   |   p2 node   | base node  | link node  |   flags   |
   | (20 bytes) |  (20 bytes) |  (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) |
   |            |             |             |            |            |           |
   +------------------------------------------------------------------------------+
   

The *delta data* consists of "chunklen - 4 - headerlen" bytes, which contain a series of *delta*s, densely packed (no separators). These deltas describe a diff from an existing entry (either that the recipient already has, or previously specified in the bundle/changegroup). The format is described more fully in "hg help internals.bdiff", but briefly:

   +---------------------------------------------------------------+
   |              |            |            |                      |
   | start offset | end offset | new length |        content       |
   |  (4 bytes)   |  (4 bytes) |  (4 bytes) | (<new length> bytes) |
   |              |            |            |                      |
   +---------------------------------------------------------------+
   

Please note that the length field in the delta data does *not* include itself.

In version 1, the delta is always applied against the previous node from the changegroup or the first parent if this is the first entry in the changegroup.

In version 2 and up, the delta base node is encoded in the entry in the changegroup. This allows the delta to be expressed against any parent, which can result in smaller deltas and more efficient encoding of data.

The *flags* field holds bitwise flags affecting the processing of revision data. The following flags are defined:

32768
Censored revision. The revision's fulltext has been replaced by censor metadata. May only occur on file revisions.
16384
Ellipsis revision. Revision hash does not match data (likely due to rewritten parents).
8192
Externally stored. The revision fulltext contains "key:value" "\n" delimited metadata defining an object stored elsewhere. Used by the LFS extension.

For historical reasons, the integer values are identical to revlog version 1 per-revision storage flags and correspond to bits being set in this 2-byte field. Bits were allocated starting from the most-significant bit, hence the reverse ordering and allocation of these flags.

Changeset Segment

The *changeset segment* consists of a single *delta group* holding changelog data. The *empty chunk* at the end of the *delta group* denotes the boundary to the *manifest segment*.

Manifest Segment

The *manifest segment* consists of a single *delta group* holding manifest data. If treemanifests are in use, it contains only the manifest for the root directory of the repository. Otherwise, it contains the entire manifest data. The *empty chunk* at the end of the *delta group* denotes the boundary to the next segment (either the *treemanifests segment* or the *filelogs segment*, depending on version and the request options).

Treemanifests Segment

The *treemanifests segment* only exists in changegroup version "3", and only if the 'treemanifest' param is part of the bundle2 changegroup part (it is not possible to use changegroup version 3 outside of bundle2). Aside from the filenames in the *treemanifests segment* containing a trailing "/" character, it behaves identically to the *filelogs segment* (see below). The final sub-segment is followed by an *empty chunk* (logically, a sub-segment with filename size 0). This denotes the boundary to the *filelogs segment*.

Filelogs Segment

The *filelogs segment* consists of multiple sub-segments, each corresponding to an individual file whose data is being described:

   +--------------------------------------------------+
   |          |          |          |     |           |
   | filelog0 | filelog1 | filelog2 | ... |    0x0    |
   |          |          |          |     | (4 bytes) |
   |          |          |          |     |           |
   +--------------------------------------------------+
   

The final filelog sub-segment is followed by an *empty chunk* (logically, a sub-segment with filename size 0). This denotes the end of the segment and of the overall changegroup.

Each filelog sub-segment consists of the following:

   +------------------------------------------------------+
   |                 |                      |             |
   | filename length |       filename       | delta group |
   |    (4 bytes)    | (<length - 4> bytes) |  (various)  |
   |                 |                      |             |
   +------------------------------------------------------+
   

That is, a *chunk* consisting of the filename (not terminated or padded) followed by N chunks constituting the *delta group* for this file. The *empty chunk* at the end of each *delta group* denotes the boundary to the next filelog sub-segment.

$ get-with-headers.py 127.0.0.1:$HGPORT "help/unknowntopic" 404 Not Found test: error

error

An error occurred while processing your request:

Not Found

[1] $ killdaemons.py #endif diff --git a/tests/test-hgweb-json.t b/tests/test-hgweb-json.t --- a/tests/test-hgweb-json.t +++ b/tests/test-hgweb-json.t @@ -1,2220 +1,2228 @@ #require serve $ request() { > get-with-headers.py --json localhost:$HGPORT "$1" > } $ hg init test $ cd test $ mkdir da $ echo foo > da/foo $ echo foo > foo $ hg -q ci -A -m initial $ echo bar > foo $ hg ci -m 'modify foo' $ echo bar > da/foo $ hg ci -m 'modify da/foo' $ hg bookmark bookmark1 $ hg up default 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (leaving bookmark bookmark1) $ hg mv foo foo-new $ hg commit -m 'move foo' $ hg tag -m 'create tag' tag1 $ hg phase --public -r . $ echo baz > da/foo $ hg commit -m 'another commit to da/foo' $ hg tag -m 'create tag2' tag2 $ hg bookmark bookmark2 $ hg -q up -r 0 $ hg -q branch test-branch $ echo branch > foo $ hg commit -m 'create test branch' $ echo branch_commit_2 > foo $ hg commit -m 'another commit in test-branch' $ hg -q up default $ hg merge --tool :local test-branch 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg commit -m 'merge test-branch into default' $ hg log -G @ changeset: 9:cc725e08502a |\ tag: tip | | parent: 6:ceed296fe500 | | parent: 8:ed66c30e87eb | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: merge test-branch into default | | | o changeset: 8:ed66c30e87eb | | branch: test-branch | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: another commit in test-branch | | | o changeset: 7:6ab967a8ab34 | | branch: test-branch | | parent: 0:06e557f3edf6 | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: create test branch | | o | changeset: 6:ceed296fe500 | | bookmark: bookmark2 | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: create tag2 | | o | changeset: 5:f2890a05fea4 | | tag: tag2 | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: another commit to da/foo | | o | changeset: 4:93a8ce14f891 | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: create tag | | o | changeset: 3:78896eb0e102 | | tag: tag1 | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: move foo | | o | changeset: 2:8d7c456572ac | | bookmark: bookmark1 | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: modify da/foo | | o | changeset: 1:f8bbb9024b10 |/ user: test | date: Thu Jan 01 00:00:00 1970 +0000 | summary: modify foo | o changeset: 0:06e557f3edf6 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: initial $ echo '[web]' >> .hg/hgrc $ echo 'allow-archive = bz2' >> .hg/hgrc $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E error.log $ cat hg.pid >> $DAEMON_PIDS (Try to keep these in roughly the order they are defined in webcommands.py) (log is handled by filelog/ and changelog/ - ignore it) (rawfile/ doesn't use templating - nothing to test) file/{revision}/{path} shows file revision $ request json-file/78896eb0e102/foo-new 200 Script output follows { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "move foo", "lines": [ { "line": "bar\n" } ], "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "path": "foo-new", "phase": "public", "tags": [ "tag1" ], "user": "test" } file/{revision} shows root directory info $ request json-file/cc725e08502a 200 Script output follows { "abspath": "/", "bookmarks": [], "directories": [ { "abspath": "/da", "basename": "da", "emptydirs": "" } ], "files": [ { "abspath": ".hgtags", "basename": ".hgtags", "date": [ 0.0, 0 ], "flags": "", "size": 92 }, { "abspath": "foo-new", "basename": "foo-new", "date": [ 0.0, 0 ], "flags": "", "size": 4 } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "tags": [ "tip" ] } changelog/ shows information about several changesets $ request json-changelog 200 Script output follows { "changeset_count": 10, "changesets": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "merge test-branch into default", "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "parents": [ "ceed296fe500c3fac9541e31dad860cb49c89e45", "ed66c30e87eb65337c05a4229efaa5f1d5285a90" ], "phase": "draft", "tags": [ "tip" ], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "another commit in test-branch", "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "parents": [ "6ab967a8ab3489227a83f80e920faa039a71819f" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "create test branch", "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [ "bookmark2" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag2", "node": "ceed296fe500c3fac9541e31dad860cb49c89e45", "parents": [ "f2890a05fea49bfaf9fb27ed5490894eba32da78" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "another commit to da/foo", "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "parents": [ "93a8ce14f89156426b7fa981af8042da53f03aa0" ], "phase": "draft", "tags": [ "tag2" ], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag", "node": "93a8ce14f89156426b7fa981af8042da53f03aa0", "parents": [ "78896eb0e102174ce9278438a95e12543e4367a7" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "move foo", "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "public", "tags": [ "tag1" ], "user": "test" }, { "bookmarks": [ "bookmark1" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify da/foo", "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7" } changelog/{revision} shows information starting at a specific changeset $ request json-changelog/f8bbb9024b10 200 Script output follows { "changeset_count": 10, "changesets": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ], "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8" } shortlog/ shows information about a set of changesets $ request json-shortlog 200 Script output follows { "changeset_count": 10, "changesets": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "merge test-branch into default", "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "parents": [ "ceed296fe500c3fac9541e31dad860cb49c89e45", "ed66c30e87eb65337c05a4229efaa5f1d5285a90" ], "phase": "draft", "tags": [ "tip" ], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "another commit in test-branch", "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "parents": [ "6ab967a8ab3489227a83f80e920faa039a71819f" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "create test branch", "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [ "bookmark2" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag2", "node": "ceed296fe500c3fac9541e31dad860cb49c89e45", "parents": [ "f2890a05fea49bfaf9fb27ed5490894eba32da78" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "another commit to da/foo", "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "parents": [ "93a8ce14f89156426b7fa981af8042da53f03aa0" ], "phase": "draft", "tags": [ "tag2" ], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag", "node": "93a8ce14f89156426b7fa981af8042da53f03aa0", "parents": [ "78896eb0e102174ce9278438a95e12543e4367a7" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "move foo", "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "public", "tags": [ "tag1" ], "user": "test" }, { "bookmarks": [ "bookmark1" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify da/foo", "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7" } shortlog is displayed by default (issue5978) $ request '?style=json' 200 Script output follows { "changeset_count": 10, "changesets": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "merge test-branch into default", "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "parents": [ "ceed296fe500c3fac9541e31dad860cb49c89e45", "ed66c30e87eb65337c05a4229efaa5f1d5285a90" ], "phase": "draft", "tags": [ "tip" ], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "another commit in test-branch", "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "parents": [ "6ab967a8ab3489227a83f80e920faa039a71819f" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "create test branch", "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [ "bookmark2" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag2", "node": "ceed296fe500c3fac9541e31dad860cb49c89e45", "parents": [ "f2890a05fea49bfaf9fb27ed5490894eba32da78" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "another commit to da/foo", "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "parents": [ "93a8ce14f89156426b7fa981af8042da53f03aa0" ], "phase": "draft", "tags": [ "tag2" ], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag", "node": "93a8ce14f89156426b7fa981af8042da53f03aa0", "parents": [ "78896eb0e102174ce9278438a95e12543e4367a7" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "move foo", "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "public", "tags": [ "tag1" ], "user": "test" }, { "bookmarks": [ "bookmark1" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify da/foo", "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7" } changeset/ renders the tip changeset $ request json-rev 200 Script output follows { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "merge test-branch into default", "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "parents": [ "ceed296fe500c3fac9541e31dad860cb49c89e45", "ed66c30e87eb65337c05a4229efaa5f1d5285a90" ], "phase": "draft", "tags": [ "tip" ], "user": "test" } changeset/{revision} shows tags $ request json-rev/78896eb0e102 200 Script output follows { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "move foo", "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "public", "tags": [ "tag1" ], "user": "test" } changeset/{revision} shows bookmarks $ request json-rev/8d7c456572ac 200 Script output follows { "bookmarks": [ "bookmark1" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify da/foo", "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "phase": "public", "tags": [], "user": "test" } changeset/{revision} shows branches $ request json-rev/6ab967a8ab34 200 Script output follows { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "create test branch", "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "tags": [], "user": "test" } manifest/{revision}/{path} shows info about a directory at a revision $ request json-manifest/06e557f3edf6/ 200 Script output follows { "abspath": "/", "bookmarks": [], "directories": [ { "abspath": "/da", "basename": "da", "emptydirs": "" } ], "files": [ { "abspath": "foo", "basename": "foo", "date": [ 0.0, 0 ], "flags": "", "size": 4 } ], "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "tags": [] } tags/ shows tags info $ request json-tags 200 Script output follows { "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "tags": [ { "date": [ 0.0, 0 ], "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "tag": "tag2" }, { "date": [ 0.0, 0 ], "node": "78896eb0e102174ce9278438a95e12543e4367a7", "tag": "tag1" } ] } bookmarks/ shows bookmarks info $ request json-bookmarks 200 Script output follows { "bookmarks": [ { "bookmark": "bookmark2", "date": [ 0.0, 0 ], "node": "ceed296fe500c3fac9541e31dad860cb49c89e45" }, { "bookmark": "bookmark1", "date": [ 0.0, 0 ], "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5" } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7" } branches/ shows branches info $ request json-branches 200 Script output follows { "branches": [ { "branch": "default", "date": [ 0.0, 0 ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "status": "open" }, { "branch": "test-branch", "date": [ 0.0, 0 ], "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "status": "inactive" } ] } summary/ shows a summary of repository state $ request json-summary 200 Script output follows { "archives": [ { "extension": ".tar.bz2", "node": "tip", "type": "bz2", "url": "http://*:$HGPORT/archive/tip.tar.bz2" (glob) } ], "bookmarks": [ { "bookmark": "bookmark2", "date": [ 0.0, 0 ], "node": "ceed296fe500c3fac9541e31dad860cb49c89e45" }, { "bookmark": "bookmark1", "date": [ 0.0, 0 ], "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5" } ], "branches": [ { "branch": "default", "date": [ 0.0, 0 ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "status": "open" }, { "branch": "test-branch", "date": [ 0.0, 0 ], "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "status": "inactive" } ], "labels": [], "lastchange": [ 0.0, 0 ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "shortlog": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "merge test-branch into default", "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "parents": [ "ceed296fe500c3fac9541e31dad860cb49c89e45", "ed66c30e87eb65337c05a4229efaa5f1d5285a90" ], "phase": "draft", "tags": [ "tip" ], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "another commit in test-branch", "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "parents": [ "6ab967a8ab3489227a83f80e920faa039a71819f" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "create test branch", "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [ "bookmark2" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag2", "node": "ceed296fe500c3fac9541e31dad860cb49c89e45", "parents": [ "f2890a05fea49bfaf9fb27ed5490894eba32da78" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "another commit to da/foo", "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "parents": [ "93a8ce14f89156426b7fa981af8042da53f03aa0" ], "phase": "draft", "tags": [ "tag2" ], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag", "node": "93a8ce14f89156426b7fa981af8042da53f03aa0", "parents": [ "78896eb0e102174ce9278438a95e12543e4367a7" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "move foo", "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "public", "tags": [ "tag1" ], "user": "test" }, { "bookmarks": [ "bookmark1" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify da/foo", "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ], "tags": [ { "date": [ 0.0, 0 ], "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "tag": "tag2" }, { "date": [ 0.0, 0 ], "node": "78896eb0e102174ce9278438a95e12543e4367a7", "tag": "tag1" } ] } $ request json-changelog?rev=create 200 Script output follows { "entries": [ { "bookmarks": [], "branch": "test-branch", "date": [ 0.0, 0 ], "desc": "create test branch", "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [ "bookmark2" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag2", "node": "ceed296fe500c3fac9541e31dad860cb49c89e45", "parents": [ "f2890a05fea49bfaf9fb27ed5490894eba32da78" ], "phase": "draft", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "create tag", "node": "93a8ce14f89156426b7fa981af8042da53f03aa0", "parents": [ "78896eb0e102174ce9278438a95e12543e4367a7" ], "phase": "public", "tags": [], "user": "test" } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "query": "create" } filediff/{revision}/{path} shows changes to a file in a revision $ request json-diff/f8bbb9024b10/foo 200 Script output follows { "author": "test", "children": [], "date": [ 0.0, 0 ], "desc": "modify foo", "diff": [ { "blockno": 1, "lines": [ { "l": "--- a/foo\tThu Jan 01 00:00:00 1970 +0000\n", "n": 1, "t": "-" }, { "l": "+++ b/foo\tThu Jan 01 00:00:00 1970 +0000\n", "n": 2, "t": "+" }, { "l": "@@ -1,1 +1,1 @@\n", "n": 3, "t": "@" }, { "l": "-foo\n", "n": 4, "t": "-" }, { "l": "+bar\n", "n": 5, "t": "+" } ] } ], "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "path": "foo" } comparison/{revision}/{path} shows information about before and after for a file $ request json-comparison/f8bbb9024b10/foo 200 Script output follows { "author": "test", "children": [], "comparison": [ { "lines": [ { "ll": "foo", "ln": 1, "rl": "bar", "rn": 1, "t": "replace" } ] } ], "date": [ 0.0, 0 ], "desc": "modify foo", "leftnode": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "path": "foo", "rightnode": "f8bbb9024b10f93cdbb8d940337398291d40dea8" } annotate/{revision}/{path} shows annotations for each line $ request json-annotate/f8bbb9024b10/foo 200 Script output follows { "abspath": "foo", "annotate": [ { "abspath": "foo", "author": "test", "desc": "modify foo", "line": "bar\n", "lineno": 1, "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "revdate": [ 0.0, 0 ], "targetline": 1 } ], "author": "test", "children": [], "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "permissions": "" } filelog/{revision}/{path} shows history of a single file $ request json-filelog/f8bbb9024b10/foo 200 Script output follows { "entries": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify foo", "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ] } $ request json-filelog/cc725e08502a/da/foo 200 Script output follows { "entries": [ { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "another commit to da/foo", "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "draft", "tags": [ "tag2" ], "user": "test" }, { "bookmarks": [ "bookmark1" ], "branch": "default", "date": [ 0.0, 0 ], "desc": "modify da/foo", "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "date": [ 0.0, 0 ], "desc": "initial", "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "tags": [], "user": "test" } ] } (archive/ doesn't use templating, so ignore it) (static/ doesn't use templating, so ignore it) graph/ shows information that can be used to render a graph of the DAG $ request json-graph 200 Script output follows { "changeset_count": 10, "changesets": [ { "bookmarks": [], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "merge test-branch into default", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 0, "color": 1, "nextcol": 1, "width": -1 } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7", "parents": [ "ceed296fe500c3fac9541e31dad860cb49c89e45", "ed66c30e87eb65337c05a4229efaa5f1d5285a90" ], "phase": "draft", "row": 0, "tags": [ "tip" ], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "col": 1, "color": 2, "date": [ 0.0, 0 ], "desc": "another commit in test-branch", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90", "parents": [ "6ab967a8ab3489227a83f80e920faa039a71819f" ], "phase": "draft", "row": 1, "tags": [], "user": "test" }, { "bookmarks": [], "branch": "test-branch", "col": 1, "color": 2, "date": [ 0.0, 0 ], "desc": "create test branch", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "6ab967a8ab3489227a83f80e920faa039a71819f", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "draft", "row": 2, "tags": [], "user": "test" }, { "bookmarks": [ "bookmark2" ], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "create tag2", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "ceed296fe500c3fac9541e31dad860cb49c89e45", "parents": [ "f2890a05fea49bfaf9fb27ed5490894eba32da78" ], "phase": "draft", "row": 3, "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "another commit to da/foo", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78", "parents": [ "93a8ce14f89156426b7fa981af8042da53f03aa0" ], "phase": "draft", "row": 4, "tags": [ "tag2" ], "user": "test" }, { "bookmarks": [], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "create tag", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "93a8ce14f89156426b7fa981af8042da53f03aa0", "parents": [ "78896eb0e102174ce9278438a95e12543e4367a7" ], "phase": "public", "row": 5, "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "move foo", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "78896eb0e102174ce9278438a95e12543e4367a7", "parents": [ "8d7c456572acf3557e8ed8a07286b10c408bcec5" ], "phase": "public", "row": 6, "tags": [ "tag1" ], "user": "test" }, { "bookmarks": [ "bookmark1" ], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "modify da/foo", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 1, "width": -1 } ], "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5", "parents": [ "f8bbb9024b10f93cdbb8d940337398291d40dea8" ], "phase": "public", "row": 7, "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "col": 0, "color": 1, "date": [ 0.0, 0 ], "desc": "modify foo", "edges": [ { "bcolor": "", "col": 0, "color": 1, "nextcol": 0, "width": -1 }, { "bcolor": "", "col": 1, "color": 2, "nextcol": 0, "width": -1 } ], "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8", "parents": [ "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e" ], "phase": "public", "row": 8, "tags": [], "user": "test" }, { "bookmarks": [], "branch": "default", "col": 0, "color": 2, "date": [ 0.0, 0 ], "desc": "initial", "edges": [], "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e", "parents": [], "phase": "public", "row": 9, "tags": [], "user": "test" } ], "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7" } help/ shows help topics $ request json-help 200 Script output follows { "earlycommands": [ { "summary": "add the specified files on the next commit", "topic": "add" }, { "summary": "show changeset information by line for each file", "topic": "annotate" }, { "summary": "make a copy of an existing repository", "topic": "clone" }, { "summary": "commit the specified files or all outstanding changes", "topic": "commit" }, { "summary": "diff repository (or selected files)", "topic": "diff" }, { "summary": "dump the header and diffs for one or more changesets", "topic": "export" }, { "summary": "forget the specified files on the next commit", "topic": "forget" }, { "summary": "create a new repository in the given directory", "topic": "init" }, { "summary": "show revision history of entire repository or files", "topic": "log" }, { "summary": "merge another revision into working directory", "topic": "merge" }, { "summary": "pull changes from the specified source", "topic": "pull" }, { "summary": "push changes to the specified destination", "topic": "push" }, { "summary": "remove the specified files on the next commit", "topic": "remove" }, { "summary": "start stand-alone webserver", "topic": "serve" }, { "summary": "show changed files in the working directory", "topic": "status" }, { "summary": "summarize working directory state", "topic": "summary" }, { "summary": "update working directory (or switch revisions)", "topic": "update" } ], "othercommands": [ { "summary": "add all new files, delete all missing files", "topic": "addremove" }, { "summary": "create an unversioned archive of a repository revision", "topic": "archive" }, { "summary": "reverse effect of earlier changeset", "topic": "backout" }, { "summary": "subdivision search of changesets", "topic": "bisect" }, { "summary": "create a new bookmark or list existing bookmarks", "topic": "bookmarks" }, { "summary": "set or show the current branch name", "topic": "branch" }, { "summary": "list repository named branches", "topic": "branches" }, { "summary": "create a bundle file", "topic": "bundle" }, { "summary": "output the current or given revision of files", "topic": "cat" }, { "summary": "show combined config settings from all hgrc files", "topic": "config" }, { "summary": "mark files as copied for the next commit", "topic": "copy" }, { "summary": "list tracked files", "topic": "files" }, { "summary": "copy changes from other branches onto the current branch", "topic": "graft" }, { "summary": "search revision history for a pattern in specified files", "topic": "grep" }, { "summary": "show branch heads", "topic": "heads" }, { "summary": "show help for a given topic or a help overview", "topic": "help" }, { "summary": "identify the working directory or specified revision", "topic": "identify" }, { "summary": "import an ordered set of patches", "topic": "import" }, { "summary": "show new changesets found in source", "topic": "incoming" }, { "summary": "output the current or given revision of the project manifest", "topic": "manifest" }, { "summary": "show changesets not found in the destination", "topic": "outgoing" }, { "summary": "show aliases for remote repositories", "topic": "paths" }, { "summary": "set or show the current phase name", "topic": "phase" }, { "summary": "roll back an interrupted transaction", "topic": "recover" }, { "summary": "rename files; equivalent of copy + remove", "topic": "rename" }, { "summary": "redo merges or set/view the merge status of files", "topic": "resolve" }, { "summary": "restore files to their checkout state", "topic": "revert" }, { "summary": "print the root (top) of the current working directory", "topic": "root" }, { + "summary": "save and set aside changes from the working directory", + "topic": "shelve" + }, + { "summary": "add one or more tags for the current or given revision", "topic": "tag" }, { "summary": "list repository tags", "topic": "tags" }, { "summary": "apply one or more bundle files", "topic": "unbundle" }, { + "summary": "restore a shelved change to the working directory", + "topic": "unshelve" + }, + { "summary": "verify the integrity of the repository", "topic": "verify" }, { "summary": "output version and copyright information", "topic": "version" } ], "topics": [ { "summary": "Bundle File Formats", "topic": "bundlespec" }, { "summary": "Colorizing Outputs", "topic": "color" }, { "summary": "Configuration Files", "topic": "config" }, { "summary": "Date Formats", "topic": "dates" }, { "summary": "Deprecated Features", "topic": "deprecated" }, { "summary": "Diff Formats", "topic": "diffs" }, { "summary": "Environment Variables", "topic": "environment" }, { "summary": "Using Additional Features", "topic": "extensions" }, { "summary": "Specifying File Sets", "topic": "filesets" }, { "summary": "Command-line flags", "topic": "flags" }, { "summary": "Glossary", "topic": "glossary" }, { "summary": "Syntax for Mercurial Ignore Files", "topic": "hgignore" }, { "summary": "Configuring hgweb", "topic": "hgweb" }, { "summary": "Technical implementation topics", "topic": "internals" }, { "summary": "Merge Tools", "topic": "merge-tools" }, { "summary": "Pager Support", "topic": "pager" }, { "summary": "File Name Patterns", "topic": "patterns" }, { "summary": "Working with Phases", "topic": "phases" }, { "summary": "Specifying Revisions", "topic": "revisions" }, { "summary": "Using Mercurial from scripts and automation", "topic": "scripting" }, { "summary": "Subrepositories", "topic": "subrepos" }, { "summary": "Template Usage", "topic": "templating" }, { "summary": "URL Paths", "topic": "urls" } ] } help/{topic} shows an individual help topic $ request json-help/phases 200 Script output follows { "rawdoc": "Working with Phases\n*", (glob) "topic": "phases" } Error page shouldn't crash $ request json-changeset/deadbeef 404 Not Found { "error": "unknown revision 'deadbeef'" } [1] Commit message with Japanese Kanji 'Noh', which ends with '\x5c' $ echo foo >> da/foo >>> open('msg', 'wb').write(b'\x94\x5c\x0a') and None $ HGENCODING=cp932 hg ci -l msg Commit message with null character $ echo foo >> da/foo >>> open('msg', 'wb').write(b'commit with null character: \0\n') and None $ hg ci -l msg $ rm msg Stop and restart with HGENCODING=cp932 $ killdaemons.py $ HGENCODING=cp932 hg serve -p $HGPORT -d --pid-file=hg.pid \ > -A access.log -E error.log $ cat hg.pid >> $DAEMON_PIDS Test json escape of multibyte characters $ request json-filelog/tip/da/foo?revcount=2 | grep '"desc":' "desc": "commit with null character: \u0000", "desc": "\u80fd", diff --git a/tests/test-keyword.t b/tests/test-keyword.t --- a/tests/test-keyword.t +++ b/tests/test-keyword.t @@ -1,1473 +1,1468 @@ #require no-reposimplestore Run kwdemo outside a repo $ hg -q --config extensions.keyword= --config keywordmaps.Foo="{author|user}" kwdemo [extensions] keyword = [keyword] demo.txt = [keywordset] svn = False [keywordmaps] Foo = {author|user} $Foo: test $ $ cat <> $HGRCPATH > [extensions] > keyword = > mq = > notify = > record = > transplant = > [ui] > interactive = true > EOF hide outer repo $ hg init Run kwdemo before [keyword] files are set up as it would succeed without uisetup otherwise $ hg --quiet kwdemo [extensions] keyword = [keyword] demo.txt = [keywordset] svn = False [keywordmaps] Author = {author|user} Date = {date|utcdate} Header = {root}/{file},v {node|short} {date|utcdate} {author|user} Id = {file|basename},v {node|short} {date|utcdate} {author|user} RCSFile = {file|basename},v RCSfile = {file|basename},v Revision = {node|short} Source = {root}/{file},v $Author: test $ $Date: ????/??/?? ??:??:?? $ (glob) $Header: */demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob) $Id: demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob) $RCSFile: demo.txt,v $ $RCSfile: demo.txt,v $ $Revision: ???????????? $ (glob) $Source: */demo.txt,v $ (glob) $ hg --quiet kwdemo "Branch = {branches}" [extensions] keyword = [keyword] demo.txt = [keywordset] svn = False [keywordmaps] Branch = {branches} $Branch: demobranch $ (test template filter svnisodate and svnutcdate) $ hg --quiet kwdemo --config keywordset.svn=True [extensions] keyword = [keyword] demo.txt = [keywordset] svn = True [keywordmaps] Author = {author|user} Date = {date|svnisodate} Id = {file|basename},v {node|short} {date|svnutcdate} {author|user} LastChangedBy = {author|user} LastChangedDate = {date|svnisodate} LastChangedRevision = {node|short} Revision = {node|short} $Author: test $ $Date: ????-??-?? ??:??:?? ????? (???, ?? ??? ????) $ (glob) $Id: demo.txt,v ???????????? ????-??-?? ??:??:??Z test $ (glob) $LastChangedBy: test $ $LastChangedDate: ????-??-?? ??:??:?? ????? (???, ?? ??? ????) $ (glob) $LastChangedRevision: ???????????? $ (glob) $Revision: ???????????? $ (glob) $ cat <> $HGRCPATH > [keyword] > ** = > b = ignore > i = ignore > [hooks] > EOF $ cp $HGRCPATH $HGRCPATH.nohooks > cat <> $HGRCPATH > commit= > commit.test=cp a hooktest > EOF $ hg init Test-bndl $ cd Test-bndl kwshrink should exit silently in empty/invalid repo $ hg kwshrink Symlinks cannot be created on Windows. A bundle to test this was made with: hg init t cd t echo a > a ln -s a sym hg add sym hg ci -m addsym -u mercurial hg bundle --base null ../test-keyword.hg $ hg unbundle "$TESTDIR"/bundles/test-keyword.hg adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files new changesets a2392c293916 (1 drafts) (run 'hg update' to get a working copy) $ hg up a2392c293916 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 'expand $Id$' > a $ echo 'do not process $Id:' >> a $ echo 'xxx $' >> a $ echo 'ignore $Id$' > b Output files as they were created $ cat a b expand $Id$ do not process $Id: xxx $ ignore $Id$ no kwfiles $ hg kwfiles untracked candidates $ hg -v kwfiles --unknown k a Add files and check status $ hg addremove adding a adding b $ hg status A a A b Default keyword expansion including commit hook Interrupted commit should not change state or run commit hook $ hg --debug commit abort: empty commit message [255] $ hg status A a A b Commit with several checks $ hg --debug commit -mabsym -u 'User Name ' committing files: a b committing manifest committing changelog overwriting a expanding keywords updating the branch cache committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9 running hook commit.test: cp a hooktest $ hg status ? hooktest $ hg debugrebuildstate $ hg --quiet identify ef63ca68695b cat files in working directory with keywords expanded $ cat a b expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ do not process $Id: xxx $ ignore $Id$ hg cat files and symlink, no expansion $ hg cat sym a b && echo expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ do not process $Id: xxx $ ignore $Id$ a $ diff a hooktest $ cp $HGRCPATH.nohooks $HGRCPATH $ rm hooktest hg status of kw-ignored binary file starting with '\1\n' >>> open("i", "wb").write(b"\1\nfoo") and None $ hg -q commit -Am metasep i $ hg status >>> open("i", "wb").write(b"\1\nbar") and None $ hg status M i $ hg -q commit -m "modify metasep" i $ hg status --rev 2:3 M i $ touch empty $ hg -q commit -A -m "another file" $ hg status -A --rev 3:4 i C i $ hg -q strip --no-backup 2 Test hook execution bundle $ hg bundle --base null ../kw.hg 2 changesets found $ cd .. $ hg init Test $ cd Test Notify on pull to check whether keywords stay as is in email ie. if patch.diff wrapper acts as it should $ cat <> $HGRCPATH > [hooks] > incoming.notify = python:hgext.notify.hook > [notify] > sources = pull > diffstat = False > maxsubject = 15 > [reposubs] > * = Test > EOF Pull from bundle and trigger notify $ hg pull -u ../kw.hg pulling from ../kw.hg requesting all changes adding changesets adding manifests adding file changes added 2 changesets with 3 changes to 3 files new changesets a2392c293916:ef63ca68695b (2 drafts) 3 files updated, 0 files merged, 0 files removed, 0 files unresolved MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Date: * (glob) Subject: changeset in... From: mercurial X-Hg-Notification: changeset a2392c293916 Message-Id: (glob) To: Test changeset a2392c293916 in $TESTTMP/Test details: $TESTTMP/Test?cmd=changeset;node=a2392c293916 description: addsym diffs (6 lines): diff -r 000000000000 -r a2392c293916 sym --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sym Sat Feb 09 20:25:47 2008 +0100 @@ -0,0 +1,1 @@ +a \ No newline at end of file MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Date:* (glob) Subject: changeset in... From: User Name X-Hg-Notification: changeset ef63ca68695b Message-Id: (glob) To: Test changeset ef63ca68695b in $TESTTMP/Test details: $TESTTMP/Test?cmd=changeset;node=ef63ca68695b description: absym diffs (12 lines): diff -r a2392c293916 -r ef63ca68695b a --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -0,0 +1,3 @@ +expand $Id$ +do not process $Id: +xxx $ diff -r a2392c293916 -r ef63ca68695b b --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/b Thu Jan 01 00:00:00 1970 +0000 @@ -0,0 +1,1 @@ +ignore $Id$ $ cp $HGRCPATH.nohooks $HGRCPATH Touch files and check with status $ touch a b $ hg status Update and expand $ rm sym a b $ hg update -C 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat a b expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ do not process $Id: xxx $ ignore $Id$ Check whether expansion is filewise and file mode is preserved $ echo '$Id$' > c $ echo 'tests for different changenodes' >> c #if unix-permissions $ chmod 600 c $ ls -l c | cut -b 1-10 -rw------- #endif commit file c $ hg commit -A -mcndiff -d '1 0' -u 'User Name ' adding c #if unix-permissions $ ls -l c | cut -b 1-10 -rw------- #endif force expansion $ hg -v kwexpand overwriting a expanding keywords overwriting c expanding keywords compare changenodes in a and c $ cat a c expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ do not process $Id: xxx $ $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $ tests for different changenodes record $ echo '$Id$' > r $ hg add r record chunk >>> lines = open('a', 'rb').readlines() >>> lines.insert(1, b'foo\n') >>> lines.append(b'bar\n') >>> open('a', 'wb').writelines(lines) $ hg record -d '10 1' -m rectest a< y > n > EOF diff --git a/a b/a 2 hunks, 2 lines changed @@ -1,3 +1,4 @@ expand $Id$ +foo do not process $Id: xxx $ record change 1/2 to 'a'? [Ynesfdaq?] y @@ -2,2 +3,3 @@ do not process $Id: xxx $ +bar record change 2/2 to 'a'? [Ynesfdaq?] n $ hg identify 5f5eb23505c3+ tip $ hg status M a A r Cat modified file a $ cat a expand $Id: a,v 5f5eb23505c3 1970/01/01 00:00:10 test $ foo do not process $Id: xxx $ bar Diff remaining chunk $ hg diff a diff -r 5f5eb23505c3 a --- a/a Thu Jan 01 00:00:09 1970 -0000 +++ b/a * (glob) @@ -2,3 +2,4 @@ foo do not process $Id: xxx $ +bar $ hg rollback repository tip rolled back to revision 2 (undo commit) working directory now based on revision 2 Record all chunks in file a $ echo foo > msg - do not use "hg record -m" here! $ hg record -l msg -d '11 1' a< y > y > y > EOF diff --git a/a b/a 2 hunks, 2 lines changed @@ -1,3 +1,4 @@ expand $Id$ +foo do not process $Id: xxx $ record change 1/2 to 'a'? [Ynesfdaq?] y @@ -2,2 +3,3 @@ do not process $Id: xxx $ +bar record change 2/2 to 'a'? [Ynesfdaq?] y File a should be clean $ hg status -A a C a rollback and revert expansion $ cat a expand $Id: a,v 78e0a02d76aa 1970/01/01 00:00:11 test $ foo do not process $Id: xxx $ bar $ hg --verbose rollback repository tip rolled back to revision 2 (undo commit) working directory now based on revision 2 overwriting a expanding keywords $ hg status a M a $ cat a expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ foo do not process $Id: xxx $ bar $ echo '$Id$' > y $ echo '$Id$' > z $ hg add y $ hg commit -Am "rollback only" z $ cat z $Id: z,v 45a5d3adce53 1970/01/01 00:00:00 test $ $ hg --verbose rollback repository tip rolled back to revision 2 (undo commit) working directory now based on revision 2 overwriting z shrinking keywords Only z should be overwritten $ hg status a y z M a A y A z $ cat z $Id$ $ hg forget y z $ rm y z record added file alone $ hg -v record -l msg -d '12 2' r< y > y > EOF diff --git a/r b/r new file mode 100644 @@ -0,0 +1,1 @@ +$Id$ record this change to 'r'? [Ynesfdaq?] y resolving manifests patching file r committing files: r committing manifest committing changelog committed changeset 3:82a2f715724d overwriting r expanding keywords $ hg status r $ hg --verbose rollback repository tip rolled back to revision 2 (undo commit) working directory now based on revision 2 overwriting r shrinking keywords $ hg forget r $ rm msg r $ hg update -C 1 files updated, 0 files merged, 0 files removed, 0 files unresolved record added keyword ignored file $ echo '$Id$' > i $ hg add i $ hg --verbose record -d '13 1' -m recignored< y > y > EOF diff --git a/i b/i new file mode 100644 examine changes to 'i'? [Ynesfdaq?] y @@ -0,0 +1,1 @@ +$Id$ record this change to 'i'? [Ynesfdaq?] y resolving manifests patching file i committing files: i committing manifest committing changelog committed changeset 3:9f40ceb5a072 $ cat i $Id$ $ hg -q rollback $ hg forget i $ rm i amend $ echo amend >> a $ echo amend >> b $ hg -q commit -d '14 1' -m 'prepare amend' $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords overwriting a expanding keywords $ hg -q id 67d8c481a6be $ head -1 a expand $Id: a,v 67d8c481a6be 1970/01/01 00:00:15 test $ $ hg -q strip --no-backup tip Test patch queue repo $ hg init --mq $ hg qimport -r tip -n mqtest.diff $ hg commit --mq -m mqtest Keywords should not be expanded in patch $ cat .hg/patches/mqtest.diff # HG changeset patch # User User Name # Date 1 0 # Thu Jan 01 00:00:01 1970 +0000 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9 cndiff diff -r ef63ca68695b -r 40a904bbbe4c c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/c Thu Jan 01 00:00:01 1970 +0000 @@ -0,0 +1,2 @@ +$Id$ +tests for different changenodes $ hg qpop popping mqtest.diff patch queue now empty qgoto, implying qpush, should expand $ hg qgoto mqtest.diff applying mqtest.diff now at: mqtest.diff $ cat c $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $ tests for different changenodes $ hg cat c $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $ tests for different changenodes Keywords should not be expanded in filelog $ hg --config 'extensions.keyword=!' cat c $Id$ tests for different changenodes qpop and move on $ hg qpop popping mqtest.diff patch queue now empty Copy and show added kwfiles $ hg cp a c $ hg kwfiles a c Commit and show expansion in original and copy $ hg --debug commit -ma2c -d '1 0' -u 'User Name ' committing files: c c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292 committing manifest committing changelog overwriting c expanding keywords updating the branch cache committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d $ cat a c expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ do not process $Id: xxx $ expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $ do not process $Id: xxx $ Touch copied c and check its status $ touch c $ hg status Copy kwfile to keyword ignored file unexpanding keywords $ hg --verbose copy a i copying a to i overwriting i shrinking keywords $ head -n 1 i expand $Id$ $ hg forget i $ rm i Copy ignored file to ignored file: no overwriting $ hg --verbose copy b i copying b to i $ hg forget i $ rm i cp symlink file; hg cp -A symlink file (part1) - copied symlink points to kwfile: overwrite #if symlink $ cp sym i $ ls -l i -rw-r--r--* (glob) $ head -1 i expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ $ hg copy --after --verbose sym i copying sym to i overwriting i shrinking keywords $ head -1 i expand $Id$ $ hg forget i $ rm i #endif Test different options of hg kwfiles $ hg kwfiles a c $ hg -v kwfiles --ignore I b I sym $ hg kwfiles --all K a K c I b I sym Diff specific revision $ hg diff --rev 1 diff -r ef63ca68695b c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/c * (glob) @@ -0,0 +1,3 @@ +expand $Id$ +do not process $Id: +xxx $ Status after rollback: $ hg rollback repository tip rolled back to revision 1 (undo commit) working directory now based on revision 1 $ hg status A c $ hg update --clean 0 files updated, 0 files merged, 0 files removed, 0 files unresolved #if symlink cp symlink file; hg cp -A symlink file (part2) - copied symlink points to kw ignored file: do not overwrite $ cat a > i $ ln -s i symignored $ hg commit -Am 'fake expansion in ignored and symlink' i symignored $ cp symignored x $ hg copy --after --verbose symignored x copying symignored to x $ head -n 1 x expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ $ hg forget x $ rm x $ hg rollback repository tip rolled back to revision 1 (undo commit) working directory now based on revision 1 $ hg update --clean 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ rm i symignored #endif Custom keywordmaps as argument to kwdemo $ hg --quiet kwdemo "Xinfo = {author}: {desc}" [extensions] keyword = [keyword] ** = b = ignore demo.txt = i = ignore [keywordset] svn = False [keywordmaps] Xinfo = {author}: {desc} $Xinfo: test: hg keyword configuration and expansion example $ Configure custom keywordmaps $ cat <>$HGRCPATH > [keywordmaps] > Id = {file} {node|short} {date|rfc822date} {author|user} > Xinfo = {author}: {desc} > EOF Cat and hg cat files before custom expansion $ cat a b expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $ do not process $Id: xxx $ ignore $Id$ $ hg cat sym a b && echo expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $ do not process $Id: xxx $ ignore $Id$ a Write custom keyword and prepare multi-line commit message $ echo '$Xinfo$' >> a $ cat <> log > firstline > secondline > EOF Interrupted commit should not change state $ hg commit abort: empty commit message [255] $ hg status M a ? c ? log Commit with multi-line message and custom expansion $ hg --debug commit -l log -d '2 0' -u 'User Name ' committing files: a committing manifest committing changelog overwriting a expanding keywords updating the branch cache committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83 $ rm log Stat, verify and show custom expansion (firstline) $ hg status ? c $ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files checked 3 changesets with 4 changes to 3 files $ cat a b expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ ignore $Id$ $ hg cat sym a b && echo expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ ignore $Id$ a annotate $ hg annotate a 1: expand $Id$ 1: do not process $Id: 1: xxx $ 2: $Xinfo$ remove with status checks $ hg debugrebuildstate $ hg remove a $ hg --debug commit -m rma committing files: committing manifest committing changelog updating the branch cache committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012 $ hg status ? c Rollback, revert, and check expansion $ hg rollback repository tip rolled back to revision 2 (undo commit) working directory now based on revision 2 $ hg status R a ? c $ hg revert --no-backup --rev tip a $ cat a expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ Clone to test global and local configurations $ cd .. Expansion in destination with global configuration $ hg --quiet clone Test globalconf $ cat globalconf/a expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ No expansion in destination with local configuration in origin only $ hg --quiet --config 'keyword.**=ignore' clone Test localconf $ cat localconf/a expand $Id$ do not process $Id: xxx $ $Xinfo$ Clone to test incoming $ hg clone -r1 Test Test-a adding changesets adding manifests adding file changes added 2 changesets with 3 changes to 3 files new changesets a2392c293916:ef63ca68695b updating to branch default 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd Test-a $ cat <> .hg/hgrc > [paths] > default = ../Test > EOF $ hg incoming comparing with $TESTTMP/Test searching for changes changeset: 2:bb948857c743 tag: tip user: User Name date: Thu Jan 01 00:00:02 1970 +0000 summary: firstline Imported patch should not be rejected >>> import re >>> text = re.sub(br'(Id.*)', br'\1 rejecttest', open('a', 'rb').read()) >>> open('a', 'wb').write(text) and None $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name ' committing files: a committing manifest committing changelog overwriting a expanding keywords updating the branch cache committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082 $ hg export -o ../rejecttest.diff tip $ cd ../Test $ hg import ../rejecttest.diff applying ../rejecttest.diff $ cat a b expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest do not process $Id: rejecttest xxx $ $Xinfo: User Name : rejects? $ ignore $Id$ $ hg rollback repository tip rolled back to revision 2 (undo import) working directory now based on revision 2 $ hg update --clean 1 files updated, 0 files merged, 0 files removed, 0 files unresolved kwexpand/kwshrink on selected files $ mkdir x $ hg copy a x/a $ hg --verbose kwshrink a overwriting a shrinking keywords - sleep required for dirstate.normal() check $ sleep 1 $ hg status a $ hg --verbose kwexpand a overwriting a expanding keywords $ hg status a kwexpand x/a should abort $ hg --verbose kwexpand x/a abort: outstanding uncommitted changes [255] $ cd x $ hg --debug commit -m xa -d '3 0' -u 'User Name ' committing files: x/a x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e committing manifest committing changelog overwriting x/a expanding keywords updating the branch cache committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4 $ cat a expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : xa $ kwshrink a inside directory x $ hg --verbose kwshrink a overwriting x/a shrinking keywords $ cat a expand $Id$ do not process $Id: xxx $ $Xinfo$ $ cd .. kwexpand nonexistent $ hg kwexpand nonexistent nonexistent:* (glob) #if serve hg serve - expand with hgweb file - no expansion with hgweb annotate/changeset/filediff/comparison - expand with hgweb file, again - check errors $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS $ get-with-headers.py localhost:$HGPORT 'file/tip/a/?style=raw' 200 Script output follows expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ $ get-with-headers.py localhost:$HGPORT 'annotate/tip/a/?style=raw' 200 Script output follows user@1: expand $Id$ user@1: do not process $Id: user@1: xxx $ user@2: $Xinfo$ $ get-with-headers.py localhost:$HGPORT 'rev/tip/?style=raw' 200 Script output follows # HG changeset patch # User User Name # Date 3 0 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83 xa diff -r bb948857c743 -r b4560182a3f9 x/a --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000 @@ -0,0 +1,4 @@ +expand $Id$ +do not process $Id: +xxx $ +$Xinfo$ $ get-with-headers.py localhost:$HGPORT 'diff/bb948857c743/a?style=raw' 200 Script output follows diff -r ef63ca68695b -r bb948857c743 a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:02 1970 +0000 @@ -1,3 +1,4 @@ expand $Id$ do not process $Id: xxx $ +$Xinfo$ $ get-with-headers.py localhost:$HGPORT 'comparison/bb948857c743/a' | grep '\$[a-zA-Z]' 1 expand $Id$ 1 expand $Id$ 2 do not process $Id: 2 do not process $Id: 4 $Xinfo$ (check "kwweb_skip"-ed webcommand doesn't suppress expanding keywords at subsequent webcommands) $ get-with-headers.py localhost:$HGPORT 'file/tip/a/?style=raw' 200 Script output follows expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ $ killdaemons.py $ cat errors.log #endif Prepare merge and resolve tests $ echo '$Id$' > m $ hg add m $ hg commit -m 4kw $ echo foo >> m $ hg commit -m 5foo simplemerge $ hg update 4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo foo >> m $ hg commit -m 6foo created new head $ hg merge 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg commit -m simplemerge $ cat m $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $ foo conflict: keyword should stay outside conflict zone $ hg update 4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo bar >> m $ hg commit -m 8bar created new head $ hg merge merging m warning: conflicts while merging m! (edit, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon [1] $ cat m $Id$ <<<<<<< working copy: 88a80c8d172e - test: 8bar bar ======= foo >>>>>>> merge rev: 85d2d2d732a5 - test: simplemerge resolve to local, m must contain hash of last change (local parent) $ hg resolve -t internal:local -a (no more unresolved files) $ hg commit -m localresolve $ cat m $Id: m 88a80c8d172e Thu, 01 Jan 1970 00:00:00 +0000 test $ bar Test restricted mode with transplant -b $ hg update 6 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch foo marked working directory as branch foo (branches are permanent and global, did you want a bookmark?) $ mv a a.bak $ echo foobranch > a $ cat a.bak >> a $ rm a.bak $ hg commit -m 9foobranch $ hg update default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -y transplant -b foo tip applying 4aa30d025d50 4aa30d025d50 transplanted to e00abbf63521 Expansion in changeset but not in file $ hg tip -p changeset: 11:e00abbf63521 tag: tip parent: 9:800511b3a22d user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: 9foobranch diff -r 800511b3a22d -r e00abbf63521 a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +1,4 @@ +foobranch expand $Id$ do not process $Id: xxx $ $ head -n 2 a foobranch expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $ Turn off expansion $ hg -q rollback $ hg -q update -C kwshrink with unknown file u $ cp a u $ hg --verbose kwshrink overwriting a shrinking keywords overwriting m shrinking keywords overwriting x/a shrinking keywords Keywords shrunk in working directory, but not yet disabled - cat shows unexpanded keywords - hg cat shows expanded keywords $ cat a b expand $Id$ do not process $Id: xxx $ $Xinfo$ ignore $Id$ $ hg cat sym a b && echo expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $ do not process $Id: xxx $ $Xinfo: User Name : firstline $ ignore $Id$ a Now disable keyword expansion $ cp $HGRCPATH $HGRCPATH.backup $ rm "$HGRCPATH" $ cat a b expand $Id$ do not process $Id: xxx $ $Xinfo$ ignore $Id$ $ hg cat sym a b && echo expand $Id$ do not process $Id: xxx $ $Xinfo$ ignore $Id$ a enable keyword expansion again $ cat $HGRCPATH.backup >> $HGRCPATH Test restricted mode with unshelve - $ cat <> $HGRCPATH - > [extensions] - > shelve = - > EOF - $ echo xxxx >> a $ hg diff diff -r 800511b3a22d a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a * (glob) @@ -2,3 +2,4 @@ do not process $Id: xxx $ $Xinfo$ +xxxx $ hg shelve -q --name tmp $ hg shelve --list --patch tmp (*)* changes to: localresolve (glob) diff --git a/a b/a --- a/a +++ b/a @@ -2,3 +2,4 @@ do not process $Id: xxx $ $Xinfo$ +xxxx $ hg update -q -C 10 $ hg unshelve -q tmp $ hg diff diff -r 4aa30d025d50 a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a * (glob) @@ -3,3 +3,4 @@ do not process $Id: xxx $ $Xinfo$ +xxxx Test restricted mode with rebase $ cat <> $HGRCPATH > [extensions] > rebase = > EOF $ hg update -q -C 9 $ echo xxxx >> a $ hg commit -m '#11' $ hg diff -c 11 diff -r 800511b3a22d -r b07670694489 a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -2,3 +2,4 @@ do not process $Id: xxx $ $Xinfo$ +xxxx $ hg diff -c 10 diff -r 27d48ee14f67 -r 4aa30d025d50 a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +1,4 @@ +foobranch expand $Id$ do not process $Id: xxx $ $ hg rebase -q -s 10 -d 11 --keep $ hg diff -r 9 -r 12 a diff -r 800511b3a22d -r 1939b927726c a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +1,6 @@ +foobranch expand $Id$ do not process $Id: xxx $ $Xinfo$ +xxxx Test restricted mode with graft $ hg graft -q 10 $ hg diff -r 9 -r 13 a diff -r 800511b3a22d -r 01a68de1003a a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +1,6 @@ +foobranch expand $Id$ do not process $Id: xxx $ $Xinfo$ +xxxx Test restricted mode with backout $ hg backout -q 11 --no-commit $ hg diff a diff -r 01a68de1003a a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a * (glob) @@ -3,4 +3,3 @@ do not process $Id: xxx $ $Xinfo$ -xxxx Test restricted mode with histedit $ cat <> $HGRCPATH > [extensions] > histedit = > EOF $ hg commit -m 'backout #11' $ hg histedit -q --command - 13 < pick 49f5f2d940c3 14 backout #11 > pick 01a68de1003a 13 9foobranch > EOF Test restricted mode with fetch (with merge) $ cat <> $HGRCPATH > [extensions] > fetch = > EOF $ hg clone -q -r 9 . ../fetch-merge $ cd ../fetch-merge $ hg -R ../Test export 10 | hg import -q - $ hg fetch -q -r 11 $ hg diff -r 9 a diff -r 800511b3a22d a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a * (glob) @@ -1,4 +1,6 @@ +foobranch expand $Id$ do not process $Id: xxx $ $Xinfo$ +xxxx Test that patch.diff(), which is implied by "hg diff" or so, doesn't suppress expanding keywords at subsequent commands #if windows $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH" #else $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH" #endif $ export PYTHONPATH $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new $ mv $HGRCPATH.new $HGRCPATH >>> from __future__ import print_function >>> from hgclient import check, readchannel, runcommand >>> @check ... def check(server): ... # hello block ... readchannel(server) ... ... runcommand(server, [b'cat', b'm']) ... runcommand(server, [b'diff', b'-c', b'.', b'm']) ... runcommand(server, [b'cat', b'm']) *** runcommand cat m $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $ bar *** runcommand diff -c . m *** runcommand cat m $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $ bar $ cd .. #if serve Test that keywords are expanded only in repositories, which enable keyword extension, even if multiple repositories are served in a process $ cat >> fetch-merge/.hg/hgrc < [extensions] > keyword = ! > EOF $ cat > paths.conf < [paths] > enabled=Test > disabled=fetch-merge > EOF $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E error.log --webdir-conf paths.conf $ cat hg.pid >> $DAEMON_PIDS $ get-with-headers.py localhost:$HGPORT 'enabled/file/tip/m/?style=raw' 200 Script output follows $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $ bar $ get-with-headers.py localhost:$HGPORT 'disabled/file/tip/m/?style=raw' 200 Script output follows $Id$ bar (check expansion again, for safety) $ get-with-headers.py localhost:$HGPORT 'enabled/file/tip/m/?style=raw' 200 Script output follows $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $ bar $ killdaemons.py #endif diff --git a/tests/test-shelve.t b/tests/test-shelve.t --- a/tests/test-shelve.t +++ b/tests/test-shelve.t @@ -1,1160 +1,1157 @@ #testcases stripbased phasebased $ cat <> $HGRCPATH > [extensions] > mq = - > shelve = > [defaults] > diff = --nodates --git > qnew = --date '0 0' > [shelve] > maxbackups = 2 > EOF #if phasebased $ cat <> $HGRCPATH > [format] > internal-phase = yes > EOF #endif $ hg init repo $ cd repo $ mkdir a b $ echo a > a/a $ echo b > b/b $ echo c > c $ echo d > d $ echo x > x $ hg addremove -q shelve has a help message $ hg shelve -h hg shelve [OPTION]... [FILE]... save and set aside changes from the working directory Shelving takes files that "hg status" reports as not clean, saves the modifications to a bundle (a shelved change), and reverts the files so that their state in the working directory becomes clean. To restore these changes to the working directory, using "hg unshelve"; this will work even if you switch to a different commit. When no files are specified, "hg shelve" saves all not-clean files. If specific files or directories are named, only changes to those files are shelved. In bare shelve (when no files are specified, without interactive, include and exclude option), shelving remembers information if the working directory was on newly created branch, in other words working directory was on different branch than its first parent. In this situation unshelving restores branch information to the working directory. Each shelved change has a name that makes it easier to find later. The name of a shelved change defaults to being based on the active bookmark, or if there is no active bookmark, the current named branch. To specify a different name, use "--name". To see a list of existing shelved changes, use the "--list" option. For each shelved change, this will print its name, age, and description; use " --patch" or "--stat" for more details. To delete specific shelved changes, use "--delete". To delete all shelved changes, use "--cleanup". - (use 'hg help -e shelve' to show help for the shelve extension) - options ([+] can be repeated): -A --addremove mark new/missing files as added/removed before shelving -u --unknown store unknown files in the shelve --cleanup delete all shelved changes --date DATE shelve with the specified commit date -d --delete delete the named shelved change(s) -e --edit invoke editor on commit messages -k --keep shelve, but keep changes in the working directory -l --list list current shelves -m --message TEXT use text as shelve message -n --name NAME use the given name for the shelved commit -p --patch output patches for changes (provide the names of the shelved changes as positional arguments) -i --interactive interactive mode, only works while creating a shelve --stat output diffstat-style summary of changes (provide the names of the shelved changes as positional arguments) -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns --mq operate on patch repository (some details hidden, use --verbose to show complete help) shelving in an empty repo should be possible (this tests also that editor is not invoked, if '--edit' is not specified) $ HGEDITOR=cat hg shelve shelved as default 0 files updated, 0 files merged, 5 files removed, 0 files unresolved $ hg unshelve unshelving change 'default' $ hg commit -q -m 'initial commit' $ hg shelve nothing changed [1] make sure shelve files were backed up $ ls .hg/shelve-backup default.hg default.patch default.shelve checks to make sure we dont create a directory or hidden file while choosing a new shelve name when we are given a name $ hg shelve -n foo/bar abort: shelved change names can not contain slashes [255] $ hg shelve -n .baz abort: shelved change names can not start with '.' [255] $ hg shelve -n foo\\bar abort: shelved change names can not contain slashes [255] when shelve has to choose itself $ hg branch x/y -q $ hg commit -q -m "Branch commit 0" $ hg shelve nothing changed [1] $ hg branch .x -q $ hg commit -q -m "Branch commit 1" $ hg shelve nothing changed [1] $ hg branch x\\y -q $ hg commit -q -m "Branch commit 2" $ hg shelve nothing changed [1] cleaning the branches made for name checking tests $ hg up default -q $ hg strip e9177275307e+6a6d231f43d+882bae7c62c2 -q create an mq patch - shelving should work fine with a patch applied $ echo n > n $ hg add n $ hg commit n -m second $ hg qnew second.patch shelve a change that we will delete later $ echo a >> a/a $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved set up some more complex changes to shelve $ echo a >> a/a $ hg mv b b.rename moving b/b to b.rename/b $ hg cp c c.copy $ hg status -C M a/a A b.rename/b b/b A c.copy c R b/b the common case - no options or filenames $ hg shelve shelved as default-01 2 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg status -C ensure that our shelved changes exist $ hg shelve -l default-01 (*)* changes to: [mq]: second.patch (glob) default (*)* changes to: [mq]: second.patch (glob) $ hg shelve -l -p default default (*)* changes to: [mq]: second.patch (glob) diff --git a/a/a b/a/a --- a/a/a +++ b/a/a @@ -1,1 +1,2 @@ a +a $ hg shelve --list --addremove abort: options '--list' and '--addremove' may not be used together [255] delete our older shelved change $ hg shelve -d default $ hg qfinish -a -q ensure shelve backups aren't overwritten $ ls .hg/shelve-backup/ default-1.hg default-1.patch default-1.shelve default.hg default.patch default.shelve local edits should not prevent a shelved change from applying $ printf "z\na\n" > a/a $ hg unshelve --keep unshelving change 'default-01' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a/a $ hg revert --all -q $ rm a/a.orig b.rename/b c.copy apply it and make sure our state is as expected (this also tests that same timestamp prevents backups from being removed, even though there are more than 'maxbackups' backups) $ f -t .hg/shelve-backup/default.patch .hg/shelve-backup/default.patch: file $ touch -t 200001010000 .hg/shelve-backup/default.patch $ f -t .hg/shelve-backup/default-1.patch .hg/shelve-backup/default-1.patch: file $ touch -t 200001010000 .hg/shelve-backup/default-1.patch $ hg unshelve unshelving change 'default-01' $ hg status -C M a/a A b.rename/b b/b A c.copy c R b/b $ hg shelve -l (both of default.hg and default-1.hg should be still kept, because it is difficult to decide actual order of them from same timestamp) $ ls .hg/shelve-backup/ default-01.hg default-01.patch default-01.shelve default-1.hg default-1.patch default-1.shelve default.hg default.patch default.shelve $ hg unshelve abort: no shelved changes to apply! [255] $ hg unshelve foo abort: shelved change 'foo' not found [255] named shelves, specific filenames, and "commit messages" should all work (this tests also that editor is invoked, if '--edit' is specified) $ hg status -C M a/a A b.rename/b b/b A c.copy c R b/b $ HGEDITOR=cat hg shelve -q -n wibble -m wat -e a wat HG: Enter commit message. Lines beginning with 'HG:' are removed. HG: Leave message empty to abort commit. HG: -- HG: user: shelve@localhost HG: branch 'default' HG: changed a/a expect "a" to no longer be present, but status otherwise unchanged $ hg status -C A b.rename/b b/b A c.copy c R b/b $ hg shelve -l --stat wibble (*) wat (glob) a/a | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) and now "a/a" should reappear $ cd a $ hg unshelve -q wibble $ cd .. $ hg status -C M a/a A b.rename/b b/b A c.copy c R b/b ensure old shelve backups are being deleted automatically $ ls .hg/shelve-backup/ default-01.hg default-01.patch default-01.shelve wibble.hg wibble.patch wibble.shelve cause unshelving to result in a merge with 'a' conflicting $ hg shelve -q $ echo c>>a/a $ hg commit -m second $ hg tip --template '{files}\n' a/a add an unrelated change that should be preserved $ mkdir foo $ echo foo > foo/foo $ hg add foo/foo force a conflicted merge to occur $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a/a warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ hg status -v M a/a M b.rename/b M c.copy R b/b ? a/a.orig # The repository is in an unfinished *unshelve* state. # Unresolved merge conflicts: # # a/a # # To mark files as resolved: hg resolve --mark FILE # To continue: hg unshelve --continue # To abort: hg unshelve --abort ensure that we have a merge with unresolved conflicts #if phasebased $ hg heads -q --template '{rev}\n' 8 5 $ hg parents -q --template '{rev}\n' 8 5 #endif #if stripbased $ hg heads -q --template '{rev}\n' 5 4 $ hg parents -q --template '{rev}\n' 4 5 #endif $ hg status M a/a M b.rename/b M c.copy R b/b ? a/a.orig $ hg diff diff --git a/a/a b/a/a --- a/a/a +++ b/a/a @@ -1,2 +1,6 @@ a +<<<<<<< shelve: 2377350b6337 - shelve: pending changes temporary commit c +======= +a +>>>>>>> working-copy: a68ec3400638 - shelve: changes to: [mq]: second.patch diff --git a/b/b b/b.rename/b rename from b/b rename to b.rename/b diff --git a/c b/c.copy copy from c copy to c.copy $ hg resolve -l U a/a $ hg shelve abort: unshelve already in progress (use 'hg unshelve --continue' or 'hg unshelve --abort') [255] abort the unshelve and be happy $ hg status M a/a M b.rename/b M c.copy R b/b ? a/a.orig $ hg unshelve -a unshelve of 'default' aborted $ hg heads -q [37]:2e69b451d1ea (re) $ hg parents changeset: [37]:2e69b451d1ea (re) tag: tip parent: 3:509104101065 (?) user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: second $ hg resolve -l $ hg status A foo/foo ? a/a.orig try to continue with no unshelve underway $ hg unshelve -c abort: no unshelve in progress [255] $ hg status A foo/foo ? a/a.orig redo the unshelve to get a conflict $ hg unshelve -q warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] attempt to continue $ hg unshelve -c abort: unresolved conflicts, can't continue (see 'hg resolve', then 'hg unshelve --continue') [255] $ hg revert -r . a/a $ hg resolve -m a/a (no more unresolved files) continue: hg unshelve --continue $ hg commit -m 'commit while unshelve in progress' abort: unshelve already in progress (use 'hg unshelve --continue' or 'hg unshelve --abort') [255] $ hg graft --continue abort: no graft in progress (continue: hg unshelve --continue) [255] $ hg unshelve -c unshelve of 'default' complete ensure the repo is as we hope $ hg parents changeset: [37]:2e69b451d1ea (re) tag: tip parent: 3:509104101065 (?) user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: second $ hg heads -q [37]:2e69b451d1ea (re) $ hg status -C A b.rename/b b/b A c.copy c A foo/foo R b/b ? a/a.orig there should be no shelves left $ hg shelve -l #if execbit ensure that metadata-only changes are shelved $ chmod +x a/a $ hg shelve -q -n execbit a/a $ hg status a/a $ hg unshelve -q execbit $ hg status a/a M a/a $ hg revert a/a #else Dummy shelve op, to keep rev numbers aligned $ echo foo > a/a $ hg shelve -q -n dummy a/a $ hg unshelve -q dummy $ hg revert a/a #endif #if symlink $ rm a/a $ ln -s foo a/a $ hg shelve -q -n symlink a/a $ hg status a/a $ hg unshelve -q -n symlink $ hg status a/a M a/a $ hg revert a/a #else Dummy shelve op, to keep rev numbers aligned $ echo bar > a/a $ hg shelve -q -n dummy a/a $ hg unshelve -q dummy $ hg revert a/a #endif set up another conflict between a commit and a shelved change $ hg revert -q -C -a $ rm a/a.orig b.rename/b c.copy $ echo a >> a/a $ hg shelve -q $ echo x >> a/a $ hg ci -m 'create conflict' $ hg add foo/foo if we resolve a conflict while unshelving, the unshelve should succeed $ hg unshelve --tool :merge-other --keep unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a/a $ hg parents -q (4|13):33f7f61e6c5e (re) $ hg shelve -l default (*)* changes to: second (glob) $ hg status M a/a A foo/foo $ cat a/a a c a $ cat > a/a << EOF > a > c > x > EOF $ HGMERGE=true hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a/a note: unshelved changes already existed in the working copy $ hg parents -q (4|13):33f7f61e6c5e (re) $ hg shelve -l $ hg status A foo/foo $ cat a/a a c x test keep and cleanup $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg shelve --list default (*)* changes to: create conflict (glob) $ hg unshelve -k unshelving change 'default' $ hg shelve --list default (*)* changes to: create conflict (glob) $ hg shelve --cleanup $ hg shelve --list $ hg shelve --cleanup --delete abort: options '--cleanup' and '--delete' may not be used together [255] $ hg shelve --cleanup --patch abort: options '--cleanup' and '--patch' may not be used together [255] $ hg shelve --cleanup --message MESSAGE abort: options '--cleanup' and '--message' may not be used together [255] test bookmarks $ hg bookmark test $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ hg shelve shelved as test 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ hg unshelve unshelving change 'test' $ hg bookmark \* test (4|13):33f7f61e6c5e (re) shelve should still work even if mq is disabled $ hg --config extensions.mq=! shelve shelved as test 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg --config extensions.mq=! shelve --list test (*)* changes to: create conflict (glob) $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ hg --config extensions.mq=! unshelve unshelving change 'test' $ hg bookmark \* test (4|13):33f7f61e6c5e (re) Recreate some conflict again $ hg up -C -r 2e69b451d1ea 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (leaving bookmark test) $ echo y >> a/a $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg up test 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (activating bookmark test) $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ hg unshelve unshelving change 'default' rebasing shelved changes merging a/a warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ hg bookmark test (4|13):33f7f61e6c5e (re) Test that resolving all conflicts in one direction (so that the rebase is a no-op), works (issue4398) $ hg revert -a -r . reverting a/a $ hg resolve -m a/a (no more unresolved files) continue: hg unshelve --continue $ hg unshelve -c note: unshelved changes already existed in the working copy unshelve of 'default' complete $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ hg diff $ hg status ? a/a.orig ? foo/foo $ hg summary parent: (4|13):33f7f61e6c5e tip (re) create conflict branch: default bookmarks: *test commit: 2 unknown (clean) update: (current) phases: 5 draft $ hg shelve --delete --stat abort: options '--delete' and '--stat' may not be used together [255] $ hg shelve --delete --name NAME abort: options '--delete' and '--name' may not be used together [255] Test interactive shelve $ cat <> $HGRCPATH > [ui] > interactive = true > EOF $ echo 'a' >> a/b $ cat a/a >> a/b $ echo 'x' >> a/b $ mv a/b a/a $ echo 'a' >> foo/foo $ hg st M a/a ? a/a.orig ? foo/foo $ cat a/a a a c x x $ cat foo/foo foo a $ hg shelve --interactive --config ui.interactive=false abort: running non-interactively [255] $ hg shelve --interactive << EOF > y > y > n > EOF diff --git a/a/a b/a/a 2 hunks, 2 lines changed examine changes to 'a/a'? [Ynesfdaq?] y @@ -1,3 +1,4 @@ +a a c x record change 1/2 to 'a/a'? [Ynesfdaq?] y @@ -1,3 +2,4 @@ a c x +x record change 2/2 to 'a/a'? [Ynesfdaq?] n shelved as test merging a/a 0 files updated, 1 files merged, 0 files removed, 0 files unresolved $ cat a/a a c x x $ cat foo/foo foo a $ hg st M a/a ? foo/foo $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ hg unshelve unshelving change 'test' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a/a $ hg bookmark \* test (4|13):33f7f61e6c5e (re) $ cat a/a a a c x x shelve --patch and shelve --stat should work with valid shelfnames $ hg up --clean . 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (leaving bookmark test) $ hg shelve --list $ echo 'patch a' > shelf-patch-a $ hg add shelf-patch-a $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo 'patch b' > shelf-patch-b $ hg add shelf-patch-b $ hg shelve shelved as default-01 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg shelve --patch default default-01 default-01 (*)* changes to: create conflict (glob) diff --git a/shelf-patch-b b/shelf-patch-b new file mode 100644 --- /dev/null +++ b/shelf-patch-b @@ -0,0 +1,1 @@ +patch b default (*)* changes to: create conflict (glob) diff --git a/shelf-patch-a b/shelf-patch-a new file mode 100644 --- /dev/null +++ b/shelf-patch-a @@ -0,0 +1,1 @@ +patch a $ hg shelve --stat default default-01 default-01 (*)* changes to: create conflict (glob) shelf-patch-b | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) default (*)* changes to: create conflict (glob) shelf-patch-a | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ hg shelve --patch default default (*)* changes to: create conflict (glob) diff --git a/shelf-patch-a b/shelf-patch-a new file mode 100644 --- /dev/null +++ b/shelf-patch-a @@ -0,0 +1,1 @@ +patch a $ hg shelve --stat default default (*)* changes to: create conflict (glob) shelf-patch-a | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ hg shelve --patch nonexistentshelf abort: cannot find shelf nonexistentshelf [255] $ hg shelve --stat nonexistentshelf abort: cannot find shelf nonexistentshelf [255] $ hg shelve --patch default nonexistentshelf abort: cannot find shelf nonexistentshelf [255] when the user asks for a patch, we assume they want the most recent shelve if they don't provide a shelve name $ hg shelve --patch default-01 (*)* changes to: create conflict (glob) diff --git a/shelf-patch-b b/shelf-patch-b new file mode 100644 --- /dev/null +++ b/shelf-patch-b @@ -0,0 +1,1 @@ +patch b $ cd .. Shelve from general delta repo uses bundle2 on disk -------------------------------------------------- no general delta $ hg clone --pull repo bundle1 --config format.usegeneraldelta=0 requesting all changes adding changesets adding manifests adding file changes added 5 changesets with 8 changes to 6 files new changesets cc01e2b0c59f:33f7f61e6c5e updating to branch default 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd bundle1 $ echo babar > jungle $ hg add jungle $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg debugbundle .hg/shelved/*.hg 330882a04d2ce8487636b1fb292e5beea77fa1e3 $ cd .. with general delta $ hg clone --pull repo bundle2 --config format.usegeneraldelta=1 requesting all changes adding changesets adding manifests adding file changes added 5 changesets with 8 changes to 6 files new changesets cc01e2b0c59f:33f7f61e6c5e updating to branch default 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd bundle2 $ echo babar > jungle $ hg add jungle $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg debugbundle .hg/shelved/*.hg Stream params: {Compression: BZ} changegroup -- {nbchanges: 1, version: 02} (mandatory: True) 330882a04d2ce8487636b1fb292e5beea77fa1e3 Test shelve --keep $ hg unshelve unshelving change 'default' $ hg shelve --keep --list abort: options '--list' and '--keep' may not be used together [255] $ hg shelve --keep --patch abort: options '--patch' and '--keep' may not be used together [255] $ hg shelve --keep --delete abort: options '--delete' and '--keep' may not be used together [255] $ hg shelve --keep shelved as default $ hg diff diff --git a/jungle b/jungle new file mode 100644 --- /dev/null +++ b/jungle @@ -0,0 +1,1 @@ +babar $ cd .. Test visibility of in-memory changes inside transaction to external hook ------------------------------------------------------------------------ $ cd repo $ echo xxxx >> x $ hg commit -m "#5: changes to invoke rebase" $ cat > $TESTTMP/checkvisibility.sh < echo "==== \$1:" > hg parents --template "VISIBLE {rev}:{node|short}\n" > # test that pending changes are hidden > unset HG_PENDING > hg parents --template "ACTUAL {rev}:{node|short}\n" > echo "====" > EOF $ cat >> .hg/hgrc < [defaults] > # to fix hash id of temporary revisions > unshelve = --date '0 0' > EOF "hg unshelve" at REV5 implies steps below: (1) commit changes in the working directory (REV6) (2) unbundle shelved revision (REV7) (3) rebase: merge REV7 into REV6 (REV6 => REV6, REV7) (4) rebase: commit merged revision (REV8) (5) rebase: update to REV6 (REV8 => REV6) (6) update to REV5 (REV6 => REV5) (7) abort transaction == test visibility to external preupdate hook $ cat >> .hg/hgrc < [hooks] > preupdate.visibility = sh $TESTTMP/checkvisibility.sh preupdate > EOF $ echo nnnn >> n $ sh $TESTTMP/checkvisibility.sh before-unshelving ==== before-unshelving: VISIBLE (5|19):703117a2acfb (re) ACTUAL (5|19):703117a2acfb (re) ==== $ hg unshelve --keep default temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes ==== preupdate: VISIBLE (6|20):54c00d20fb3f (re) ACTUAL (5|19):703117a2acfb (re) ==== ==== preupdate: VISIBLE (8|21):8efe6f7537dc (re) ACTUAL (5|19):703117a2acfb (re) ==== ==== preupdate: VISIBLE (6|20):54c00d20fb3f (re) ACTUAL (5|19):703117a2acfb (re) ==== $ cat >> .hg/hgrc < [hooks] > preupdate.visibility = > EOF $ sh $TESTTMP/checkvisibility.sh after-unshelving ==== after-unshelving: VISIBLE (5|19):703117a2acfb (re) ACTUAL (5|19):703117a2acfb (re) ==== == test visibility to external update hook $ hg update -q -C 703117a2acfb $ cat >> .hg/hgrc < [hooks] > update.visibility = sh $TESTTMP/checkvisibility.sh update > EOF $ echo nnnn >> n $ sh $TESTTMP/checkvisibility.sh before-unshelving ==== before-unshelving: VISIBLE (5|19):703117a2acfb (re) ACTUAL (5|19):703117a2acfb (re) ==== $ hg unshelve --keep default temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes ==== update: VISIBLE (6|20):54c00d20fb3f (re) VISIBLE 1?7:492ed9d705e5 (re) ACTUAL (5|19):703117a2acfb (re) ==== ==== update: VISIBLE (6|20):54c00d20fb3f (re) ACTUAL (5|19):703117a2acfb (re) ==== ==== update: VISIBLE (5|19):703117a2acfb (re) ACTUAL (5|19):703117a2acfb (re) ==== $ cat >> .hg/hgrc < [hooks] > update.visibility = > EOF $ sh $TESTTMP/checkvisibility.sh after-unshelving ==== after-unshelving: VISIBLE (5|19):703117a2acfb (re) ACTUAL (5|19):703117a2acfb (re) ==== $ cd .. Keep active bookmark while (un)shelving even on shared repo (issue4940) ----------------------------------------------------------------------- $ cat <> $HGRCPATH > [extensions] > share = > EOF $ hg bookmarks -R repo test (4|13):33f7f61e6c5e (re) $ hg share -B repo share updating working directory 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd share $ hg bookmarks test (4|13):33f7f61e6c5e (re) $ hg bookmarks foo $ hg bookmarks \* foo (5|19):703117a2acfb (re) test (4|13):33f7f61e6c5e (re) $ echo x >> x $ hg shelve shelved as foo 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bookmarks \* foo (5|19):703117a2acfb (re) test (4|13):33f7f61e6c5e (re) $ hg unshelve unshelving change 'foo' $ hg bookmarks \* foo (5|19):703117a2acfb (re) test (4|13):33f7f61e6c5e (re) $ cd .. Abort unshelve while merging (issue5123) ---------------------------------------- $ hg init issue5123 $ cd issue5123 $ echo > a $ hg ci -Am a adding a $ hg co null 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo > b $ hg ci -Am b adding b created new head $ echo > c $ hg add c $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg co 1 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) -- successful merge with two parents $ hg log -G @ changeset: 1:406bf70c274f tag: tip parent: -1:000000000000 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: b @ changeset: 0:ada8c9eb8252 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: a -- trying to pull in the shelve bits -- unshelve should abort otherwise, it'll eat my second parent. $ hg unshelve abort: outstanding uncommitted merge (use 'hg commit' or 'hg merge --abort') [255] $ cd .. diff --git a/tests/test-shelve2.t b/tests/test-shelve2.t --- a/tests/test-shelve2.t +++ b/tests/test-shelve2.t @@ -1,850 +1,849 @@ #testcases stripbased phasebased $ cat <> $HGRCPATH > [extensions] > mq = - > shelve = > [defaults] > diff = --nodates --git > qnew = --date '0 0' > [shelve] > maxbackups = 2 > EOF #if phasebased $ cat <> $HGRCPATH > [format] > internal-phase = yes > EOF #endif shelve should leave dirstate clean (issue4055) $ hg init shelverebase $ cd shelverebase $ printf 'x\ny\n' > x $ echo z > z $ hg commit -Aqm xy $ echo z >> x $ hg commit -Aqm z $ hg up 5c4c67fb7dce 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ printf 'a\nx\ny\nz\n' > x $ hg commit -Aqm xyz $ echo c >> z $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg rebase -d 6c103be8f4e4 --config extensions.rebase= rebasing 2:323bfa07f744 "xyz"( \(tip\))? (re) merging x saved backup bundle to \$TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-(78114325|7ae538ef)-rebase.hg (re) $ hg unshelve unshelving change 'default' rebasing shelved changes $ hg status M z $ cd .. shelve should only unshelve pending changes (issue4068) $ hg init onlypendingchanges $ cd onlypendingchanges $ touch a $ hg ci -Aqm a $ touch b $ hg ci -Aqm b $ hg up -q 3903775176ed $ touch c $ hg ci -Aqm c $ touch d $ hg add d $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg up -q 0e067c57feba $ hg unshelve unshelving change 'default' rebasing shelved changes $ hg status A d unshelve should work on an ancestor of the original commit $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg up 3903775176ed 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg unshelve unshelving change 'default' rebasing shelved changes $ hg status A d test bug 4073 we need to enable obsolete markers for it $ cat >> $HGRCPATH << EOF > [experimental] > evolution.createmarkers=True > EOF $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg debugobsolete `hg log -r 0e067c57feba -T '{node}'` obsoleted 1 changesets $ hg unshelve unshelving change 'default' unshelve should leave unknown files alone (issue4113) $ echo e > e $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg status ? e $ hg unshelve unshelving change 'default' $ hg status A d ? e $ cat e e unshelve should keep a copy of unknown files $ hg add e $ hg shelve shelved as default 0 files updated, 0 files merged, 2 files removed, 0 files unresolved $ echo z > e $ hg unshelve unshelving change 'default' $ cat e e $ cat e.orig z $ rm e.orig restores backup of unknown file to right directory $ hg shelve shelved as default 0 files updated, 0 files merged, 2 files removed, 0 files unresolved $ echo z > e $ mkdir dir $ hg unshelve --cwd dir unshelving change 'default' $ rmdir dir $ cat e e $ cat e.orig z unshelve and conflicts with tracked and untracked files preparing: $ rm -f *.orig $ hg ci -qm 'commit stuff' $ hg phase -p null: no other changes - no merge: $ echo f > f $ hg add f $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo g > f $ hg unshelve unshelving change 'default' $ hg st A f ? f.orig $ cat f f $ cat f.orig g other uncommitted changes - merge: $ hg st A f ? f.orig $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved #if repobundlerepo $ hg log -G --template '{rev} {desc|firstline} {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()' --hidden o [48] changes to: commit stuff shelve@localhost (re) | ~ #endif $ hg log -G --template '{rev} {desc|firstline} {author}' @ [37] commit stuff test (re) | | o 2 c test |/ o 0 a test $ mv f.orig f $ echo 1 > a $ hg unshelve --date '1073741824 0' unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging f warning: conflicts while merging f! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] #if phasebased $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}' @ 9 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000 | | @ 8 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000 |/ o 7 commit stuff test 1970-01-01 00:00 +0000 | | o 2 c test 1970-01-01 00:00 +0000 |/ o 0 a test 1970-01-01 00:00 +0000 #endif #if stripbased $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}' @ 5 changes to: commit stuff shelve@localhost 1970-01-01 00:00 +0000 | | @ 4 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000 |/ o 3 commit stuff test 1970-01-01 00:00 +0000 | | o 2 c test 1970-01-01 00:00 +0000 |/ o 0 a test 1970-01-01 00:00 +0000 #endif $ hg st M f ? f.orig $ cat f <<<<<<< shelve: d44eae5c3d33 - shelve: pending changes temporary commit g ======= f >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff $ cat f.orig g $ hg unshelve --abort -t false tool option will be ignored unshelve of 'default' aborted $ hg st M a ? f.orig $ cat f.orig g $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes $ hg st M a A f ? f.orig other committed changes - merge: $ hg shelve f shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg ci a -m 'intermediate other change' $ mv f.orig f $ hg unshelve unshelving change 'default' rebasing shelved changes merging f warning: conflicts while merging f! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ hg st M f ? f.orig $ cat f <<<<<<< shelve: 6b563750f973 - test: intermediate other change g ======= f >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff $ cat f.orig g $ hg unshelve --abort unshelve of 'default' aborted $ hg st ? f.orig $ cat f.orig g $ hg shelve --delete default $ cd .. you shouldn't be able to ask for the patch/stats of the most recent shelve if there are no shelves $ hg init noshelves $ cd noshelves $ hg shelve --patch abort: there are no shelves to show [255] $ hg shelve --stat abort: there are no shelves to show [255] $ cd .. test .orig files go where the user wants them to --------------------------------------------------------------- $ hg init salvage $ cd salvage $ echo 'content' > root $ hg commit -A -m 'root' -q $ echo '' > root $ hg shelve -q $ echo 'contADDent' > root $ hg unshelve -q --config 'ui.origbackuppath=.hg/origbackups' warning: conflicts while merging root! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ ls .hg/origbackups root $ rm -rf .hg/origbackups test Abort unshelve always gets user out of the unshelved state --------------------------------------------------------------- with a corrupted shelve state file $ sed 's/ae8c668541e8/123456789012/' .hg/shelvedstate > ../corrupt-shelvedstate $ mv ../corrupt-shelvedstate .hg/shelvestate $ hg unshelve --abort 2>&1 | grep 'aborted' unshelve of 'default' aborted $ hg summary parent: 0:ae8c668541e8 tip root branch: default commit: 1 modified update: (current) phases: 1 draft $ hg up -C . 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. Shelve and unshelve unknown files. For the purposes of unshelve, a shelved unknown file is the same as a shelved added file, except that it will be in unknown state after unshelve if and only if it was either absent or unknown before the unshelve operation. $ hg init unknowns $ cd unknowns The simplest case is if I simply have an unknown file that I shelve and unshelve $ echo unknown > unknown $ hg status ? unknown $ hg shelve --unknown shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg status $ hg unshelve unshelving change 'default' $ hg status ? unknown $ rm unknown If I shelve, add the file, and unshelve, does it stay added? $ echo unknown > unknown $ hg shelve -u shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg status $ touch unknown $ hg add unknown $ hg status A unknown $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging unknown $ hg status A unknown $ hg forget unknown $ rm unknown And if I shelve, commit, then unshelve, does it become modified? $ echo unknown > unknown $ hg shelve -u shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg status $ touch unknown $ hg add unknown $ hg commit -qm "Add unknown" $ hg status $ hg unshelve unshelving change 'default' rebasing shelved changes merging unknown $ hg status M unknown $ hg remove --force unknown $ hg commit -qm "Remove unknown" $ cd .. We expects that non-bare shelve keeps newly created branch in working directory. $ hg init shelve-preserve-new-branch $ cd shelve-preserve-new-branch $ echo "a" >> a $ hg add a $ echo "b" >> b $ hg add b $ hg commit -m "ab" $ echo "aa" >> a $ echo "bb" >> b $ hg branch new-branch marked working directory as branch new-branch (branches are permanent and global, did you want a bookmark?) $ hg status M a M b $ hg branch new-branch $ hg shelve a shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch new-branch $ hg status M b $ touch "c" >> c $ hg add c $ hg status M b A c $ hg shelve --exclude c shelved as default-01 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch new-branch $ hg status A c $ hg shelve --include c shelved as default-02 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg branch new-branch $ hg status $ echo "d" >> d $ hg add d $ hg status A d We expect that bare-shelve will not keep branch in current working directory. $ hg shelve shelved as default-03 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg branch default $ cd .. When i shelve commit on newly created branch i expect that after unshelve newly created branch will be preserved. $ hg init shelve_on_new_branch_simple $ cd shelve_on_new_branch_simple $ echo "aaa" >> a $ hg commit -A -m "a" adding a $ hg branch default $ hg branch test marked working directory as branch test (branches are permanent and global, did you want a bookmark?) $ echo "bbb" >> a $ hg status M a $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch default $ echo "bbb" >> b $ hg status ? b $ hg unshelve unshelving change 'default' marked working directory as branch test $ hg status M a ? b $ hg branch test $ cd .. When i shelve commit on newly created branch, make some changes, unshelve it and running into merge conflicts i expect that after fixing them and running unshelve --continue newly created branch will be preserved. $ hg init shelve_on_new_branch_conflict $ cd shelve_on_new_branch_conflict $ echo "aaa" >> a $ hg commit -A -m "a" adding a $ hg branch default $ hg branch test marked working directory as branch test (branches are permanent and global, did you want a bookmark?) $ echo "bbb" >> a $ hg status M a $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch default $ echo "ccc" >> a $ hg status M a $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ echo "aaabbbccc" > a $ rm a.orig $ hg resolve --mark a (no more unresolved files) continue: hg unshelve --continue $ hg unshelve --continue marked working directory as branch test unshelve of 'default' complete $ cat a aaabbbccc $ hg status M a $ hg branch test $ hg commit -m "test-commit" When i shelve on test branch, update to default branch and unshelve i expect that it will not preserve previous test branch. $ echo "xxx" > b $ hg add b $ hg shelve shelved as test 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg update -r 7049e48789d7 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg unshelve unshelving change 'test' rebasing shelved changes $ hg status A b $ hg branch default $ cd .. When i unshelve resulting in merge conflicts and makes saved file shelvedstate looks like in previous versions in mercurial(without restore branch information in 7th line) i expect that after resolving conflicts and successfully running 'shelve --continue' the branch information won't be restored and branch will be unchanged. shelve on new branch, conflict with previous shelvedstate $ hg init conflict $ cd conflict $ echo "aaa" >> a $ hg commit -A -m "a" adding a $ hg branch default $ hg branch test marked working directory as branch test (branches are permanent and global, did you want a bookmark?) $ echo "bbb" >> a $ hg status M a $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch default $ echo "ccc" >> a $ hg status M a $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] Removing restore branch information from shelvedstate file(making it looks like in previous versions) and running unshelve --continue $ cp .hg/shelvedstate .hg/shelvedstate_old $ cat .hg/shelvedstate_old | grep -v 'branchtorestore' > .hg/shelvedstate $ echo "aaabbbccc" > a $ rm a.orig $ hg resolve --mark a (no more unresolved files) continue: hg unshelve --continue $ hg unshelve --continue unshelve of 'default' complete $ cat a aaabbbccc $ hg status M a $ hg branch default $ cd .. On non bare shelve the branch information shouldn't be restored $ hg init bare_shelve_on_new_branch $ cd bare_shelve_on_new_branch $ echo "aaa" >> a $ hg commit -A -m "a" adding a $ hg branch default $ hg branch test marked working directory as branch test (branches are permanent and global, did you want a bookmark?) $ echo "bbb" >> a $ hg status M a $ hg shelve a shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch test $ hg branch default marked working directory as branch default (branches are permanent and global, did you want a bookmark?) $ echo "bbb" >> b $ hg status ? b $ hg unshelve unshelving change 'default' $ hg status M a ? b $ hg branch default $ cd .. Prepare unshelve with a corrupted shelvedstate $ hg init r1 && cd r1 $ echo text1 > file && hg add file $ hg shelve shelved as default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo text2 > file && hg ci -Am text1 adding file $ hg unshelve unshelving change 'default' rebasing shelved changes merging file warning: conflicts while merging file! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ echo somethingsomething > .hg/shelvedstate Unshelve --continue fails with appropriate message if shelvedstate is corrupted $ hg unshelve --continue abort: corrupted shelved state file (please run hg unshelve --abort to abort unshelve operation) [255] Unshelve --abort works with a corrupted shelvedstate $ hg unshelve --abort could not read shelved state file, your working copy may be in an unexpected state please update to some commit Unshelve --abort fails with appropriate message if there's no unshelve in progress $ hg unshelve --abort abort: no unshelve in progress [255] $ cd .. Unshelve respects --keep even if user intervention is needed $ hg init unshelvekeep && cd unshelvekeep $ echo 1 > file && hg ci -Am 1 adding file $ echo 2 >> file $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 3 >> file && hg ci -Am 13 $ hg shelve --list default (*s ago) * changes to: 1 (glob) $ hg unshelve --keep unshelving change 'default' rebasing shelved changes merging file warning: conflicts while merging file! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ hg resolve --mark file (no more unresolved files) continue: hg unshelve --continue $ hg unshelve --continue unshelve of 'default' complete $ hg shelve --list default (*s ago) * changes to: 1 (glob) $ cd .. Unshelving when there are deleted files does not crash (issue4176) $ hg init unshelve-deleted-file && cd unshelve-deleted-file $ echo a > a && echo b > b && hg ci -Am ab adding a adding b $ echo aa > a && hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ rm b $ hg st ! b $ hg unshelve unshelving change 'default' $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ rm a && echo b > b $ hg st ! a $ hg unshelve unshelving change 'default' abort: shelved change touches missing files (run hg status to see which files are missing) [255] $ hg st ! a $ cd .. New versions of Mercurial know how to read onld shelvedstate files $ hg init oldshelvedstate $ cd oldshelvedstate $ echo root > root && hg ci -Am root adding root $ echo 1 > a $ hg add a $ hg shelve --name ashelve shelved as ashelve 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo 2 > a $ hg ci -Am a adding a $ hg unshelve unshelving change 'ashelve' rebasing shelved changes merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] putting v1 shelvedstate file in place of a created v2 $ cat << EOF > .hg/shelvedstate > 1 > ashelve > 8b058dae057a5a78f393f4535d9e363dd5efac9d > 8b058dae057a5a78f393f4535d9e363dd5efac9d > 8b058dae057a5a78f393f4535d9e363dd5efac9d f543b27db2cdb41737e2e0008dc524c471da1446 > f543b27db2cdb41737e2e0008dc524c471da1446 > > nokeep > :no-active-bookmark > EOF $ echo 1 > a $ hg resolve --mark a (no more unresolved files) continue: hg unshelve --continue mercurial does not crash $ hg unshelve --continue unshelve of 'ashelve' complete #if phasebased Unshelve with some metadata file missing ---------------------------------------- $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 3 > a Test with the `.shelve` missing, but the changeset still in the repo (non-natural case) $ rm .hg/shelved/default.shelve $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ hg unshelve --abort unshelve of 'default' aborted Unshelve without .shelve metadata (can happen when upgrading a repository with old shelve) $ cat .hg/shelved/default.shelve node=82e0cb9893247d12667017593ce1e5655860f1ac $ hg strip --hidden --rev 82e0cb989324 --no-backup $ rm .hg/shelved/default.shelve $ hg unshelve unshelving change 'default' temporarily committing pending changes (restore with 'hg unshelve --abort') rebasing shelved changes merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [1] $ cat .hg/shelved/default.shelve node=82e0cb9893247d12667017593ce1e5655860f1ac $ hg unshelve --abort unshelve of 'default' aborted #endif $ cd .. diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t --- a/tests/test-treemanifest.t +++ b/tests/test-treemanifest.t @@ -1,900 +1,900 @@ $ cat << EOF >> $HGRCPATH > [ui] > ssh="$PYTHON" "$TESTDIR/dummyssh" > EOF Set up repo $ hg --config experimental.treemanifest=True init repo $ cd repo Requirements get set on init $ grep treemanifest .hg/requires treemanifest Without directories, looks like any other repo $ echo 0 > a $ echo 0 > b $ hg ci -Aqm initial $ hg debugdata -m 0 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) Submanifest is stored in separate revlog $ mkdir dir1 $ echo 1 > dir1/a $ echo 1 > dir1/b $ echo 1 > e $ hg ci -Aqm 'add dir1' $ hg debugdata -m 1 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc) dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc) e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) $ hg debugdata --dir dir1 0 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) Can add nested directories $ mkdir dir1/dir1 $ echo 2 > dir1/dir1/a $ echo 2 > dir1/dir1/b $ mkdir dir1/dir2 $ echo 2 > dir1/dir2/a $ echo 2 > dir1/dir2/b $ hg ci -Aqm 'add dir1/dir1' $ hg files -r . a b dir1/a dir1/b dir1/dir1/a dir1/dir1/b dir1/dir2/a dir1/dir2/b e The manifest command works $ hg manifest a b dir1/a dir1/b dir1/dir1/a dir1/dir1/b dir1/dir2/a dir1/dir2/b e Revision is not created for unchanged directory $ mkdir dir2 $ echo 3 > dir2/a $ hg add dir2 adding dir2/a $ hg debugindex --dir dir1 > before $ hg ci -qm 'add dir2' $ hg debugindex --dir dir1 > after $ diff before after $ rm before after Removing directory does not create an revlog entry $ hg rm dir1/dir1 removing dir1/dir1/a removing dir1/dir1/b $ hg debugindex --dir dir1/dir1 > before $ hg ci -qm 'remove dir1/dir1' $ hg debugindex --dir dir1/dir1 > after $ diff before after $ rm before after Check that hg files (calls treemanifest.walk()) works without loading all directory revlogs $ hg co 'desc("add dir2")' 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup $ hg files -r . dir1 dir1/a dir1/b dir1/dir1/a dir1/dir1/b dir1/dir2/a dir1/dir2/b Check that status between revisions works (calls treemanifest.matches()) without loading all directory revlogs $ hg status --rev 'desc("add dir1")' --rev . dir1 A dir1/dir1/a A dir1/dir1/b A dir1/dir2/a A dir1/dir2/b $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2 Merge creates 2-parent revision of directory revlog $ echo 5 > dir1/a $ hg ci -Aqm 'modify dir1/a' $ hg co '.^' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 6 > dir1/b $ hg ci -Aqm 'modify dir1/b' $ hg merge 'desc("modify dir1/a")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m 'conflict-free merge involving dir1/' $ cat dir1/a 5 $ cat dir1/b 6 $ hg debugindex --dir dir1 rev linkrev nodeid p1 p2 0 1 8b3ffd73f901 000000000000 000000000000 1 2 68e9d057c5a8 8b3ffd73f901 000000000000 2 4 4698198d2624 68e9d057c5a8 000000000000 3 5 44844058ccce 68e9d057c5a8 000000000000 4 6 bf3d9b744927 68e9d057c5a8 000000000000 5 7 dde7c0af2a03 bf3d9b744927 44844058ccce Merge keeping directory from parent 1 does not create revlog entry. (Note that dir1's manifest does change, but only because dir1/a's filelog changes.) $ hg co 'desc("add dir2")' 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 8 > dir2/a $ hg ci -m 'modify dir2/a' created new head $ hg debugindex --dir dir2 > before $ hg merge 'desc("modify dir1/a")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg revert -r 'desc("modify dir2/a")' . reverting dir1/a $ hg ci -m 'merge, keeping parent 1' $ hg debugindex --dir dir2 > after $ diff before after $ rm before after Merge keeping directory from parent 2 does not create revlog entry. (Note that dir2's manifest does change, but only because dir2/a's filelog changes.) $ hg co 'desc("modify dir2/a")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg debugindex --dir dir1 > before $ hg merge 'desc("modify dir1/a")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg revert -r 'desc("modify dir1/a")' . reverting dir2/a $ hg ci -m 'merge, keeping parent 2' created new head $ hg debugindex --dir dir1 > after $ diff before after $ rm before after Create flat source repo for tests with mixed flat/tree manifests $ cd .. $ hg init repo-flat $ cd repo-flat Create a few commits with flat manifest $ echo 0 > a $ echo 0 > b $ echo 0 > e $ for d in dir1 dir1/dir1 dir1/dir2 dir2 > do > mkdir $d > echo 0 > $d/a > echo 0 > $d/b > done $ hg ci -Aqm initial $ echo 1 > a $ echo 1 > dir1/a $ echo 1 > dir1/dir1/a $ hg ci -Aqm 'modify on branch 1' $ hg co 0 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 2 > b $ echo 2 > dir1/b $ echo 2 > dir1/dir1/b $ hg ci -Aqm 'modify on branch 2' $ hg merge 1 3 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m 'merge of flat manifests to new flat manifest' $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log $ cat hg.pid >> $DAEMON_PIDS Create clone with tree manifests enabled $ cd .. $ hg clone --config experimental.treemanifest=1 \ > http://localhost:$HGPORT repo-mixed -r 1 adding changesets adding manifests adding file changes added 2 changesets with 14 changes to 11 files new changesets 5b02a3e8db7e:581ef6037d8b updating to branch default 11 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd repo-mixed $ test -d .hg/store/meta [1] $ grep treemanifest .hg/requires treemanifest Should be possible to push updates from flat to tree manifest repo $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed pushing to ssh://user@dummy/repo-mixed searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 2 changesets with 3 changes to 3 files Commit should store revlog per directory $ hg co 1 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo 3 > a $ echo 3 > dir1/a $ echo 3 > dir1/dir1/a $ hg ci -m 'first tree' created new head $ find .hg/store/meta | sort .hg/store/meta .hg/store/meta/dir1 .hg/store/meta/dir1/00manifest.i .hg/store/meta/dir1/dir1 .hg/store/meta/dir1/dir1/00manifest.i .hg/store/meta/dir1/dir2 .hg/store/meta/dir1/dir2/00manifest.i .hg/store/meta/dir2 .hg/store/meta/dir2/00manifest.i Merge of two trees $ hg co 2 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 1 3 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m 'merge of flat manifests to new tree manifest' created new head $ hg diff -r 3 Parent of tree root manifest should be flat manifest, and two for merge $ hg debugindex -m rev linkrev nodeid p1 p2 0 0 40536115ed9e 000000000000 000000000000 1 1 f3376063c255 40536115ed9e 000000000000 2 2 5d9b9da231a2 40536115ed9e 000000000000 3 3 d17d663cbd8a 5d9b9da231a2 f3376063c255 4 4 51e32a8c60ee f3376063c255 000000000000 5 5 cc5baa78b230 5d9b9da231a2 f3376063c255 Status across flat/tree boundary should work $ hg status --rev '.^' --rev . M a M dir1/a M dir1/dir1/a Turning off treemanifest config has no effect $ hg debugindex --dir dir1 rev linkrev nodeid p1 p2 0 4 064927a0648a 000000000000 000000000000 1 5 25ecb8cb8618 000000000000 000000000000 $ echo 2 > dir1/a $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a' $ hg debugindex --dir dir1 rev linkrev nodeid p1 p2 0 4 064927a0648a 000000000000 000000000000 1 5 25ecb8cb8618 000000000000 000000000000 2 6 5b16163a30c6 25ecb8cb8618 000000000000 Stripping and recovering changes should work $ hg st --change tip M dir1/a $ hg --config extensions.strip= strip tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/51cfd7b1e13b-78a2f3ed-backup.hg $ hg debugindex --dir dir1 rev linkrev nodeid p1 p2 0 4 064927a0648a 000000000000 000000000000 1 5 25ecb8cb8618 000000000000 000000000000 #if repobundlerepo $ hg incoming .hg/strip-backup/* comparing with .hg/strip-backup/*-backup.hg (glob) searching for changes changeset: 6:51cfd7b1e13b tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: modify dir1/a #endif $ hg unbundle .hg/strip-backup/* adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files new changesets 51cfd7b1e13b (1 drafts) (run 'hg update' to get a working copy) $ hg --config extensions.strip= strip tip saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/*-backup.hg (glob) $ hg unbundle -q .hg/strip-backup/* $ hg debugindex --dir dir1 rev linkrev nodeid p1 p2 0 4 064927a0648a 000000000000 000000000000 1 5 25ecb8cb8618 000000000000 000000000000 2 6 5b16163a30c6 25ecb8cb8618 000000000000 $ hg st --change tip M dir1/a Shelving and unshelving should work $ echo foo >> dir1/a - $ hg --config extensions.shelve= shelve + $ hg shelve shelved as default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --config extensions.shelve= unshelve + $ hg unshelve unshelving change 'default' $ hg diff --nodates diff -r 708a273da119 dir1/a --- a/dir1/a +++ b/dir1/a @@ -1,1 +1,2 @@ 1 +foo Pushing from treemanifest repo to an empty repo makes that a treemanifest repo $ cd .. $ hg init empty-repo $ cat << EOF >> empty-repo/.hg/hgrc > [experimental] > changegroup3=yes > EOF $ grep treemanifest empty-repo/.hg/requires [1] $ hg push -R repo -r 0 empty-repo pushing to empty-repo searching for changes adding changesets adding manifests adding file changes added 1 changesets with 2 changes to 2 files $ grep treemanifest empty-repo/.hg/requires treemanifest Pushing to an empty repo works $ hg --config experimental.treemanifest=1 init clone $ grep treemanifest clone/.hg/requires treemanifest $ hg push -R repo clone pushing to clone searching for changes adding changesets adding manifests adding file changes added 11 changesets with 15 changes to 10 files (+3 heads) $ grep treemanifest clone/.hg/requires treemanifest $ hg -R clone verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 11 changesets with 15 changes to 10 files Create deeper repo with tree manifests. $ hg --config experimental.treemanifest=True init deeprepo $ cd deeprepo $ mkdir .A $ mkdir b $ mkdir b/bar $ mkdir b/bar/orange $ mkdir b/bar/orange/fly $ mkdir b/foo $ mkdir b/foo/apple $ mkdir b/foo/apple/bees $ touch .A/one.txt $ touch .A/two.txt $ touch b/bar/fruits.txt $ touch b/bar/orange/fly/gnat.py $ touch b/bar/orange/fly/housefly.txt $ touch b/foo/apple/bees/flower.py $ touch c.txt $ touch d.py $ hg ci -Aqm 'initial' $ echo >> .A/one.txt $ echo >> .A/two.txt $ echo >> b/bar/fruits.txt $ echo >> b/bar/orange/fly/gnat.py $ echo >> b/bar/orange/fly/housefly.txt $ echo >> b/foo/apple/bees/flower.py $ echo >> c.txt $ echo >> d.py $ hg ci -Aqm 'second' We'll see that visitdir works by removing some treemanifest revlogs and running the files command with various parameters. Test files from the root. $ hg files -r . .A/one.txt .A/two.txt b/bar/fruits.txt b/bar/orange/fly/gnat.py b/bar/orange/fly/housefly.txt b/foo/apple/bees/flower.py c.txt d.py Excludes with a glob should not exclude everything from the glob's root $ hg files -r . -X 'b/fo?' b b/bar/fruits.txt b/bar/orange/fly/gnat.py b/bar/orange/fly/housefly.txt $ cp -R .hg/store .hg/store-copy Test files for a subdirectory. #if reporevlogstore $ rm -r .hg/store/meta/~2e_a #endif #if reposimplestore $ rm -r .hg/store/meta/._a #endif $ hg files -r . b b/bar/fruits.txt b/bar/orange/fly/gnat.py b/bar/orange/fly/housefly.txt b/foo/apple/bees/flower.py $ hg diff -r '.^' -r . --stat b b/bar/fruits.txt | 1 + b/bar/orange/fly/gnat.py | 1 + b/bar/orange/fly/housefly.txt | 1 + b/foo/apple/bees/flower.py | 1 + 4 files changed, 4 insertions(+), 0 deletions(-) $ cp -R .hg/store-copy/. .hg/store Test files with just includes and excludes. #if reporevlogstore $ rm -r .hg/store/meta/~2e_a #endif #if reposimplestore $ rm -r .hg/store/meta/._a #endif $ rm -r .hg/store/meta/b/bar/orange/fly $ rm -r .hg/store/meta/b/foo/apple/bees $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees b/bar/fruits.txt $ hg diff -r '.^' -r . --stat -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees b/bar/fruits.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ cp -R .hg/store-copy/. .hg/store Test files for a subdirectory, excluding a directory within it. #if reporevlogstore $ rm -r .hg/store/meta/~2e_a #endif #if reposimplestore $ rm -r .hg/store/meta/._a #endif $ rm -r .hg/store/meta/b/foo $ hg files -r . -X path:b/foo b b/bar/fruits.txt b/bar/orange/fly/gnat.py b/bar/orange/fly/housefly.txt $ hg diff -r '.^' -r . --stat -X path:b/foo b b/bar/fruits.txt | 1 + b/bar/orange/fly/gnat.py | 1 + b/bar/orange/fly/housefly.txt | 1 + 3 files changed, 3 insertions(+), 0 deletions(-) $ cp -R .hg/store-copy/. .hg/store Test files for a sub directory, including only a directory within it, and including an unrelated directory. #if reporevlogstore $ rm -r .hg/store/meta/~2e_a #endif #if reposimplestore $ rm -r .hg/store/meta/._a #endif $ rm -r .hg/store/meta/b/foo $ hg files -r . -I path:b/bar/orange -I path:a b b/bar/orange/fly/gnat.py b/bar/orange/fly/housefly.txt $ hg diff -r '.^' -r . --stat -I path:b/bar/orange -I path:a b b/bar/orange/fly/gnat.py | 1 + b/bar/orange/fly/housefly.txt | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) $ cp -R .hg/store-copy/. .hg/store Test files for a pattern, including a directory, and excluding a directory within that. #if reporevlogstore $ rm -r .hg/store/meta/~2e_a #endif #if reposimplestore $ rm -r .hg/store/meta/._a #endif $ rm -r .hg/store/meta/b/foo $ rm -r .hg/store/meta/b/bar/orange $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange b/bar/fruits.txt $ hg diff -r '.^' -r . --stat glob:**.txt -I path:b/bar -X path:b/bar/orange b/bar/fruits.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ cp -R .hg/store-copy/. .hg/store Add some more changes to the deep repo $ echo narf >> b/bar/fruits.txt $ hg ci -m narf $ echo troz >> b/bar/orange/fly/gnat.py $ hg ci -m troz Verify works $ hg verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files #if repofncache Dirlogs are included in fncache $ grep meta/.A/00manifest.i .hg/store/fncache meta/.A/00manifest.i Rebuilt fncache includes dirlogs $ rm .hg/store/fncache $ hg debugrebuildfncache adding data/.A/one.txt.i adding data/.A/two.txt.i adding data/b/bar/fruits.txt.i adding data/b/bar/orange/fly/gnat.py.i adding data/b/bar/orange/fly/housefly.txt.i adding data/b/foo/apple/bees/flower.py.i adding data/c.txt.i adding data/d.py.i adding meta/.A/00manifest.i adding meta/b/00manifest.i adding meta/b/bar/00manifest.i adding meta/b/bar/orange/00manifest.i adding meta/b/bar/orange/fly/00manifest.i adding meta/b/foo/00manifest.i adding meta/b/foo/apple/00manifest.i adding meta/b/foo/apple/bees/00manifest.i 16 items added, 0 removed from fncache #endif Finish first server $ killdaemons.py Back up the recently added revlogs $ cp -R .hg/store .hg/store-newcopy Verify reports missing dirlog $ rm .hg/store/meta/b/00manifest.* $ hg verify checking changesets checking manifests checking directory manifests 0: empty or missing b/ b/@0: parent-directory manifest refers to unknown revision 67688a370455 b/@1: parent-directory manifest refers to unknown revision f065da70369e b/@2: parent-directory manifest refers to unknown revision ac0d30948e0b b/@3: parent-directory manifest refers to unknown revision 367152e6af28 warning: orphan data file 'meta/b/bar/00manifest.i' (reporevlogstore !) warning: orphan data file 'meta/b/bar/orange/00manifest.i' (reporevlogstore !) warning: orphan data file 'meta/b/bar/orange/fly/00manifest.i' (reporevlogstore !) warning: orphan data file 'meta/b/foo/00manifest.i' (reporevlogstore !) warning: orphan data file 'meta/b/foo/apple/00manifest.i' (reporevlogstore !) warning: orphan data file 'meta/b/foo/apple/bees/00manifest.i' (reporevlogstore !) crosschecking files in changesets and manifests b/bar/fruits.txt@0: in changeset but not in manifest b/bar/orange/fly/gnat.py@0: in changeset but not in manifest b/bar/orange/fly/housefly.txt@0: in changeset but not in manifest b/foo/apple/bees/flower.py@0: in changeset but not in manifest checking files checked 4 changesets with 18 changes to 8 files 6 warnings encountered! (reporevlogstore !) 9 integrity errors encountered! (first damaged changeset appears to be 0) [1] $ cp -R .hg/store-newcopy/. .hg/store Verify reports missing dirlog entry $ mv -f .hg/store-copy/meta/b/00manifest.* .hg/store/meta/b/ $ hg verify checking changesets checking manifests checking directory manifests b/@2: parent-directory manifest refers to unknown revision ac0d30948e0b b/@3: parent-directory manifest refers to unknown revision 367152e6af28 b/bar/@?: rev 2 points to unexpected changeset 2 b/bar/@?: 44d7e1146e0d not in parent-directory manifest b/bar/@?: rev 3 points to unexpected changeset 3 b/bar/@?: 70b10c6b17b7 not in parent-directory manifest b/bar/orange/@?: rev 2 points to unexpected changeset 3 (expected None) b/bar/orange/fly/@?: rev 2 points to unexpected changeset 3 (expected None) crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files 2 warnings encountered! 8 integrity errors encountered! (first damaged changeset appears to be 2) [1] $ cp -R .hg/store-newcopy/. .hg/store Test cloning a treemanifest repo over http. $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log $ cat hg.pid >> $DAEMON_PIDS $ cd .. We can clone even with the knob turned off and we'll get a treemanifest repo. $ hg clone --config experimental.treemanifest=False \ > --config experimental.changegroup3=True \ > http://localhost:$HGPORT deepclone requesting all changes adding changesets adding manifests adding file changes added 4 changesets with 18 changes to 8 files new changesets 775704be6f52:523e5c631710 updating to branch default 8 files updated, 0 files merged, 0 files removed, 0 files unresolved No server errors. $ cat deeprepo/errors.log requires got updated to include treemanifest $ cat deepclone/.hg/requires | grep treemanifest treemanifest Tree manifest revlogs exist. $ find deepclone/.hg/store/meta | sort deepclone/.hg/store/meta deepclone/.hg/store/meta/._a (reposimplestore !) deepclone/.hg/store/meta/._a/00manifest.i (reposimplestore !) deepclone/.hg/store/meta/b deepclone/.hg/store/meta/b/00manifest.i deepclone/.hg/store/meta/b/bar deepclone/.hg/store/meta/b/bar/00manifest.i deepclone/.hg/store/meta/b/bar/orange deepclone/.hg/store/meta/b/bar/orange/00manifest.i deepclone/.hg/store/meta/b/bar/orange/fly deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i deepclone/.hg/store/meta/b/foo deepclone/.hg/store/meta/b/foo/00manifest.i deepclone/.hg/store/meta/b/foo/apple deepclone/.hg/store/meta/b/foo/apple/00manifest.i deepclone/.hg/store/meta/b/foo/apple/bees deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i deepclone/.hg/store/meta/~2e_a (reporevlogstore !) deepclone/.hg/store/meta/~2e_a/00manifest.i (reporevlogstore !) Verify passes. $ cd deepclone $ hg verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files $ cd .. #if reporevlogstore Create clones using old repo formats to use in later tests $ hg clone --config format.usestore=False \ > --config experimental.changegroup3=True \ > http://localhost:$HGPORT deeprepo-basicstore requesting all changes adding changesets adding manifests adding file changes added 4 changesets with 18 changes to 8 files new changesets 775704be6f52:523e5c631710 updating to branch default 8 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd deeprepo-basicstore $ grep store .hg/requires [1] $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --errorlog=errors.log $ cat hg.pid >> $DAEMON_PIDS $ cd .. $ hg clone --config format.usefncache=False \ > --config experimental.changegroup3=True \ > http://localhost:$HGPORT deeprepo-encodedstore requesting all changes adding changesets adding manifests adding file changes added 4 changesets with 18 changes to 8 files new changesets 775704be6f52:523e5c631710 updating to branch default 8 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd deeprepo-encodedstore $ grep fncache .hg/requires [1] $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log $ cat hg.pid >> $DAEMON_PIDS $ cd .. Local clone with basicstore $ hg clone -U deeprepo-basicstore local-clone-basicstore $ hg -R local-clone-basicstore verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files Local clone with encodedstore $ hg clone -U deeprepo-encodedstore local-clone-encodedstore $ hg -R local-clone-encodedstore verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files Local clone with fncachestore $ hg clone -U deeprepo local-clone-fncachestore $ hg -R local-clone-fncachestore verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files Stream clone with basicstore $ hg clone --config experimental.changegroup3=True --stream -U \ > http://localhost:$HGPORT1 stream-clone-basicstore streaming all changes 21 files to transfer, * of data (glob) transferred * in * seconds (*) (glob) $ hg -R stream-clone-basicstore verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files Stream clone with encodedstore $ hg clone --config experimental.changegroup3=True --stream -U \ > http://localhost:$HGPORT2 stream-clone-encodedstore streaming all changes 21 files to transfer, * of data (glob) transferred * in * seconds (*) (glob) $ hg -R stream-clone-encodedstore verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files Stream clone with fncachestore $ hg clone --config experimental.changegroup3=True --stream -U \ > http://localhost:$HGPORT stream-clone-fncachestore streaming all changes 22 files to transfer, * of data (glob) transferred * in * seconds (*) (glob) $ hg -R stream-clone-fncachestore verify checking changesets checking manifests checking directory manifests crosschecking files in changesets and manifests checking files checked 4 changesets with 18 changes to 8 files Packed bundle $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg writing 5330 bytes for 18 files bundle requirements: generaldelta, revlogv1, sparserevlog, treemanifest $ hg debugbundle --spec repo-packed.hg none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog%2Ctreemanifest #endif Bundle with changegroup2 is not supported $ hg -R deeprepo bundle --all -t v2 deeprepo.bundle abort: repository does not support bundle version 02 [255] Pull does not include changegroup for manifest the client already has from other branch $ mkdir grafted-dir-repo $ cd grafted-dir-repo $ hg --config experimental.treemanifest=1 init $ mkdir dir $ echo a > dir/file $ echo a > file $ hg ci -Am initial adding dir/file adding file $ echo b > dir/file $ hg ci -m updated $ hg co '.^' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg revert -r tip dir/ reverting dir/file $ echo b > file # to make sure root manifest is sent $ hg ci -m grafted created new head $ cd .. $ hg --config experimental.treemanifest=1 clone --pull -r 1 \ > grafted-dir-repo grafted-dir-repo-clone adding changesets adding manifests adding file changes added 2 changesets with 3 changes to 2 files new changesets d84f4c419457:09ab742f3b0f updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd grafted-dir-repo-clone $ hg pull -r 2 pulling from $TESTTMP/grafted-dir-repo searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) new changesets 73699489fb7c (run 'hg heads' to see heads, 'hg merge' to merge) Committing a empty commit does not duplicate root treemanifest $ echo z >> z $ hg commit -Aqm 'pre-empty commit' $ hg rm z $ hg commit --amend -m 'empty commit' saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-9e3b6b02-amend.hg $ hg log -r 'tip + tip^' -T '{manifest}\n' 1:678d3574b88c 1:678d3574b88c $ hg --config extensions.strip= strip -r . -q