Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/mercurial/commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/mercurial/commands.py')
-rw-r--r--[l---------]websdk/mercurial/commands.py5673
1 files changed, 5672 insertions, 1 deletions
diff --git a/websdk/mercurial/commands.py b/websdk/mercurial/commands.py
index dcb9954..7ed52fb 120000..100644
--- a/websdk/mercurial/commands.py
+++ b/websdk/mercurial/commands.py
@@ -1 +1,5672 @@
-/usr/share/pyshared/mercurial/commands.py \ No newline at end of file
+# commands.py - command processing for mercurial
+#
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from node import hex, bin, nullid, nullrev, short
+from lock import release
+from i18n import _, gettext
+import os, re, difflib, time, tempfile, errno
+import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
+import patch, help, url, encoding, templatekw, discovery
+import archival, changegroup, cmdutil, hbisect
+import sshserver, hgweb, hgweb.server, commandserver
+import merge as mergemod
+import minirst, revset, fileset
+import dagparser, context, simplemerge
+import random, setdiscovery, treediscovery, dagutil
+
+table = {}
+
+command = cmdutil.command(table)
+
+# 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')),
+ ('', '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')),
+]
+
+dryrunopts = [('n', 'dry-run', None,
+ _('do not perform actions, just print output'))]
+
+remoteopts = [
+ ('e', 'ssh', '',
+ _('specify ssh command to use'), _('CMD')),
+ ('', 'remotecmd', '',
+ _('specify hg command to run on the remote side'), _('CMD')),
+ ('', 'insecure', None,
+ _('do not verify server certificate (ignoring web.cacerts config)')),
+]
+
+walkopts = [
+ ('I', 'include', [],
+ _('include names matching the given patterns'), _('PATTERN')),
+ ('X', 'exclude', [],
+ _('exclude names matching the given patterns'), _('PATTERN')),
+]
+
+commitopts = [
+ ('m', 'message', '',
+ _('use text as commit message'), _('TEXT')),
+ ('l', 'logfile', '',
+ _('read commit message from file'), _('FILE')),
+]
+
+commitopts2 = [
+ ('d', 'date', '',
+ _('record the specified date as commit date'), _('DATE')),
+ ('u', 'user', '',
+ _('record the specified user as committer'), _('USER')),
+]
+
+templateopts = [
+ ('', 'style', '',
+ _('display using template map file'), _('STYLE')),
+ ('', 'template', '',
+ _('display with template'), _('TEMPLATE')),
+]
+
+logopts = [
+ ('p', 'patch', None, _('show patch')),
+ ('g', 'git', None, _('use git extended diff format')),
+ ('l', 'limit', '',
+ _('limit number of changes displayed'), _('NUM')),
+ ('M', 'no-merges', None, _('do not show merges')),
+ ('', 'stat', None, _('output diffstat-style summary of changes')),
+] + templateopts
+
+diffopts = [
+ ('a', 'text', None, _('treat all files as text')),
+ ('g', 'git', None, _('use git extended diff format')),
+ ('', 'nodates', None, _('omit dates from diff headers'))
+]
+
+diffopts2 = [
+ ('p', 'show-function', None, _('show which function each change is in')),
+ ('', 'reverse', None, _('produce a diff that undoes the changes')),
+ ('w', 'ignore-all-space', None,
+ _('ignore white space when comparing lines')),
+ ('b', 'ignore-space-change', None,
+ _('ignore changes in the amount of white space')),
+ ('B', 'ignore-blank-lines', None,
+ _('ignore changes whose lines are all blank')),
+ ('U', 'unified', '',
+ _('number of lines of context to show'), _('NUM')),
+ ('', 'stat', None, _('output diffstat-style summary of changes')),
+]
+
+mergetoolopts = [
+ ('t', 'tool', '', _('specify merge tool')),
+]
+
+similarityopts = [
+ ('s', 'similarity', '',
+ _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
+]
+
+subrepoopts = [
+ ('S', 'subrepos', None,
+ _('recurse into subrepositories'))
+]
+
+# Commands start here, listed alphabetically
+
+@command('^add',
+ walkopts + subrepoopts + dryrunopts,
+ _('[OPTION]... [FILE]...'))
+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.
+
+ .. container:: verbose
+
+ An example showing how 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
+
+ Returns 0 if all files are successfully added.
+ """
+
+ m = scmutil.match(repo[None], pats, opts)
+ rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
+ opts.get('subrepos'), prefix="")
+ return rejected and 1 or 0
+
+@command('addremove',
+ similarityopts + walkopts + dryrunopts,
+ _('[OPTION]... [FILE]...'))
+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.
+
+ 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. With a
+ parameter greater than 0, this compares every removed file with
+ every added file and records those similar enough as renames. This
+ option takes a percentage between 0 (disabled) and 100 (files must
+ be identical) as its parameter. 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.
+
+ Returns 0 if all files are successfully added.
+ """
+ try:
+ sim = float(opts.get('similarity') or 100)
+ except ValueError:
+ raise util.Abort(_('similarity must be a number'))
+ if sim < 0 or sim > 100:
+ raise util.Abort(_('similarity must be between 0 and 100'))
+ return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
+
+@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'))
+ ] + walkopts,
+ _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
+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.
+
+ 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.
+
+ Returns 0 on success.
+ """
+ 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
+
+ datefunc = ui.quiet and util.shortdate or util.datestr
+ getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
+
+ if not pats:
+ raise util.Abort(_('at least one filename or pattern is required'))
+
+ opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
+ ('number', ' ', lambda x: str(x[0].rev())),
+ ('changeset', ' ', lambda x: short(x[0].node())),
+ ('date', ' ', getdate),
+ ('file', ' ', lambda x: x[0].path()),
+ ('line_number', ':', lambda x: str(x[1])),
+ ]
+
+ 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 util.Abort(_('at least one of -n/-c is required for -l'))
+
+ funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
+ funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
+
+ def bad(x, y):
+ raise util.Abort("%s: %s" % (x, y))
+
+ ctx = scmutil.revsingle(repo, opts.get('rev'))
+ m = scmutil.match(ctx, pats, opts)
+ m.bad = bad
+ follow = not opts.get('no_follow')
+ for abs in ctx.walk(m):
+ fctx = ctx[abs]
+ if not opts.get('text') and util.binary(fctx.data()):
+ ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
+ continue
+
+ lines = fctx.annotate(follow=follow, linenumber=linenumber)
+ pieces = []
+
+ for f, sep in funcmap:
+ l = [f(n) for n, dummy in lines]
+ if l:
+ sized = [(x, encoding.colwidth(x)) for x in l]
+ ml = max([w for x, w in sized])
+ pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
+ for x, w in sized])
+
+ if pieces:
+ for p, l in zip(zip(*pieces), lines):
+ ui.write("%s: %s" % ("".join(p), l[1]))
+
+@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'))
+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 (or override using -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.
+ '''
+
+ ctx = scmutil.revsingle(repo, opts.get('rev'))
+ if not ctx:
+ raise util.Abort(_('no working directory: please specify a revision'))
+ node = ctx.node()
+ dest = cmdutil.makefilename(repo, dest, node)
+ if os.path.realpath(dest) == repo.root:
+ raise util.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 util.Abort(_('cannot archive plain files to stdout'))
+ dest = cmdutil.makefileobj(repo, dest)
+ if not prefix:
+ prefix = os.path.basename(repo.root) + '-%h'
+
+ prefix = cmdutil.makefilename(repo, prefix, node)
+ matchfn = scmutil.match(ctx, [], opts)
+ archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
+ matchfn, prefix, subrepos=opts.get('subrepos'))
+
+@command('backout',
+ [('', 'merge', None, _('merge with old dirstate parent after backout')),
+ ('', 'parent', '',
+ _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
+ ('r', 'rev', '', _('revision to backout'), _('REV')),
+ ] + mergetoolopts + walkopts + commitopts + commitopts2,
+ _('[OPTION]... [-r] REV'))
+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 REV is the parent of the working directory, then this new changeset
+ is committed automatically. Otherwise, hg needs to merge the
+ changes and the merged result is left uncommitted.
+
+ .. note::
+ backout cannot be used to fix either an unwanted or
+ incorrect merge.
+
+ .. container:: verbose
+
+ 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.
+
+ Returns 0 on success.
+ '''
+ if rev and node:
+ raise util.Abort(_("please specify just one revision"))
+
+ if not rev:
+ rev = node
+
+ if not rev:
+ raise util.Abort(_("please specify a revision to backout"))
+
+ date = opts.get('date')
+ if date:
+ opts['date'] = util.parsedate(date)
+
+ cmdutil.bailifchanged(repo)
+ node = scmutil.revsingle(repo, rev).node()
+
+ op1, op2 = repo.dirstate.parents()
+ a = repo.changelog.ancestor(op1, node)
+ if a != node:
+ raise util.Abort(_('cannot backout change on a different branch'))
+
+ p1, p2 = repo.changelog.parents(node)
+ if p1 == nullid:
+ raise util.Abort(_('cannot backout a change with no parents'))
+ if p2 != nullid:
+ if not opts.get('parent'):
+ raise util.Abort(_('cannot backout a merge changeset'))
+ p = repo.lookup(opts['parent'])
+ if p not in (p1, p2):
+ raise util.Abort(_('%s is not a parent of %s') %
+ (short(p), short(node)))
+ parent = p
+ else:
+ if opts.get('parent'):
+ raise util.Abort(_('cannot use --parent on non-merge changeset'))
+ parent = p1
+
+ # the backout should appear on the same branch
+ branch = repo.dirstate.branch()
+ hg.clean(repo, node, show_stats=False)
+ repo.dirstate.setbranch(branch)
+ revert_opts = opts.copy()
+ revert_opts['date'] = None
+ revert_opts['all'] = True
+ revert_opts['rev'] = hex(parent)
+ revert_opts['no_backup'] = None
+ revert(ui, repo, **revert_opts)
+ if not opts.get('merge') and op1 != node:
+ try:
+ ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ return hg.update(repo, op1)
+ finally:
+ ui.setconfig('ui', 'forcemerge', '')
+
+ commit_opts = opts.copy()
+ commit_opts['addremove'] = False
+ if not commit_opts['message'] and not commit_opts['logfile']:
+ # we don't translate commit messages
+ commit_opts['message'] = "Backed out changeset %s" % short(node)
+ commit_opts['force_editor'] = True
+ commit(ui, repo, **commit_opts)
+ 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()))
+ try:
+ ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ return hg.merge(repo, hex(repo.changelog.tip()))
+ finally:
+ ui.setconfig('ui', 'forcemerge', '')
+ 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]"))
+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.
+ Its exit status 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 12, and good revision 34::
+
+ 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 (eg. if
+ that revision is not usable because of another issue)::
+
+ hg bisect --skip
+ hg bisect --skip 23
+
+ - 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 all changesets that took part in the current bisection::
+
+ hg log -r "bisect(range)"
+
+ - with the graphlog extension, you can even get a nice graph::
+
+ hg log --graph -r "bisect(range)"
+
+ See :hg:`help revsets` for more about the `bisect()` keyword.
+
+ Returns 0 on success.
+ """
+ def extendbisectrange(nodes, good):
+ # bisect is incomplete when it ends on a merge node and
+ # one of the parent was not checked.
+ parents = repo[nodes[0]].parents()
+ if len(parents) > 1:
+ side = good and state['bad'] or state['good']
+ num = len(set(i.node() for i in parents) & set(side))
+ if num == 1:
+ return parents[0].ancestor(parents[1])
+ return None
+
+ def print_result(nodes, good):
+ displayer = cmdutil.show_changeset(ui, repo, {})
+ if len(nodes) == 1:
+ # narrowed it down to a single revision
+ if good:
+ ui.write(_("The first good revision is:\n"))
+ else:
+ ui.write(_("The first bad revision is:\n"))
+ displayer.show(repo[nodes[0]])
+ extendnode = extendbisectrange(nodes, good)
+ if extendnode is not None:
+ ui.write(_('Not all ancestors of this changeset have been'
+ ' checked.\nUse bisect --extend to continue the '
+ 'bisection from\nthe common ancestor, %s.\n')
+ % extendnode)
+ else:
+ # multiple possible revisions
+ if good:
+ ui.write(_("Due to skipped revisions, the first "
+ "good revision could be any of:\n"))
+ else:
+ ui.write(_("Due to skipped revisions, the first "
+ "bad revision could be any of:\n"))
+ for n in nodes:
+ displayer.show(repo[n])
+ displayer.close()
+
+ def check_state(state, interactive=True):
+ if not state['good'] or not state['bad']:
+ if (good or bad or skip or reset) and interactive:
+ return
+ if not state['good']:
+ raise util.Abort(_('cannot bisect (no known good revisions)'))
+ else:
+ raise util.Abort(_('cannot bisect (no known bad revisions)'))
+ return True
+
+ # backward compatibility
+ if rev in "good bad reset init".split():
+ ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
+ cmd, rev, extra = rev, extra, None
+ if cmd == "good":
+ good = True
+ elif cmd == "bad":
+ bad = True
+ else:
+ reset = True
+ elif extra or good + bad + skip + reset + extend + bool(command) > 1:
+ raise util.Abort(_('incompatible arguments'))
+
+ if reset:
+ p = repo.join("bisect.state")
+ if os.path.exists(p):
+ os.unlink(p)
+ return
+
+ state = hbisect.load_state(repo)
+
+ if command:
+ changesets = 1
+ try:
+ while changesets:
+ # update state
+ status = util.system(command, out=ui.fout)
+ if status == 125:
+ transition = "skip"
+ elif status == 0:
+ transition = "good"
+ # status < 0 means process was killed
+ elif status == 127:
+ raise util.Abort(_("failed to execute %s") % command)
+ elif status < 0:
+ raise util.Abort(_("%s killed") % command)
+ else:
+ transition = "bad"
+ ctx = scmutil.revsingle(repo, rev)
+ rev = None # clear for future iterations
+ state[transition].append(ctx.node())
+ ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
+ check_state(state, interactive=False)
+ # bisect
+ nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+ # update to next check
+ cmdutil.bailifchanged(repo)
+ hg.clean(repo, nodes[0], show_stats=False)
+ finally:
+ hbisect.save_state(repo, state)
+ print_result(nodes, good)
+ return
+
+ # update state
+
+ if rev:
+ nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
+ else:
+ nodes = [repo.lookup('.')]
+
+ if good or bad or skip:
+ if good:
+ state['good'] += nodes
+ elif bad:
+ state['bad'] += nodes
+ elif skip:
+ state['skip'] += nodes
+ hbisect.save_state(repo, state)
+
+ if not check_state(state):
+ return
+
+ # actually bisect
+ nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+ if extend:
+ if not changesets:
+ extendnode = extendbisectrange(nodes, good)
+ if extendnode is not None:
+ ui.write(_("Extending search to changeset %d:%s\n"
+ % (extendnode.rev(), extendnode)))
+ if noupdate:
+ return
+ cmdutil.bailifchanged(repo)
+ return hg.clean(repo, extendnode.node())
+ raise util.Abort(_("nothing to extend"))
+
+ if changesets == 0:
+ print_result(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))
+ if not noupdate:
+ cmdutil.bailifchanged(repo)
+ return hg.clean(repo, node)
+
+@command('bookmarks',
+ [('f', 'force', False, _('force')),
+ ('r', 'rev', '', _('revision'), _('REV')),
+ ('d', 'delete', False, _('delete a given bookmark')),
+ ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
+ ('i', 'inactive', False, _('do not mark a new bookmark active'))],
+ _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
+def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
+ rename=None, inactive=False):
+ '''track a line of development with movable markers
+
+ Bookmarks are pointers to certain commits that move when
+ committing. Bookmarks are local. They can be renamed, copied and
+ deleted. It is possible to use bookmark names in :hg:`merge` and
+ :hg:`update` to merge and update respectively to a given bookmark.
+
+ You can use :hg:`bookmark NAME` to set a bookmark on the working
+ directory's parent revision with the given name. If you specify
+ a revision using -r REV (where REV may be an existing bookmark),
+ the bookmark is assigned to that revision.
+
+ Bookmarks can be pushed and pulled between repositories (see :hg:`help
+ push` and :hg:`help pull`). This requires both the local and remote
+ repositories to support bookmarks. For versions prior to 1.8, this means
+ the bookmarks extension must be enabled.
+ '''
+ hexfn = ui.debugflag and hex or short
+ marks = repo._bookmarks
+ cur = repo.changectx('.').node()
+
+ if rename:
+ if rename not in marks:
+ raise util.Abort(_("bookmark '%s' does not exist") % rename)
+ if mark in marks and not force:
+ raise util.Abort(_("bookmark '%s' already exists "
+ "(use -f to force)") % mark)
+ if mark is None:
+ raise util.Abort(_("new bookmark name required"))
+ marks[mark] = marks[rename]
+ if repo._bookmarkcurrent == rename and not inactive:
+ bookmarks.setcurrent(repo, mark)
+ del marks[rename]
+ bookmarks.write(repo)
+ return
+
+ if delete:
+ if mark is None:
+ raise util.Abort(_("bookmark name required"))
+ if mark not in marks:
+ raise util.Abort(_("bookmark '%s' does not exist") % mark)
+ if mark == repo._bookmarkcurrent:
+ bookmarks.setcurrent(repo, None)
+ del marks[mark]
+ bookmarks.write(repo)
+ return
+
+ if mark is not None:
+ if "\n" in mark:
+ raise util.Abort(_("bookmark name cannot contain newlines"))
+ mark = mark.strip()
+ if not mark:
+ raise util.Abort(_("bookmark names cannot consist entirely of "
+ "whitespace"))
+ if inactive and mark == repo._bookmarkcurrent:
+ bookmarks.setcurrent(repo, None)
+ return
+ if mark in marks and not force:
+ raise util.Abort(_("bookmark '%s' already exists "
+ "(use -f to force)") % mark)
+ if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
+ and not force):
+ raise util.Abort(
+ _("a bookmark cannot have the name of an existing branch"))
+ if rev:
+ marks[mark] = repo.lookup(rev)
+ else:
+ marks[mark] = repo.changectx('.').node()
+ if not inactive and repo.changectx('.').node() == marks[mark]:
+ bookmarks.setcurrent(repo, mark)
+ bookmarks.write(repo)
+ return
+
+ if mark is None:
+ if rev:
+ raise util.Abort(_("bookmark name required"))
+ if len(marks) == 0:
+ ui.status(_("no bookmarks set\n"))
+ else:
+ for bmark, n in sorted(marks.iteritems()):
+ current = repo._bookmarkcurrent
+ if bmark == current and n == cur:
+ prefix, label = '*', 'bookmarks.current'
+ else:
+ prefix, label = ' ', ''
+
+ if ui.quiet:
+ ui.write("%s\n" % bmark, label=label)
+ else:
+ ui.write(" %s %-25s %d:%s\n" % (
+ prefix, bmark, repo.changelog.rev(n), hexfn(n)),
+ label=label)
+ return
+
+@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'))],
+ _('[-fC] [NAME]'))
+def branch(ui, repo, label=None, **opts):
+ """set or show the current branch name
+
+ 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, even if it's inactive.
+
+ 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 as closed.
+
+ .. note::
+ Branch names are permanent. Use :hg:`bookmark` to create a
+ light-weight bookmark instead. See :hg:`help glossary` for more
+ information about named branches and bookmarks.
+
+ Returns 0 on success.
+ """
+
+ if opts.get('clean'):
+ label = repo[None].p1().branch()
+ repo.dirstate.setbranch(label)
+ ui.status(_('reset working directory to branch %s\n') % label)
+ elif label:
+ if not opts.get('force') and label in repo.branchtags():
+ if label not in [p.branch() for p in repo.parents()]:
+ raise util.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)
+ else:
+ ui.write("%s\n" % repo.dirstate.branch())
+
+@command('branches',
+ [('a', 'active', False, _('show only branches that have unmerged heads')),
+ ('c', 'closed', False, _('show normal and closed branches'))],
+ _('[-ac]'))
+def branches(ui, repo, active=False, closed=False):
+ """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`).
+
+ If -a/--active is specified, only show active branches. A branch
+ is considered active if it contains repository heads.
+
+ Use the command :hg:`update` to switch to an existing branch.
+
+ Returns 0.
+ """
+
+ hexfunc = ui.debugflag and hex or short
+ activebranches = [repo[n].branch() for n in repo.heads()]
+ def testactive(tag, node):
+ realhead = tag in activebranches
+ open = node in repo.branchheads(tag, closed=False)
+ return realhead and open
+ branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
+ for tag, node in repo.branchtags().items()],
+ reverse=True)
+
+ for isactive, node, tag in branches:
+ if (not active) or isactive:
+ if ui.quiet:
+ ui.write("%s\n" % tag)
+ else:
+ hn = repo.lookup(node)
+ if isactive:
+ label = 'branches.active'
+ notice = ''
+ elif hn not in repo.branchheads(tag, closed=False):
+ if not closed:
+ continue
+ label = 'branches.closed'
+ notice = _(' (closed)')
+ else:
+ label = 'branches.inactive'
+ notice = _(' (inactive)')
+ if tag == repo.dirstate.branch():
+ label = 'branches.current'
+ rev = str(node).rjust(31 - encoding.colwidth(tag))
+ rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
+ tag = ui.label(tag, label)
+ ui.write("%s %s%s\n" % (tag, rev, notice))
+
+@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 TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
+def bundle(ui, repo, fname, dest=None, **opts):
+ """create a changegroup file
+
+ Generate a compressed changegroup file collecting changesets not
+ known to be in another repository.
+
+ If you omit the destination repository, then hg assumes the
+ destination will have all the nodes you specify with --base
+ parameters. To create a bundle containing all changesets, use
+ -a/--all (or --base null).
+
+ You can change compression method with the -t/--type option.
+ The available compression methods are: none, bzip2, and
+ gzip (by default, bundles are compressed using 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.
+ """
+ revs = None
+ if 'rev' in opts:
+ revs = scmutil.revrange(repo, opts['rev'])
+
+ if opts.get('all'):
+ base = ['null']
+ else:
+ base = scmutil.revrange(repo, opts.get('base'))
+ if base:
+ if dest:
+ raise util.Abort(_("--base is incompatible with specifying "
+ "a destination"))
+ common = [repo.lookup(rev) for rev in base]
+ heads = revs and map(repo.lookup, revs) or revs
+ 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, checkout = hg.addbranchrevs(repo, other, branches, revs)
+ heads = revs and map(repo.lookup, revs) or revs
+ common, outheads = discovery.findcommonoutgoing(repo, other,
+ onlyheads=heads,
+ force=opts.get('force'))
+
+ cg = repo.getbundle('bundle', common=common, heads=heads)
+ if not cg:
+ ui.status(_("no changes found\n"))
+ return 1
+
+ bundletype = opts.get('type', 'bzip2').lower()
+ btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
+ bundletype = btypes.get(bundletype)
+ if bundletype not in changegroup.bundletypes:
+ raise util.Abort(_('unknown bundle type specified with --type'))
+
+ changegroup.writebundle(cg, fname, bundletype)
+
+@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,
+ _('[OPTION]... FILE...'))
+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,
+ or tip if no revision is checked out.
+
+ Output may be to a file, in which case the name of the file is
+ given using a format string. The formatting rules are the same as
+ for the export command, with the following additions:
+
+ :``%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
+
+ Returns 0 on success.
+ """
+ ctx = scmutil.revsingle(repo, opts.get('rev'))
+ err = 1
+ m = scmutil.match(ctx, (file1,) + pats, opts)
+ for abs in ctx.walk(m):
+ fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
+ pathname=abs)
+ data = ctx[abs].data()
+ if opts.get('decode'):
+ data = repo.wwritedata(abs, data)
+ fp.write(data)
+ fp.close()
+ err = 0
+ return err
+
+@command('^clone',
+ [('U', 'noupdate', None,
+ _('the clone will include an empty working copy (only a repository)')),
+ ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
+ ('r', 'rev', [], _('include the specified changeset'), _('REV')),
+ ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
+ ('', 'pull', None, _('use pull protocol to copy metadata')),
+ ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
+ ] + remoteopts,
+ _('[OPTION]... SOURCE [DEST]'))
+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.
+
+ 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. Note that specifying a
+ tag will include the tagged changeset but not the changeset
+ containing the tag.
+
+ To check out a particular version, use -u/--update, or
+ -U/--noupdate to create a clone with no working directory.
+
+ .. 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.
+
+ In some cases, you can clone repositories and the working
+ directory using full hardlinks with ::
+
+ $ cp -al REPO REPOCLONE
+
+ This is the fastest way to clone, but it is not always safe. The
+ operation is not atomic (making sure REPO is not modified during
+ the operation is up to you) and you have to make sure your
+ editor breaks hardlinks (Emacs and most Linux Kernel tools do
+ so). Also, this is not compatible with certain extensions that
+ place their metadata under the .hg directory, such as mq.
+
+ 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 tipmost head of the default branch
+ h) tip
+
+ Examples:
+
+ - clone a remote repository to a new directory named hg/::
+
+ hg clone http://selenic.com/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 high-speed clone over a LAN while checking out a
+ specified version::
+
+ hg clone --uncompressed 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 http://selenic.com/hg#stable
+
+ See :hg:`help urls` for details on specifying URLs.
+
+ Returns 0 on success.
+ """
+ if opts.get('noupdate') and opts.get('updaterev'):
+ raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
+
+ r = hg.clone(ui, opts, source, dest,
+ pull=opts.get('pull'),
+ stream=opts.get('uncompressed'),
+ rev=opts.get('rev'),
+ update=opts.get('updaterev') or not opts.get('noupdate'),
+ branch=opts.get('branch'))
+
+ 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 as closed, hiding it from the branch list')),
+ ] + walkopts + commitopts + commitopts2 + subrepoopts,
+ _('[OPTION]... [FILE]...'))
+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``.
+
+ See :hg:`help dates` for a list of formats valid for -d/--date.
+
+ Returns 0 on success, 1 if nothing changed.
+ """
+ if opts.get('subrepos'):
+ # Let --subrepos on the command line overide config setting.
+ ui.setconfig('ui', 'commitsubrepos', True)
+
+ extra = {}
+ if opts.get('close_branch'):
+ if repo['.'].node() not in repo.branchheads():
+ # The topo heads set is included in the branch heads set of the
+ # current branch, so it's sufficient to test branchheads
+ raise util.Abort(_('can only close branch heads'))
+ extra['close'] = 1
+ e = cmdutil.commiteditor
+ if opts.get('force_editor'):
+ e = cmdutil.commitforceeditor
+
+ def commitfunc(ui, repo, message, match, opts):
+ return repo.commit(message, opts.get('user'), opts.get('date'), match,
+ editor=e, extra=extra)
+
+ branch = repo[None].branch()
+ bheads = repo.branchheads(branch)
+
+ node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
+ if not node:
+ stat = repo.status(match=scmutil.match(repo[None], 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
+
+ ctx = repo[node]
+ parents = ctx.parents()
+
+ if (bheads and node not in bheads and not
+ [x for x in parents if x.node() in bheads and x.branch() == branch]):
+ ui.status(_('created new head\n'))
+ # The message is not printed for initial roots. For the other
+ # changesets, it is printed in the following situations:
+ #
+ # Par column: for the 2 parents with ...
+ # N: null or no parent
+ # B: parent is on another named branch
+ # C: parent is a regular non head changeset
+ # H: parent was a branch head of the current branch
+ # Msg column: whether we print "created new head" message
+ # In the following, it is assumed that there already exists some
+ # initial branch heads of the current branch, otherwise nothing is
+ # printed anyway.
+ #
+ # Par Msg Comment
+ # NN y additional topo root
+ #
+ # BN y additional branch root
+ # CN y additional topo head
+ # HN n usual case
+ #
+ # BB y weird additional branch root
+ # CB y branch merge
+ # HB n merge with named branch
+ #
+ # CC y additional head from merge
+ # CH n merge with a head
+ #
+ # HH n head merge: head count decreases
+
+ if not opts.get('close_branch'):
+ for r in parents:
+ if r.extra().get('close') and r.branch() == branch:
+ ui.status(_('reopening closed branch head %d\n') % r)
+
+ if ui.debugflag:
+ ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
+ elif ui.verbose:
+ ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
+
+@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'))
+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.
+ """
+ wlock = repo.wlock(False)
+ try:
+ return cmdutil.copy(ui, repo, pats, opts)
+ finally:
+ wlock.release()
+
+@command('debugancestor', [], _('[INDEX] REV1 REV2'))
+def debugancestor(ui, repo, *args):
+ """find the ancestor revision of two revisions in a given index"""
+ if len(args) == 3:
+ index, rev1, rev2 = args
+ r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
+ lookup = r.lookup
+ elif len(args) == 2:
+ if not repo:
+ raise util.Abort(_("there is no Mercurial repository here "
+ "(.hg not found)"))
+ rev1, rev2 = args
+ r = repo.changelog
+ lookup = repo.lookup
+ else:
+ raise util.Abort(_('either two or three arguments required'))
+ a = r.ancestor(lookup(rev1), lookup(rev2))
+ ui.write("%d:%s\n" % (r.rev(a), hex(a)))
+
+@command('debugbuilddag',
+ [('m', 'mergeable-file', None, _('add single file mergeable changes')),
+ ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
+ ('n', 'new-file', None, _('add new file at each rev'))],
+ _('[OPTION]... [TEXT]'))
+def debugbuilddag(ui, repo, text=None,
+ mergeable_file=False,
+ overwritten_file=False,
+ new_file=False):
+ """builds a repo with a given DAG from scratch in the current empty repo
+
+ The description of the DAG is read from stdin if not given on the
+ command line.
+
+ Elements:
+
+ - "+n" is a linear run of n nodes based on the current default parent
+ - "." is a single node based on the current default parent
+ - "$" resets the default parent to null (implied at the start);
+ otherwise the default parent is always the last node created
+ - "<p" sets the default parent to the backref p
+ - "*p" is a fork at parent p, which is a backref
+ - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
+ - "/p2" is a merge of the preceding node and p2
+ - ":tag" defines a local tag for the preceding node
+ - "@branch" sets the named branch for subsequent nodes
+ - "#...\\n" is a comment up to the end of the line
+
+ Whitespace between the above elements is ignored.
+
+ A backref is either
+
+ - a number n, which references the node curr-n, where curr is the current
+ node, or
+ - the name of a local tag you placed earlier using ":tag", or
+ - empty to denote the default parent.
+
+ All string valued-elements are either strictly alphanumeric, or must
+ be enclosed in double quotes ("..."), with "\\" as escape character.
+ """
+
+ if text is None:
+ ui.status(_("reading DAG from stdin\n"))
+ text = ui.fin.read()
+
+ cl = repo.changelog
+ if len(cl) > 0:
+ raise util.Abort(_('repository is not empty'))
+
+ # determine number of revs in DAG
+ total = 0
+ for type, data in dagparser.parsedag(text):
+ if type == 'n':
+ total += 1
+
+ if mergeable_file:
+ linesperrev = 2
+ # make a file with k lines per rev
+ initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
+ initialmergedlines.append("")
+
+ tags = []
+
+ tr = repo.transaction("builddag")
+ try:
+
+ at = -1
+ atbranch = 'default'
+ nodeids = []
+ ui.progress(_('building'), 0, unit=_('revisions'), total=total)
+ for type, data in dagparser.parsedag(text):
+ if type == 'n':
+ ui.note('node %s\n' % str(data))
+ id, ps = data
+
+ files = []
+ fctxs = {}
+
+ p2 = None
+ if mergeable_file:
+ fn = "mf"
+ p1 = repo[ps[0]]
+ if len(ps) > 1:
+ p2 = repo[ps[1]]
+ pa = p1.ancestor(p2)
+ base, local, other = [x[fn].data() for x in pa, p1, p2]
+ m3 = simplemerge.Merge3Text(base, local, other)
+ ml = [l.strip() for l in m3.merge_lines()]
+ ml.append("")
+ elif at > 0:
+ ml = p1[fn].data().split("\n")
+ else:
+ ml = initialmergedlines
+ ml[id * linesperrev] += " r%i" % id
+ mergedtext = "\n".join(ml)
+ files.append(fn)
+ fctxs[fn] = context.memfilectx(fn, mergedtext)
+
+ if overwritten_file:
+ fn = "of"
+ files.append(fn)
+ fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
+
+ if new_file:
+ fn = "nf%i" % id
+ files.append(fn)
+ fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
+ if len(ps) > 1:
+ if not p2:
+ p2 = repo[ps[1]]
+ for fn in p2:
+ if fn.startswith("nf"):
+ files.append(fn)
+ fctxs[fn] = p2[fn]
+
+ def fctxfn(repo, cx, path):
+ return fctxs.get(path)
+
+ if len(ps) == 0 or ps[0] < 0:
+ pars = [None, None]
+ elif len(ps) == 1:
+ pars = [nodeids[ps[0]], None]
+ else:
+ pars = [nodeids[p] for p in ps]
+ cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
+ date=(id, 0),
+ user="debugbuilddag",
+ extra={'branch': atbranch})
+ nodeid = repo.commitctx(cx)
+ nodeids.append(nodeid)
+ at = id
+ elif type == 'l':
+ id, name = data
+ ui.note('tag %s\n' % name)
+ tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
+ elif type == 'a':
+ ui.note('branch %s\n' % data)
+ atbranch = data
+ ui.progress(_('building'), id, unit=_('revisions'), total=total)
+ tr.close()
+ finally:
+ ui.progress(_('building'), None)
+ tr.release()
+
+ if tags:
+ repo.opener.write("localtags", "".join(tags))
+
+@command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
+def debugbundle(ui, bundlepath, all=None, **opts):
+ """lists the contents of a bundle"""
+ f = url.open(ui, bundlepath)
+ try:
+ gen = changegroup.readbundle(f, bundlepath)
+ if all:
+ ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
+
+ def showchunks(named):
+ ui.write("\n%s\n" % named)
+ chain = None
+ while True:
+ chunkdata = gen.deltachunk(chain)
+ if not chunkdata:
+ break
+ node = chunkdata['node']
+ p1 = chunkdata['p1']
+ p2 = chunkdata['p2']
+ cs = chunkdata['cs']
+ deltabase = chunkdata['deltabase']
+ delta = chunkdata['delta']
+ ui.write("%s %s %s %s %s %s\n" %
+ (hex(node), hex(p1), hex(p2),
+ hex(cs), hex(deltabase), len(delta)))
+ chain = node
+
+ chunkdata = gen.changelogheader()
+ showchunks("changelog")
+ chunkdata = gen.manifestheader()
+ showchunks("manifest")
+ while True:
+ chunkdata = gen.filelogheader()
+ if not chunkdata:
+ break
+ fname = chunkdata['filename']
+ showchunks(fname)
+ else:
+ chunkdata = gen.changelogheader()
+ chain = None
+ while True:
+ chunkdata = gen.deltachunk(chain)
+ if not chunkdata:
+ break
+ node = chunkdata['node']
+ ui.write("%s\n" % hex(node))
+ chain = node
+ finally:
+ f.close()
+
+@command('debugcheckstate', [], '')
+def debugcheckstate(ui, repo):
+ """validate the correctness of the current dirstate"""
+ parent1, parent2 = repo.dirstate.parents()
+ m1 = repo[parent1].manifest()
+ m2 = repo[parent2].manifest()
+ errors = 0
+ for f in repo.dirstate:
+ state = repo.dirstate[f]
+ if state in "nr" and f not in m1:
+ ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
+ errors += 1
+ if state in "a" and f in m1:
+ ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
+ errors += 1
+ if state in "m" and f not in m1 and f not in m2:
+ ui.warn(_("%s in state %s, but not in either manifest\n") %
+ (f, state))
+ errors += 1
+ for f in m1:
+ state = repo.dirstate[f]
+ if state not in "nrm":
+ ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
+ errors += 1
+ if errors:
+ error = _(".hg/dirstate inconsistent with current parent's manifest")
+ raise util.Abort(error)
+
+@command('debugcommands', [], _('[COMMAND]'))
+def debugcommands(ui, cmd='', *args):
+ """list all available commands and options"""
+ for cmd, vals in sorted(table.iteritems()):
+ cmd = cmd.split('|')[0].strip('^')
+ 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'))
+def debugcomplete(ui, cmd='', **opts):
+ """returns the completion list associated with the given command"""
+
+ if opts.get('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 = 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('debugdag',
+ [('t', 'tags', None, _('use tags as labels')),
+ ('b', 'branches', None, _('annotate with branch names')),
+ ('', 'dots', None, _('use dots for runs')),
+ ('s', 'spaces', None, _('separate elements by spaces'))],
+ _('[OPTION]... [FILE [REV]...]'))
+def debugdag(ui, repo, file_=None, *revs, **opts):
+ """format the changelog or an index DAG as a concise textual description
+
+ If you pass a revlog index, the revlog's DAG is emitted. If you list
+ revision numbers, they get labelled in the output as rN.
+
+ Otherwise, the changelog DAG of the current repo is emitted.
+ """
+ spaces = opts.get('spaces')
+ dots = opts.get('dots')
+ if file_:
+ rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
+ revs = set((int(r) for r in revs))
+ def events():
+ for r in rlog:
+ yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
+ if r in revs:
+ yield 'l', (r, "r%i" % r)
+ elif repo:
+ cl = repo.changelog
+ tags = opts.get('tags')
+ branches = opts.get('branches')
+ if tags:
+ labels = {}
+ for l, n in repo.tags().items():
+ labels.setdefault(cl.rev(n), []).append(l)
+ def events():
+ b = "default"
+ for r in cl:
+ if branches:
+ newb = cl.read(cl.node(r))[5]['branch']
+ if newb != b:
+ yield 'a', newb
+ b = newb
+ yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
+ if tags:
+ ls = labels.get(r)
+ if ls:
+ for l in ls:
+ yield 'l', (r, l)
+ else:
+ raise util.Abort(_('need repo for changelog dag'))
+
+ for line in dagparser.dagtextlines(events(),
+ addspaces=spaces,
+ wraplabels=True,
+ wrapannotations=True,
+ wrapnonlinear=dots,
+ usedots=dots,
+ maxlinewidth=70):
+ ui.write(line)
+ ui.write("\n")
+
+@command('debugdata',
+ [('c', 'changelog', False, _('open changelog')),
+ ('m', 'manifest', False, _('open manifest'))],
+ _('-c|-m|FILE REV'))
+def debugdata(ui, repo, file_, rev = None, **opts):
+ """dump the contents of a data file revision"""
+ if opts.get('changelog') or opts.get('manifest'):
+ file_, rev = None, file_
+ elif rev is None:
+ raise error.CommandError('debugdata', _('invalid arguments'))
+ r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
+ try:
+ ui.write(r.revision(r.lookup(rev)))
+ except KeyError:
+ raise util.Abort(_('invalid revision identifier %s') % rev)
+
+@command('debugdate',
+ [('e', 'extended', None, _('try extended date formats'))],
+ _('[-e] DATE [RANGE]'))
+def debugdate(ui, date, range=None, **opts):
+ """parse and display a date"""
+ if opts["extended"]:
+ d = util.parsedate(date, util.extendeddateformats)
+ else:
+ d = util.parsedate(date)
+ ui.write("internal: %s %s\n" % d)
+ ui.write("standard: %s\n" % util.datestr(d))
+ if range:
+ m = util.matchdate(range)
+ ui.write("match: %s\n" % m(d[0]))
+
+@command('debugdiscovery',
+ [('', 'old', None, _('use old-style discovery')),
+ ('', 'nonheads', None,
+ _('use old-style discovery with non-heads included')),
+ ] + remoteopts,
+ _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
+def debugdiscovery(ui, repo, remoteurl="default", **opts):
+ """runs the changeset discovery protocol in isolation"""
+ remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
+ remote = hg.peer(repo, opts, remoteurl)
+ ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
+
+ # make sure tests are repeatable
+ random.seed(12323)
+
+ def doit(localheads, remoteheads):
+ if opts.get('old'):
+ if localheads:
+ raise util.Abort('cannot use localheads with old style discovery')
+ common, _in, hds = treediscovery.findcommonincoming(repo, remote,
+ force=True)
+ common = set(common)
+ if not opts.get('nonheads'):
+ ui.write("unpruned common: %s\n" % " ".join([short(n)
+ for n in common]))
+ dag = dagutil.revlogdag(repo.changelog)
+ all = dag.ancestorset(dag.internalizeall(common))
+ common = dag.externalizeall(dag.headsetofconnecteds(all))
+ else:
+ common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
+ common = set(common)
+ rheads = set(hds)
+ lheads = set(repo.heads())
+ ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
+ if lheads <= common:
+ ui.write("local is subset\n")
+ elif rheads <= common:
+ ui.write("remote is subset\n")
+
+ serverlogs = opts.get('serverlog')
+ if serverlogs:
+ for filename in serverlogs:
+ logfile = open(filename, 'r')
+ try:
+ line = logfile.readline()
+ while line:
+ parts = line.strip().split(';')
+ op = parts[1]
+ if op == 'cg':
+ pass
+ elif op == 'cgss':
+ doit(parts[2].split(' '), parts[3].split(' '))
+ elif op == 'unb':
+ doit(parts[3].split(' '), parts[2].split(' '))
+ line = logfile.readline()
+ finally:
+ logfile.close()
+
+ else:
+ remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
+ opts.get('remote_head'))
+ localrevs = opts.get('local_head')
+ doit(localrevs, remoterevs)
+
+@command('debugfileset', [], ('REVSPEC'))
+def debugfileset(ui, repo, expr):
+ '''parse and apply a fileset specification'''
+ if ui.verbose:
+ tree = fileset.parse(expr)[0]
+ ui.note(tree, "\n")
+
+ for f in fileset.getfileset(repo[None], expr):
+ ui.write("%s\n" % f)
+
+@command('debugfsinfo', [], _('[PATH]'))
+def debugfsinfo(ui, path = "."):
+ """show information detected about current filesystem"""
+ util.writefile('.debugfsinfo', '')
+ ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
+ ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
+ ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
+ and 'yes' or 'no'))
+ os.unlink('.debugfsinfo')
+
+@command('debuggetbundle',
+ [('H', 'head', [], _('id of head node'), _('ID')),
+ ('C', 'common', [], _('id of common node'), _('ID')),
+ ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
+ _('REPO FILE [-H|-C ID]...'))
+def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
+ """retrieves a bundle from a repo
+
+ Every ID must be a full-length hex node id string. Saves the bundle to the
+ given file.
+ """
+ repo = hg.peer(ui, opts, repopath)
+ if not repo.capable('getbundle'):
+ raise util.Abort("getbundle() not supported by target repository")
+ args = {}
+ if common:
+ args['common'] = [bin(s) for s in common]
+ if head:
+ args['heads'] = [bin(s) for s in head]
+ bundle = repo.getbundle('debug', **args)
+
+ bundletype = opts.get('type', 'bzip2').lower()
+ btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
+ bundletype = btypes.get(bundletype)
+ if bundletype not in changegroup.bundletypes:
+ raise util.Abort(_('unknown bundle type specified with --type'))
+ changegroup.writebundle(bundle, bundlepath, bundletype)
+
+@command('debugignore', [], '')
+def debugignore(ui, repo, *values, **opts):
+ """display the combined ignore pattern"""
+ ignore = repo.dirstate._ignore
+ includepat = getattr(ignore, 'includepat', None)
+ if includepat is not None:
+ ui.write("%s\n" % includepat)
+ else:
+ raise util.Abort(_("no ignore patterns found"))
+
+@command('debugindex',
+ [('c', 'changelog', False, _('open changelog')),
+ ('m', 'manifest', False, _('open manifest')),
+ ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
+ _('[-f FORMAT] -c|-m|FILE'))
+def debugindex(ui, repo, file_ = None, **opts):
+ """dump the contents of an index file"""
+ r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
+ format = opts.get('format', 0)
+ if format not in (0, 1):
+ raise util.Abort(_("unknown format %d") % format)
+
+ generaldelta = r.version & revlog.REVLOGGENERALDELTA
+ if generaldelta:
+ basehdr = ' delta'
+ else:
+ basehdr = ' base'
+
+ if format == 0:
+ ui.write(" rev offset length " + basehdr + " linkrev"
+ " nodeid p1 p2\n")
+ elif format == 1:
+ ui.write(" rev flag offset length"
+ " size " + basehdr + " link p1 p2 nodeid\n")
+
+ for i in r:
+ node = r.node(i)
+ if generaldelta:
+ base = r.deltaparent(i)
+ else:
+ base = r.chainbase(i)
+ if format == 0:
+ try:
+ pp = r.parents(node)
+ except:
+ pp = [nullid, nullid]
+ ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
+ i, r.start(i), r.length(i), base, r.linkrev(i),
+ short(node), short(pp[0]), short(pp[1])))
+ elif format == 1:
+ pr = r.parentrevs(i)
+ ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
+ i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
+ base, r.linkrev(i), pr[0], pr[1], short(node)))
+
+@command('debugindexdot', [], _('FILE'))
+def debugindexdot(ui, repo, file_):
+ """dump an index DAG as a graphviz dot file"""
+ r = None
+ if repo:
+ filelog = repo.file(file_)
+ if len(filelog):
+ r = filelog
+ if not r:
+ r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
+ ui.write("digraph G {\n")
+ for i in r:
+ node = r.node(i)
+ pp = r.parents(node)
+ ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
+ if pp[1] != nullid:
+ ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
+ ui.write("}\n")
+
+@command('debuginstall', [], '')
+def debuginstall(ui):
+ '''test Mercurial installation
+
+ Returns 0 on success.
+ '''
+
+ def writetemp(contents):
+ (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
+ f = os.fdopen(fd, "wb")
+ f.write(contents)
+ f.close()
+ return name
+
+ problems = 0
+
+ # encoding
+ ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
+ try:
+ encoding.fromlocal("test")
+ except util.Abort, inst:
+ ui.write(" %s\n" % inst)
+ ui.write(_(" (check that your locale is properly set)\n"))
+ problems += 1
+
+ # compiled modules
+ ui.status(_("Checking installed modules (%s)...\n")
+ % os.path.dirname(__file__))
+ try:
+ import bdiff, mpatch, base85, osutil
+ dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
+ except Exception, inst:
+ ui.write(" %s\n" % inst)
+ ui.write(_(" One or more extensions could not be found"))
+ ui.write(_(" (check that you compiled the extensions)\n"))
+ problems += 1
+
+ # templates
+ import templater
+ p = templater.templatepath()
+ ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
+ try:
+ templater.templater(templater.templatepath("map-cmdline.default"))
+ except Exception, inst:
+ ui.write(" %s\n" % inst)
+ ui.write(_(" (templates seem to have been installed incorrectly)\n"))
+ problems += 1
+
+ # editor
+ ui.status(_("Checking commit editor...\n"))
+ editor = ui.geteditor()
+ cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
+ if not cmdpath:
+ if editor == 'vi':
+ ui.write(_(" No commit editor set and can't find vi in PATH\n"))
+ ui.write(_(" (specify a commit editor in your configuration"
+ " file)\n"))
+ else:
+ ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
+ ui.write(_(" (specify a commit editor in your configuration"
+ " file)\n"))
+ problems += 1
+
+ # check username
+ ui.status(_("Checking username...\n"))
+ try:
+ ui.username()
+ except util.Abort, e:
+ ui.write(" %s\n" % e)
+ ui.write(_(" (specify a username in your configuration file)\n"))
+ problems += 1
+
+ if not problems:
+ ui.status(_("No problems detected\n"))
+ else:
+ ui.write(_("%s problems detected,"
+ " please check your install!\n") % problems)
+
+ return problems
+
+@command('debugknown', [], _('REPO ID...'))
+def debugknown(ui, repopath, *ids, **opts):
+ """test whether node ids are known to a repo
+
+ Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
+ indicating unknown/known.
+ """
+ repo = hg.peer(ui, opts, repopath)
+ if not repo.capable('known'):
+ raise util.Abort("known() not supported by target repository")
+ flags = repo.known([bin(s) for s in ids])
+ ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
+
+@command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
+def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
+ '''access the pushkey key/value protocol
+
+ With two args, list the keys in the given namespace.
+
+ With five args, set a key to new if it currently is set to old.
+ Reports success or failure.
+ '''
+
+ target = hg.peer(ui, {}, repopath)
+ if keyinfo:
+ key, old, new = keyinfo
+ r = target.pushkey(namespace, key, old, new)
+ ui.status(str(r) + '\n')
+ return not r
+ else:
+ for k, v in target.listkeys(namespace).iteritems():
+ ui.write("%s\t%s\n" % (k.encode('string-escape'),
+ v.encode('string-escape')))
+
+@command('debugrebuildstate',
+ [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
+ _('[-r REV] [REV]'))
+def debugrebuildstate(ui, repo, rev="tip"):
+ """rebuild the dirstate as it would look like for the given revision"""
+ ctx = scmutil.revsingle(repo, rev)
+ wlock = repo.wlock()
+ try:
+ repo.dirstate.rebuild(ctx.node(), ctx.manifest())
+ finally:
+ wlock.release()
+
+@command('debugrename',
+ [('r', 'rev', '', _('revision to debug'), _('REV'))],
+ _('[-r REV] FILE'))
+def debugrename(ui, repo, file1, *pats, **opts):
+ """dump rename information"""
+
+ ctx = scmutil.revsingle(repo, opts.get('rev'))
+ m = scmutil.match(ctx, (file1,) + pats, opts)
+ for abs in ctx.walk(m):
+ fctx = ctx[abs]
+ o = fctx.filelog().renamed(fctx.filenode())
+ rel = m.rel(abs)
+ if o:
+ ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
+ else:
+ ui.write(_("%s not renamed\n") % rel)
+
+@command('debugrevlog',
+ [('c', 'changelog', False, _('open changelog')),
+ ('m', 'manifest', False, _('open manifest')),
+ ('d', 'dump', False, _('dump index data'))],
+ _('-c|-m|FILE'))
+def debugrevlog(ui, repo, file_ = None, **opts):
+ """show data and statistics about a revlog"""
+ r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
+
+ if opts.get("dump"):
+ numrevs = len(r)
+ ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
+ " rawsize totalsize compression heads\n")
+ ts = 0
+ heads = set()
+ for rev in xrange(numrevs):
+ dbase = r.deltaparent(rev)
+ if dbase == -1:
+ dbase = rev
+ cbase = r.chainbase(rev)
+ p1, p2 = r.parentrevs(rev)
+ rs = r.rawsize(rev)
+ ts = ts + rs
+ heads -= set(r.parentrevs(rev))
+ heads.add(rev)
+ ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
+ (rev, p1, p2, r.start(rev), r.end(rev),
+ r.start(dbase), r.start(cbase),
+ r.start(p1), r.start(p2),
+ rs, ts, ts / r.end(rev), len(heads)))
+ return 0
+
+ v = r.version
+ format = v & 0xFFFF
+ flags = []
+ gdelta = False
+ if v & revlog.REVLOGNGINLINEDATA:
+ flags.append('inline')
+ if v & revlog.REVLOGGENERALDELTA:
+ gdelta = True
+ flags.append('generaldelta')
+ if not flags:
+ flags = ['(none)']
+
+ nummerges = 0
+ numfull = 0
+ numprev = 0
+ nump1 = 0
+ nump2 = 0
+ numother = 0
+ nump1prev = 0
+ nump2prev = 0
+ chainlengths = []
+
+ datasize = [None, 0, 0L]
+ fullsize = [None, 0, 0L]
+ deltasize = [None, 0, 0L]
+
+ def addsize(size, l):
+ if l[0] is None or size < l[0]:
+ l[0] = size
+ if size > l[1]:
+ l[1] = size
+ l[2] += size
+
+ numrevs = len(r)
+ for rev in xrange(numrevs):
+ p1, p2 = r.parentrevs(rev)
+ delta = r.deltaparent(rev)
+ if format > 0:
+ addsize(r.rawsize(rev), datasize)
+ if p2 != nullrev:
+ nummerges += 1
+ size = r.length(rev)
+ if delta == nullrev:
+ chainlengths.append(0)
+ numfull += 1
+ addsize(size, fullsize)
+ else:
+ chainlengths.append(chainlengths[delta] + 1)
+ addsize(size, deltasize)
+ if delta == rev - 1:
+ numprev += 1
+ if delta == p1:
+ nump1prev += 1
+ elif delta == p2:
+ nump2prev += 1
+ elif delta == p1:
+ nump1 += 1
+ elif delta == p2:
+ nump2 += 1
+ elif delta != nullrev:
+ numother += 1
+
+ numdeltas = numrevs - numfull
+ numoprev = numprev - nump1prev - nump2prev
+ totalrawsize = datasize[2]
+ datasize[2] /= numrevs
+ fulltotal = fullsize[2]
+ fullsize[2] /= numfull
+ deltatotal = deltasize[2]
+ deltasize[2] /= numrevs - numfull
+ totalsize = fulltotal + deltatotal
+ avgchainlen = sum(chainlengths) / numrevs
+ compratio = totalrawsize / totalsize
+
+ basedfmtstr = '%%%dd\n'
+ basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
+
+ def dfmtstr(max):
+ return basedfmtstr % len(str(max))
+ def pcfmtstr(max, padding=0):
+ return basepcfmtstr % (len(str(max)), ' ' * padding)
+
+ def pcfmt(value, total):
+ return (value, 100 * float(value) / total)
+
+ ui.write('format : %d\n' % format)
+ ui.write('flags : %s\n' % ', '.join(flags))
+
+ ui.write('\n')
+ fmt = pcfmtstr(totalsize)
+ fmt2 = dfmtstr(totalsize)
+ ui.write('revisions : ' + fmt2 % numrevs)
+ ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
+ ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
+ ui.write('revisions : ' + fmt2 % numrevs)
+ ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
+ ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
+ ui.write('revision size : ' + fmt2 % totalsize)
+ ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
+ ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
+
+ ui.write('\n')
+ fmt = dfmtstr(max(avgchainlen, compratio))
+ ui.write('avg chain length : ' + fmt % avgchainlen)
+ ui.write('compression ratio : ' + fmt % compratio)
+
+ if format > 0:
+ ui.write('\n')
+ ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
+ % tuple(datasize))
+ ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
+ % tuple(fullsize))
+ ui.write('delta size (min/max/avg) : %d / %d / %d\n'
+ % tuple(deltasize))
+
+ if numdeltas > 0:
+ ui.write('\n')
+ fmt = pcfmtstr(numdeltas)
+ fmt2 = pcfmtstr(numdeltas, 4)
+ ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
+ if numprev > 0:
+ ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
+ ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
+ ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
+ if gdelta:
+ ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
+ ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
+ ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
+
+@command('debugrevspec', [], ('REVSPEC'))
+def debugrevspec(ui, repo, expr):
+ '''parse and apply a revision specification'''
+ if ui.verbose:
+ tree = revset.parse(expr)[0]
+ ui.note(tree, "\n")
+ newtree = revset.findaliases(ui, tree)
+ if newtree != tree:
+ ui.note(newtree, "\n")
+ func = revset.match(ui, expr)
+ for c in func(repo, range(len(repo))):
+ ui.write("%s\n" % c)
+
+@command('debugsetparents', [], _('REV1 [REV2]'))
+def debugsetparents(ui, repo, rev1, rev2=None):
+ """manually set the parents of the current working directory
+
+ This is useful for writing repository conversion tools, but should
+ be used with care.
+
+ Returns 0 on success.
+ """
+
+ r1 = scmutil.revsingle(repo, rev1).node()
+ r2 = scmutil.revsingle(repo, rev2, 'null').node()
+
+ wlock = repo.wlock()
+ try:
+ repo.dirstate.setparents(r1, r2)
+ finally:
+ wlock.release()
+
+@command('debugstate',
+ [('', 'nodates', None, _('do not display the saved mtime')),
+ ('', 'datesort', None, _('sort by saved mtime'))],
+ _('[OPTION]...'))
+def debugstate(ui, repo, nodates=None, datesort=None):
+ """show the contents of the current dirstate"""
+ timestr = ""
+ showdate = not nodates
+ if datesort:
+ keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
+ else:
+ keyfunc = None # sort by filename
+ for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
+ if showdate:
+ if ent[3] == -1:
+ # Pad or slice to locale representation
+ locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
+ time.localtime(0)))
+ timestr = 'unset'
+ timestr = (timestr[:locale_len] +
+ ' ' * (locale_len - len(timestr)))
+ else:
+ timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
+ time.localtime(ent[3]))
+ if ent[1] & 020000:
+ mode = 'lnk'
+ else:
+ mode = '%3o' % (ent[1] & 0777)
+ ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
+ for f in repo.dirstate.copies():
+ ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
+
+@command('debugsub',
+ [('r', 'rev', '',
+ _('revision to check'), _('REV'))],
+ _('[-r REV] [REV]'))
+def debugsub(ui, repo, rev=None):
+ ctx = scmutil.revsingle(repo, rev, None)
+ for k, v in sorted(ctx.substate.items()):
+ ui.write('path %s\n' % k)
+ ui.write(' source %s\n' % v[0])
+ ui.write(' revision %s\n' % v[1])
+
+@command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
+def debugwalk(ui, repo, *pats, **opts):
+ """show how files match on given patterns"""
+ m = scmutil.match(repo[None], pats, opts)
+ items = list(repo.walk(m))
+ if not items:
+ return
+ fmt = 'f %%-%ds %%-%ds %%s' % (
+ max([len(abs) for abs in items]),
+ max([len(m.rel(abs)) for abs in items]))
+ for abs in items:
+ line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
+ ui.write("%s\n" % line.rstrip())
+
+@command('debugwireargs',
+ [('', 'three', '', 'three'),
+ ('', 'four', '', 'four'),
+ ('', 'five', '', 'five'),
+ ] + remoteopts,
+ _('REPO [OPTIONS]... [ONE [TWO]]'))
+def debugwireargs(ui, repopath, *vals, **opts):
+ repo = hg.peer(ui, opts, repopath)
+ for opt in remoteopts:
+ del opts[opt[1]]
+ args = {}
+ for k, v in opts.iteritems():
+ if v:
+ args[k] = v
+ # run twice to check that we don't mess up the stream for the next command
+ res1 = repo.debugwireargs(*vals, **args)
+ res2 = repo.debugwireargs(*vals, **args)
+ ui.write("%s\n" % res1)
+ if res1 != res2:
+ ui.warn("%s\n" % res2)
+
+@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]...'))
+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::
+ 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 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.
+ """
+
+ 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 util.Abort(msg)
+ elif change:
+ node2 = scmutil.revsingle(repo, change, None).node()
+ node1 = repo[node2].p1().node()
+ else:
+ node1, node2 = scmutil.revpair(repo, revs)
+
+ if reverse:
+ node1, node2 = node2, node1
+
+ diffopts = patch.diffopts(ui, opts)
+ m = scmutil.match(repo[node2], pats, opts)
+ cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
+ listsubrepos=opts.get('subrepos'))
+
+@command('^export',
+ [('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,
+ _('[OPTION]... [-o OUTFILESPEC] REV...'))
+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.
+
+ The information shown in the changeset header is: author, date,
+ branch name (if non-default), changeset hash, parent(s) and commit
+ comment.
+
+ .. note::
+ 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 format string. The formatting rules are as follows:
+
+ :``%%``: 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
+
+ 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.
+
+ 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
+
+ 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.
+ """
+ changesets += tuple(opts.get('rev', []))
+ if not changesets:
+ raise util.Abort(_("export requires at least one changeset"))
+ revs = scmutil.revrange(repo, changesets)
+ if len(revs) > 1:
+ ui.note(_('exporting patches:\n'))
+ else:
+ ui.note(_('exporting patch:\n'))
+ cmdutil.export(repo, revs, template=opts.get('output'),
+ switch_parent=opts.get('switch_parent'),
+ opts=patch.diffopts(ui, opts))
+
+@command('^forget', walkopts, _('[OPTION]... FILE...'))
+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 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.
+ """
+
+ if not pats:
+ raise util.Abort(_('no files specified'))
+
+ m = scmutil.match(repo[None], pats, opts)
+ s = repo.status(match=m, clean=True)
+ forget = sorted(s[0] + s[1] + s[3] + s[6])
+ errs = 0
+
+ for f in m.files():
+ if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
+ if os.path.exists(m.rel(f)):
+ ui.warn(_('not removing %s: file is already untracked\n')
+ % m.rel(f))
+ errs = 1
+
+ for f in forget:
+ if ui.verbose or not m.exact(f):
+ ui.status(_('removing %s\n') % m.rel(f))
+
+ repo[None].forget(forget)
+ return errs
+
+@command(
+ 'graft',
+ [('c', 'continue', False, _('resume interrupted graft')),
+ ('e', 'edit', False, _('invoke editor on commit messages')),
+ ('D', 'currentdate', False,
+ _('record the current date as commit date')),
+ ('U', 'currentuser', False,
+ _('record the current user as committer'), _('DATE'))]
+ + commitopts2 + mergetoolopts,
+ _('[OPTION]... REVISION...'))
+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 a graft merge results in conflicts, the graft process is
+ aborted 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.
+
+ .. note::
+ The -c/--continue option does not reapply earlier options.
+
+ .. 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 tip
+
+ Returns 0 on successful completion.
+ '''
+
+ 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" % util.makedate()
+
+ editor = None
+ if opts.get('edit'):
+ editor = cmdutil.commitforceeditor
+
+ cont = False
+ if opts['continue']:
+ cont = True
+ if revs:
+ raise util.Abort(_("can't specify --continue and revisions"))
+ # read in unfinished revisions
+ try:
+ nodes = repo.opener.read('graftstate').splitlines()
+ revs = [repo[node].rev() for node in nodes]
+ except IOError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+ raise util.Abort(_("no graft state found, can't continue"))
+ else:
+ cmdutil.bailifchanged(repo)
+ if not revs:
+ raise util.Abort(_('no revisions specified'))
+ revs = scmutil.revrange(repo, revs)
+
+ # check for merges
+ for ctx in repo.set('%ld and merge()', revs):
+ ui.warn(_('skipping ungraftable merge revision %s\n') % ctx.rev())
+ revs.remove(ctx.rev())
+ if not revs:
+ return -1
+
+ # check for ancestors of dest branch
+ for ctx in repo.set('::. and %ld', revs):
+ ui.warn(_('skipping ancestor revision %s\n') % ctx.rev())
+ revs.remove(ctx.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')
+ for ctx in repo.set("::. - ::%ld", revs):
+ n = ctx.extra().get('source')
+ if n in ids:
+ r = repo[n].rev()
+ if r in revs:
+ ui.warn(_('skipping already grafted revision %s\n') % r)
+ revs.remove(r)
+ elif ids[n] in revs:
+ ui.warn(_('skipping already grafted revision %s '
+ '(same origin %d)\n') % (ids[n], r))
+ revs.remove(ids[n])
+ elif ctx.hex() in ids:
+ r = ids[ctx.hex()]
+ ui.warn(_('skipping already grafted revision %s '
+ '(was grafted from %d)\n') % (r, ctx.rev()))
+ revs.remove(r)
+ if not revs:
+ return -1
+
+ for pos, ctx in enumerate(repo.set("%ld", revs)):
+ current = repo['.']
+ ui.status(_('grafting revision %s\n') % ctx.rev())
+
+ # we don't merge the first commit when continuing
+ if not cont:
+ # perform the graft merge with p1(rev) as 'ancestor'
+ try:
+ # ui.forcemerge is an internal variable, do not document
+ repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ stats = mergemod.update(repo, ctx.node(), True, True, False,
+ ctx.p1().node())
+ finally:
+ ui.setconfig('ui', 'forcemerge', '')
+ # drop the second merge parent
+ repo.dirstate.setparents(current.node(), nullid)
+ repo.dirstate.write()
+ # fix up dirstate for copies and renames
+ cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid)
+ # report any conflicts
+ if stats and stats[3] > 0:
+ # write out state for --continue
+ nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
+ repo.opener.write('graftstate', ''.join(nodelines))
+ raise util.Abort(
+ _("unresolved conflicts, can't continue"),
+ hint=_('use hg resolve and hg graft --continue'))
+ else:
+ cont = False
+
+ # commit
+ source = ctx.extra().get('source')
+ if not source:
+ source = ctx.hex()
+ extra = {'source': source}
+ user = ctx.user()
+ if opts.get('user'):
+ user = opts['user']
+ date = ctx.date()
+ if opts.get('date'):
+ date = opts['date']
+ repo.commit(text=ctx.description(), user=user,
+ date=date, extra=extra, editor=editor)
+
+ # remove state when we complete successfully
+ if os.path.exists(repo.join('graftstate')):
+ util.unlinkpath(repo.join('graftstate'))
+
+ return 0
+
+@command('grep',
+ [('0', 'print0', None, _('end fields with NUL')),
+ ('', 'all', None, _('print all revisions that match')),
+ ('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')),
+ ('u', 'user', None, _('list the author (long with -v)')),
+ ('d', 'date', None, _('list the date (short with -q)')),
+ ] + walkopts,
+ _('[OPTION]... PATTERN [FILE]...'))
+def grep(ui, repo, pattern, *pats, **opts):
+ """search for a pattern in specified files and revisions
+
+ Search revisions of files for a regular expression.
+
+ This command behaves differently than Unix grep. It only accepts
+ Python/Perl regexps. It searches repository history, not the
+ working directory. It always prints the revision number in which a
+ match appears.
+
+ By default, grep only prints output for the first revision of a
+ 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 --all flag.
+
+ Returns 0 if a match is found, 1 otherwise.
+ """
+ reflags = 0
+ if opts.get('ignore_case'):
+ reflags |= re.I
+ try:
+ regexp = re.compile(pattern, reflags)
+ except re.error, inst:
+ ui.warn(_("grep: invalid match pattern: %s\n") % 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 True:
+ 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
+
+ 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 == 'insert':
+ for i in xrange(blo, bhi):
+ yield ('+', b[i])
+ elif tag == 'delete':
+ for i in xrange(alo, ahi):
+ yield ('-', a[i])
+ elif tag == 'replace':
+ for i in xrange(alo, ahi):
+ yield ('-', a[i])
+ for i in xrange(blo, bhi):
+ yield ('+', b[i])
+
+ def display(fn, ctx, pstates, states):
+ rev = ctx.rev()
+ datefunc = ui.quiet and util.shortdate or util.datestr
+ found = False
+ filerevmatches = {}
+ def binary():
+ flog = getfile(fn)
+ return util.binary(flog.read(ctx.filenode(fn)))
+
+ if opts.get('all'):
+ iter = difflinestates(pstates, states)
+ else:
+ iter = [('', l) for l in states]
+ for change, l in iter:
+ cols = [fn, str(rev)]
+ before, match, after = None, None, None
+ if opts.get('line_number'):
+ cols.append(str(l.linenum))
+ if opts.get('all'):
+ cols.append(change)
+ if opts.get('user'):
+ cols.append(ui.shortuser(ctx.user()))
+ if opts.get('date'):
+ cols.append(datefunc(ctx.date()))
+ if opts.get('files_with_matches'):
+ c = (fn, rev)
+ if c in filerevmatches:
+ continue
+ filerevmatches[c] = 1
+ else:
+ before = l.line[:l.colstart]
+ match = l.line[l.colstart:l.colend]
+ after = l.line[l.colend:]
+ ui.write(sep.join(cols))
+ if before is not None:
+ if not opts.get('text') and binary():
+ ui.write(sep + " Binary file matches")
+ else:
+ ui.write(sep + before)
+ ui.write(match, label='grep.match')
+ ui.write(after)
+ ui.write(eol)
+ found = True
+ return found
+
+ skip = {}
+ revfiles = {}
+ matchfn = scmutil.match(repo[None], pats, opts)
+ found = False
+ follow = opts.get('follow')
+
+ 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
+
+ copied = flog.renamed(fnode)
+ copy = follow and copied and copied[0]
+ if copy:
+ copies.setdefault(rev, {})[fn] = copy
+ if fn in skip:
+ if copy:
+ skip[copy] = True
+ continue
+ files.append(fn)
+
+ if fn not in matches[rev]:
+ grepbody(fn, rev, flog.read(fnode))
+
+ 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
+
+ for ctx in cmdutil.walkchangerevs(repo, matchfn, 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[copy] = True
+ continue
+ pstates = matches.get(parent, {}).get(copy or fn, [])
+ if pstates or states:
+ r = display(fn, ctx, pstates, states)
+ found = found or r
+ if r and not opts.get('all'):
+ skip[fn] = True
+ if copy:
+ skip[copy] = True
+ del matches[rev]
+ del revfiles[rev]
+
+ 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,
+ _('[-ac] [-r STARTREV] [REV]...'))
+def heads(ui, repo, *branchrevs, **opts):
+ """show current repository heads or show branch heads
+
+ With no arguments, show all repository branch heads.
+
+ Repository "heads" are changesets with no child changesets. They are
+ where development generally takes place and are the usual targets
+ for update and merge operations. Branch heads are changesets that have
+ no child changeset on the same branch.
+
+ If one or more REVs are given, only branch heads on the branches
+ associated with the specified changesets are shown. This means
+ that you can use :hg:`heads foo` to see the heads on a branch
+ named ``foo``.
+
+ 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
+ changesets without children will be shown.
+
+ Returns 0 if matching heads are found, 1 if not.
+ """
+
+ start = None
+ if 'rev' in opts:
+ start = scmutil.revsingle(repo, opts['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[br].branch() for br in 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
+
+ heads = sorted(heads, key=lambda x: -x.rev())
+ displayer = cmdutil.show_changeset(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'))],
+ _('[-ec] [TOPIC]'))
+def help_(ui, name=None, unknowncmd=False, full=True, **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.
+ """
+
+ textwidth = min(ui.termwidth(), 80) - 2
+
+ def optrst(options):
+ data = []
+ multioccur = False
+ for option in options:
+ if len(option) == 5:
+ shortopt, longopt, default, desc, optlabel = option
+ else:
+ shortopt, longopt, default, desc = option
+ optlabel = _("VALUE") # default label
+
+ if _("DEPRECATED") in desc and not ui.verbose:
+ continue
+
+ so = ''
+ if shortopt:
+ so = '-' + shortopt
+ lo = '--' + longopt
+ if default:
+ desc += _(" (default: %s)") % default
+
+ if isinstance(default, list):
+ lo += " %s [+]" % optlabel
+ multioccur = True
+ elif (default is not None) and not isinstance(default, bool):
+ lo += " %s" % optlabel
+
+ data.append((so, lo, desc))
+
+ rst = minirst.maketable(data, 1)
+
+ if multioccur:
+ rst += _("\n[+] marked option can be specified multiple times\n")
+
+ return rst
+
+ # list all option lists
+ def opttext(optlist, width):
+ rst = ''
+ if not optlist:
+ return ''
+
+ for title, options in optlist:
+ rst += '\n%s\n' % title
+ if options:
+ rst += "\n"
+ rst += optrst(options)
+ rst += '\n'
+
+ return '\n' + minirst.format(rst, width)
+
+ def addglobalopts(optlist, aliases):
+ if ui.quiet:
+ return []
+
+ if ui.verbose:
+ optlist.append((_("global options:"), globalopts))
+ if name == 'shortlist':
+ optlist.append((_('use "hg help" for the full list '
+ 'of commands'), ()))
+ else:
+ if name == 'shortlist':
+ msg = _('use "hg help" for the full list of commands '
+ 'or "hg -v" for details')
+ elif name and not full:
+ msg = _('use "hg help %s" to show the full help text' % name)
+ elif aliases:
+ msg = _('use "hg -v help%s" to show builtin aliases and '
+ 'global options') % (name and " " + name or "")
+ else:
+ msg = _('use "hg -v help %s" to show more info') % name
+ optlist.append((msg, ()))
+
+ def helpcmd(name):
+ try:
+ aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
+ except error.AmbiguousCommand, inst:
+ # py3k fix: except vars can't be used outside the scope of the
+ # except block, nor can be used inside a lambda. python issue4617
+ prefix = inst.args[0]
+ select = lambda c: c.lstrip('^').startswith(prefix)
+ helplist(select)
+ return
+
+ # check if it's an invalid alias and display its error if it is
+ if getattr(entry[0], 'badalias', False):
+ if not unknowncmd:
+ entry[0](ui)
+ return
+
+ rst = ""
+
+ # synopsis
+ if len(entry) > 2:
+ if entry[2].startswith('hg'):
+ rst += "%s\n" % entry[2]
+ else:
+ rst += 'hg %s %s\n' % (aliases[0], entry[2])
+ else:
+ rst += 'hg %s\n' % aliases[0]
+
+ # aliases
+ if full and not ui.quiet and len(aliases) > 1:
+ rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
+
+ # description
+ doc = gettext(entry[0].__doc__)
+ if not doc:
+ doc = _("(no help text available)")
+ if util.safehasattr(entry[0], 'definition'): # aliased command
+ if entry[0].definition.startswith('!'): # shell alias
+ doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
+ else:
+ doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
+ if ui.quiet or not full:
+ doc = doc.splitlines()[0]
+ rst += "\n" + doc + "\n"
+
+ # check if this command shadows a non-trivial (multi-line)
+ # extension help text
+ try:
+ mod = extensions.find(name)
+ doc = gettext(mod.__doc__) or ''
+ if '\n' in doc.strip():
+ msg = _('use "hg help -e %s" to show help for '
+ 'the %s extension') % (name, name)
+ rst += '\n%s\n' % msg
+ except KeyError:
+ pass
+
+ # options
+ if not ui.quiet and entry[1]:
+ rst += '\noptions:\n\n'
+ rst += optrst(entry[1])
+
+ if ui.verbose:
+ rst += '\nglobal options:\n\n'
+ rst += optrst(globalopts)
+
+ keep = ui.verbose and ['verbose'] or []
+ formatted, pruned = minirst.format(rst, textwidth, keep=keep)
+ ui.write(formatted)
+
+ if not ui.verbose:
+ if not full:
+ ui.write(_('\nuse "hg help %s" to show the full help text\n')
+ % name)
+ elif not ui.quiet:
+ ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
+
+
+ def helplist(select=None):
+ # list of commands
+ if name == "shortlist":
+ header = _('basic commands:\n\n')
+ else:
+ header = _('list of commands:\n\n')
+
+ h = {}
+ cmds = {}
+ for c, e in table.iteritems():
+ f = c.split("|", 1)[0]
+ if select and not select(f):
+ continue
+ if (not select and name != 'shortlist' and
+ e[0].__module__ != __name__):
+ continue
+ if name == "shortlist" and not f.startswith("^"):
+ continue
+ f = f.lstrip("^")
+ if not ui.debugflag and f.startswith("debug"):
+ continue
+ doc = e[0].__doc__
+ if doc and 'DEPRECATED' in doc and not ui.verbose:
+ continue
+ doc = gettext(doc)
+ if not doc:
+ doc = _("(no help text available)")
+ h[f] = doc.splitlines()[0].rstrip()
+ cmds[f] = c.lstrip("^")
+
+ if not h:
+ ui.status(_('no commands defined\n'))
+ return
+
+ ui.status(header)
+ fns = sorted(h)
+ m = max(map(len, fns))
+ for f in fns:
+ if ui.verbose:
+ commands = cmds[f].replace("|",", ")
+ ui.write(" %s:\n %s\n"%(commands, h[f]))
+ else:
+ ui.write('%s\n' % (util.wrap(h[f], textwidth,
+ initindent=' %-*s ' % (m, f),
+ hangindent=' ' * (m + 4))))
+
+ if not name:
+ text = help.listexts(_('enabled extensions:'), extensions.enabled())
+ if text:
+ ui.write("\n%s" % minirst.format(text, textwidth))
+
+ ui.write(_("\nadditional help topics:\n\n"))
+ topics = []
+ for names, header, doc in help.helptable:
+ topics.append((sorted(names, key=len, reverse=True)[0], header))
+ topics_len = max([len(s[0]) for s in topics])
+ for t, desc in topics:
+ ui.write(" %-*s %s\n" % (topics_len, t, desc))
+
+ optlist = []
+ addglobalopts(optlist, True)
+ ui.write(opttext(optlist, textwidth))
+
+ def helptopic(name):
+ for names, header, doc in help.helptable:
+ if name in names:
+ break
+ else:
+ raise error.UnknownCommand(name)
+
+ # description
+ if not doc:
+ doc = _("(no help text available)")
+ if util.safehasattr(doc, '__call__'):
+ doc = doc()
+
+ ui.write("%s\n\n" % header)
+ ui.write("%s" % minirst.format(doc, textwidth, indent=4))
+ try:
+ cmdutil.findcmd(name, table)
+ ui.write(_('\nuse "hg help -c %s" to see help for '
+ 'the %s command\n') % (name, name))
+ except error.UnknownCommand:
+ pass
+
+ def helpext(name):
+ try:
+ mod = extensions.find(name)
+ doc = gettext(mod.__doc__) or _('no help text available')
+ except KeyError:
+ mod = None
+ doc = extensions.disabledext(name)
+ if not doc:
+ raise error.UnknownCommand(name)
+
+ if '\n' not in doc:
+ head, tail = doc, ""
+ else:
+ head, tail = doc.split('\n', 1)
+ ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
+ if tail:
+ ui.write(minirst.format(tail, textwidth))
+ ui.status('\n')
+
+ if mod:
+ try:
+ ct = mod.cmdtable
+ except AttributeError:
+ ct = {}
+ modcmds = set([c.split('|', 1)[0] for c in ct])
+ helplist(modcmds.__contains__)
+ else:
+ ui.write(_('use "hg help extensions" for information on enabling '
+ 'extensions\n'))
+
+ def helpextcmd(name):
+ cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
+ doc = gettext(mod.__doc__).splitlines()[0]
+
+ msg = help.listexts(_("'%s' is provided by the following "
+ "extension:") % cmd, {ext: doc}, indent=4)
+ ui.write(minirst.format(msg, textwidth))
+ ui.write('\n')
+ ui.write(_('use "hg help extensions" for information on enabling '
+ 'extensions\n'))
+
+ if name and name != 'shortlist':
+ i = None
+ if unknowncmd:
+ queries = (helpextcmd,)
+ elif opts.get('extension'):
+ queries = (helpext,)
+ elif opts.get('command'):
+ queries = (helpcmd,)
+ else:
+ queries = (helptopic, helpcmd, helpext, helpextcmd)
+ for f in queries:
+ try:
+ f(name)
+ i = None
+ break
+ except error.UnknownCommand, inst:
+ i = inst
+ if i:
+ raise i
+ else:
+ # program name
+ ui.status(_("Mercurial Distributed SCM\n"))
+ ui.status('\n')
+ helplist()
+
+
+@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'))],
+ _('[-nibtB] [-r REV] [SOURCE]'))
+def identify(ui, repo, source=None, rev=None,
+ num=None, id=None, branch=None, tags=None, bookmarks=None):
+ """identify the working copy 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.
+
+ Specifying a path to a repository root or Mercurial bundle will
+ cause lookup to operate on that repository/bundle.
+
+ .. container:: verbose
+
+ 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 http://selenic.com/hg/
+
+ Returns 0 if successful.
+ """
+
+ if not repo and not source:
+ raise util.Abort(_("there is no Mercurial repository here "
+ "(.hg not found)"))
+
+ hexfunc = ui.debugflag and hex or short
+ default = not (num or id or branch or tags or bookmarks)
+ output = []
+ revs = []
+
+ if source:
+ source, branches = hg.parseurl(ui.expandpath(source))
+ repo = hg.peer(ui, {}, source)
+ revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
+
+ if not repo.local():
+ if num or branch or tags:
+ raise util.Abort(
+ _("can't query remote revision number, branch, or tags"))
+ if not rev and revs:
+ rev = revs[0]
+ if not rev:
+ rev = "tip"
+
+ remoterev = repo.lookup(rev)
+ if default or id:
+ output = [hexfunc(remoterev)]
+
+ def getbms():
+ bms = []
+
+ if 'bookmarks' in repo.listkeys('namespaces'):
+ hexremoterev = hex(remoterev)
+ bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
+ if bmr == hexremoterev]
+
+ return bms
+
+ 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:
+ if not rev:
+ ctx = repo[None]
+ parents = ctx.parents()
+ changed = ""
+ if default or id or num:
+ changed = util.any(repo.status()) and "+" or ""
+ if default or id:
+ output = ["%s%s" %
+ ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
+ if num:
+ output.append("%s%s" %
+ ('+'.join([str(p.rev()) for p in parents]), changed))
+ else:
+ ctx = scmutil.revsingle(repo, rev)
+ if default or id:
+ output = [hexfunc(ctx.node())]
+ if num:
+ output.append(str(ctx.rev()))
+
+ 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(ctx.tags())
+ 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(ctx.tags())
+
+ if bookmarks:
+ output.extend(ctx.bookmarks())
+
+ ui.write("%s\n" % ' '.join(output))
+
+@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')),
+ ('', 'no-commit', None,
+ _("don't commit, just update the working directory")),
+ ('', 'bypass', None,
+ _("apply patch without touching the working directory")),
+ ('', 'exact', None,
+ _('apply patch to the nodes from which it was generated')),
+ ('', 'import-branch', None,
+ _('use any branch information in patch (implied by --exact)'))] +
+ commitopts + commitopts2 + similarityopts,
+ _('[OPTION]... PATCH...'))
+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).
+
+ If there are outstanding changes in the working directory, import
+ will abort unless given the -f/--force flag.
+
+ 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 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 may happen due to character set problems or other
+ deficiencies in the text patch format.
+
+ Use --bypass to apply and commit patches directly to the
+ repository, not touching the working directory. Without --exact,
+ patches will be applied on top of the working directory parent
+ revision.
+
+ With -s/--similarity, hg will attempt to discover renames and
+ copies in the patch in the same way as 'addremove'.
+
+ To read a patch from standard input, use "-" as the patch name. If
+ a URL is specified, the patch will be downloaded from it.
+ 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 http://www.selenic.com/hg/rev/5ca8c111e9aa
+
+ - import all the patches in an Unix-style mbox::
+
+ hg import incoming-patches.mbox
+
+ - attempt to exactly restore an exported changeset (not always
+ possible)::
+
+ hg import --exact proposed-fix.patch
+
+ Returns 0 on success.
+ """
+
+ if not patch1:
+ raise util.Abort(_('need at least one patch to import'))
+
+ patches = (patch1,) + patches
+
+ date = opts.get('date')
+ if date:
+ opts['date'] = util.parsedate(date)
+
+ editor = cmdutil.commiteditor
+ if opts.get('edit'):
+ editor = cmdutil.commitforceeditor
+
+ update = not opts.get('bypass')
+ if not update and opts.get('no_commit'):
+ raise util.Abort(_('cannot use --no-commit with --bypass'))
+ try:
+ sim = float(opts.get('similarity') or 0)
+ except ValueError:
+ raise util.Abort(_('similarity must be a number'))
+ if sim < 0 or sim > 100:
+ raise util.Abort(_('similarity must be between 0 and 100'))
+ if sim and not update:
+ raise util.Abort(_('cannot use --similarity with --bypass'))
+
+ if (opts.get('exact') or not opts.get('force')) and update:
+ cmdutil.bailifchanged(repo)
+
+ base = opts["base"]
+ strip = opts["strip"]
+ wlock = lock = tr = None
+ msgs = []
+
+ def checkexact(repo, n, nodeid):
+ if opts.get('exact') and hex(n) != nodeid:
+ repo.rollback()
+ raise util.Abort(_('patch is damaged or loses information'))
+
+ def tryone(ui, hunk, parents):
+ tmpname, message, user, date, branch, nodeid, p1, p2 = \
+ patch.extract(ui, hunk)
+
+ if not tmpname:
+ return (None, None)
+ msg = _('applied to working directory')
+
+ try:
+ cmdline_message = cmdutil.logmessage(ui, opts)
+ if cmdline_message:
+ # pickup the cmdline msg
+ message = cmdline_message
+ elif message:
+ # pickup the patch msg
+ message = message.strip()
+ else:
+ # launch the editor
+ message = None
+ ui.debug('message:\n%s\n' % message)
+
+ if len(parents) == 1:
+ parents.append(repo[nullid])
+ if opts.get('exact'):
+ if not nodeid or not p1:
+ raise util.Abort(_('not a Mercurial patch'))
+ p1 = repo[p1]
+ p2 = repo[p2 or nullid]
+ elif p2:
+ try:
+ p1 = repo[p1]
+ p2 = repo[p2]
+ # Without any options, consider p2 only if the
+ # patch is being applied on top of the recorded
+ # first parent.
+ if p1 != parents[0]:
+ p1 = parents[0]
+ p2 = repo[nullid]
+ except error.RepoError:
+ p1, p2 = parents
+ else:
+ p1, p2 = parents
+
+ n = None
+ if update:
+ if p1 != parents[0]:
+ hg.clean(repo, p1.node())
+ if p2 != parents[1]:
+ repo.dirstate.setparents(p1.node(), p2.node())
+
+ if opts.get('exact') or opts.get('import_branch'):
+ repo.dirstate.setbranch(branch or 'default')
+
+ files = set()
+ patch.patch(ui, repo, tmpname, strip=strip, files=files,
+ eolmode=None, similarity=sim / 100.0)
+ files = list(files)
+ if opts.get('no_commit'):
+ if message:
+ msgs.append(message)
+ else:
+ if opts.get('exact') or p2:
+ # If you got here, you either use --force and know what
+ # you are doing or used --exact or a merge patch while
+ # being updated to its first parent.
+ m = None
+ else:
+ m = scmutil.matchfiles(repo, files or [])
+ n = repo.commit(message, opts.get('user') or user,
+ opts.get('date') or date, match=m,
+ editor=editor)
+ checkexact(repo, n, nodeid)
+ else:
+ if opts.get('exact') or opts.get('import_branch'):
+ branch = branch or 'default'
+ else:
+ branch = p1.branch()
+ store = patch.filestore()
+ try:
+ files = set()
+ try:
+ patch.patchrepo(ui, repo, p1, store, tmpname, strip,
+ files, eolmode=None)
+ except patch.PatchError, e:
+ raise util.Abort(str(e))
+ memctx = patch.makememctx(repo, (p1.node(), p2.node()),
+ message,
+ opts.get('user') or user,
+ opts.get('date') or date,
+ branch, files, store,
+ editor=cmdutil.commiteditor)
+ repo.savecommitmessage(memctx.description())
+ n = memctx.commit()
+ checkexact(repo, n, nodeid)
+ finally:
+ store.close()
+ if n:
+ # i18n: refers to a short changeset id
+ msg = _('created %s') % short(n)
+ return (msg, n)
+ finally:
+ os.unlink(tmpname)
+
+ try:
+ try:
+ wlock = repo.wlock()
+ lock = repo.lock()
+ tr = repo.transaction('import')
+ parents = repo.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 = url.open(ui, patchurl)
+
+ haspatch = False
+ for hunk in patch.split(patchfile):
+ (msg, node) = tryone(ui, hunk, parents)
+ if msg:
+ haspatch = True
+ ui.note(msg + '\n')
+ if update or opts.get('exact'):
+ parents = repo.parents()
+ else:
+ parents = [repo[node]]
+
+ if not haspatch:
+ raise util.Abort(_('%s: no diffs found') % patchurl)
+
+ tr.close()
+ if msgs:
+ repo.savecommitmessage('\n* * *\n'.join(msgs))
+ except:
+ # wlock.release() indirectly calls dirstate.write(): since
+ # we're crashing, we do not want to change the working dir
+ # parent after all, so make sure it writes nothing
+ repo.dirstate.invalidate()
+ raise
+ finally:
+ if tr:
+ tr.release()
+ release(lock, wlock)
+
+@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]'))
+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
+ if a pull at the time you issued this command.
+
+ For remote repository, using --bundle avoids downloading the
+ changesets twice if the incoming is followed by a pull.
+
+ See pull for valid source format details.
+
+ Returns 0 if there are incoming changes, 1 otherwise.
+ """
+ if opts.get('bundle') and opts.get('subrepos'):
+ raise util.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.status(_('comparing with %s\n') % util.hidepassword(source))
+ return bookmarks.diff(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]'))
+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.
+ """
+ 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]...'))
+def locate(ui, repo, *pats, **opts):
+ """locate files matching specific patterns
+
+ 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.
+
+ Returns 0 if a match is found, 1 otherwise.
+ """
+ end = opts.get('print0') and '\0' or '\n'
+ rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
+
+ ret = 1
+ m = scmutil.match(repo[rev], pats, opts, default='relglob')
+ m.bad = lambda x, y: False
+ for abs in repo[rev].walk(m):
+ if not rev and abs not in repo.dirstate:
+ continue
+ if opts.get('fullpath'):
+ ui.write(repo.wjoin(abs), end)
+ else:
+ ui.write(((pats and m.rel(abs)) or 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')),
+ ('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 range'), _('REV')),
+ ('', 'removed', None, _('include revisions where files were removed')),
+ ('m', 'only-merges', None, _('show only merges')),
+ ('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')),
+ ('', 'hidden', False, _('show hidden changesets')),
+ ] + logopts + walkopts,
+ _('[OPTION]... [FILE]'))
+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 or descendants 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.
+
+ .. note::
+ log -p/--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, log FILE may omit duplicate changes
+ made on branches and will not show deletions. To see all
+ changes including duplicates and deletions, use the --removed
+ switch.
+
+ .. 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"
+
+ - check if a given changeset is included is 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"
+
+ See :hg:`help dates` for a list of formats valid for -d/--date.
+
+ See :hg:`help revisions` and :hg:`help revsets` for more about
+ specifying revisions.
+
+ Returns 0 on success.
+ """
+
+ matchfn = scmutil.match(repo[None], pats, opts)
+ limit = cmdutil.loglimit(opts)
+ count = 0
+
+ endrev = None
+ if opts.get('copies') and opts.get('rev'):
+ endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
+
+ df = False
+ if opts["date"]:
+ df = util.matchdate(opts["date"])
+
+ branches = opts.get('branch', []) + opts.get('only_branch', [])
+ opts['branch'] = [repo.lookupbranch(b) for b in branches]
+
+ displayer = cmdutil.show_changeset(ui, repo, opts, True)
+ def prep(ctx, fns):
+ rev = ctx.rev()
+ parents = [p for p in repo.changelog.parentrevs(rev)
+ if p != nullrev]
+ if opts.get('no_merges') and len(parents) == 2:
+ return
+ if opts.get('only_merges') and len(parents) != 2:
+ return
+ if opts.get('branch') and ctx.branch() not in opts['branch']:
+ return
+ if not opts.get('hidden') and ctx.hidden():
+ return
+ if df and not df(ctx.date()[0]):
+ return
+ if opts['user'] and not [k for k in opts['user']
+ if k.lower() in ctx.user().lower()]:
+ return
+ if opts.get('keyword'):
+ for k in [kw.lower() for kw in opts['keyword']]:
+ if (k in ctx.user().lower() or
+ k in ctx.description().lower() or
+ k in " ".join(ctx.files()).lower()):
+ break
+ else:
+ return
+
+ copies = None
+ if opts.get('copies') and rev:
+ copies = []
+ getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
+ for fn in ctx.files():
+ rename = getrenamed(fn, rev)
+ if rename:
+ copies.append((fn, rename[0]))
+
+ revmatchfn = None
+ if opts.get('patch') or opts.get('stat'):
+ if opts.get('follow') or opts.get('follow_first'):
+ # note: this might be wrong when following through merges
+ revmatchfn = scmutil.match(repo[None], fns, default='path')
+ else:
+ revmatchfn = matchfn
+
+ displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+
+ for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
+ if count == limit:
+ break
+ if displayer.flush(ctx.rev()):
+ count += 1
+ displayer.close()
+
+@command('manifest',
+ [('r', 'rev', '', _('revision to display'), _('REV')),
+ ('', 'all', False, _("list files from all revisions"))],
+ _('[-r REV]'))
+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.
+ """
+ if opts.get('all'):
+ if rev or node:
+ raise util.Abort(_("can't specify a revision with --all"))
+
+ res = []
+ prefix = "data/"
+ suffix = ".i"
+ plen = len(prefix)
+ slen = len(suffix)
+ lock = repo.lock()
+ try:
+ for fn, b, size in repo.store.datafiles():
+ if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
+ res.append(fn[plen:-slen])
+ finally:
+ lock.release()
+ for f in sorted(res):
+ ui.write("%s\n" % f)
+ return
+
+ if rev and node:
+ raise util.Abort(_("please specify just one revision"))
+
+ if not node:
+ node = rev
+
+ decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
+ ctx = scmutil.revsingle(repo, node)
+ for f in ctx:
+ if ui.debugflag:
+ ui.write("%40s " % hex(ctx.manifest()[f]))
+ if ui.verbose:
+ ui.write(decor[ctx.flags(f)])
+ ui.write("%s\n" % f)
+
+@command('^merge',
+ [('f', 'force', None, _('force a merge with outstanding changes')),
+ ('r', 'rev', '', _('revision to merge'), _('REV')),
+ ('P', 'preview', None,
+ _('review revisions to merge (no merge is performed)'))
+ ] + mergetoolopts,
+ _('[-P] [-f] [[-r] REV]'))
+def merge(ui, repo, node=None, **opts):
+ """merge working directory with another revision
+
+ 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 with must be provided.
+
+ :hg:`resolve` must be used to resolve unresolved files.
+
+ To undo an uncommitted merge, use :hg:`update --clean .` 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.
+ """
+
+ if opts.get('rev') and node:
+ raise util.Abort(_("please specify just one revision"))
+ if not node:
+ node = opts.get('rev')
+
+ if not node:
+ branch = repo[None].branch()
+ bheads = repo.branchheads(branch)
+ if len(bheads) > 2:
+ raise util.Abort(_("branch '%s' has %d heads - "
+ "please merge with an explicit rev")
+ % (branch, len(bheads)),
+ hint=_("run 'hg heads .' to see heads"))
+
+ parent = repo.dirstate.p1()
+ if len(bheads) == 1:
+ if len(repo.heads()) > 1:
+ raise util.Abort(_("branch '%s' has one head - "
+ "please merge with an explicit rev")
+ % branch,
+ hint=_("run 'hg heads' to see all heads"))
+ msg = _('there is nothing to merge')
+ if parent != repo.lookup(repo[None].branch()):
+ msg = _('%s - use "hg update" instead') % msg
+ raise util.Abort(msg)
+
+ if parent not in bheads:
+ raise util.Abort(_('working directory not at a head revision'),
+ hint=_("use 'hg update' or merge with an "
+ "explicit revision"))
+ node = parent == bheads[0] and bheads[-1] or bheads[0]
+ else:
+ node = scmutil.revsingle(repo, node).node()
+
+ if opts.get('preview'):
+ # find nodes that are ancestors of p2 but not of p1
+ p1 = repo.lookup('.')
+ p2 = repo.lookup(node)
+ nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
+
+ displayer = cmdutil.show_changeset(ui, repo, opts)
+ for node in nodes:
+ displayer.show(repo[node])
+ displayer.close()
+ return 0
+
+ try:
+ # ui.forcemerge is an internal variable, do not document
+ repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ return hg.merge(repo, node, force=opts.get('force'))
+ finally:
+ ui.setconfig('ui', 'forcemerge', '')
+
+@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]'))
+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.
+
+ Returns 0 if there are outgoing changes, 1 otherwise.
+ """
+
+ if opts.get('bookmarks'):
+ dest = ui.expandpath(dest or 'default-push', dest or 'default')
+ dest, branches = hg.parseurl(dest, opts.get('branch'))
+ 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))
+ return bookmarks.diff(ui, other, repo)
+
+ repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
+ 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]'))
+def parents(ui, repo, file_=None, **opts):
+ """show the parents of the working directory or revision
+
+ 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.
+
+ Returns 0 on success.
+ """
+
+ ctx = scmutil.revsingle(repo, opts.get('rev'), None)
+
+ if file_:
+ m = scmutil.match(ctx, (file_,), opts)
+ if m.anypats() or len(m.files()) != 1:
+ raise util.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 util.Abort(_("'%s' not found in manifest!") % file_)
+ fl = repo.file(file_)
+ p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
+ else:
+ p = [cp.node() for cp in ctx.parents()]
+
+ displayer = cmdutil.show_changeset(ui, repo, opts)
+ for n in p:
+ if n != nullid:
+ displayer.show(repo[n])
+ displayer.close()
+
+@command('paths', [], _('[NAME]'))
+def paths(ui, repo, search=None):
+ """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 that
+ ``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.
+
+ Returns 0 on success.
+ """
+ if search:
+ for name, path in ui.configitems("paths"):
+ if name == search:
+ ui.status("%s\n" % util.hidepassword(path))
+ return
+ if not ui.quiet:
+ ui.warn(_("not found!\n"))
+ return 1
+ else:
+ for name, path in ui.configitems("paths"):
+ if ui.quiet:
+ ui.write("%s\n" % name)
+ else:
+ ui.write("%s = %s\n" % (name, util.hidepassword(path)))
+
+def postincoming(ui, repo, modheads, optupdate, checkout):
+ if modheads == 0:
+ return
+ if optupdate:
+ try:
+ return hg.update(repo, checkout)
+ except util.Abort, inst:
+ ui.warn(_("not updating: %s\n" % str(inst)))
+ return 0
+ if 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"))
+ else:
+ ui.status(_("(run 'hg update' to get a working copy)\n"))
+
+@command('^pull',
+ [('u', 'update', None,
+ _('update to new branch head if changesets 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]'))
+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.
+
+ 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.
+
+ Returns 0 on success, 1 if an update had unresolved files.
+ """
+ source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
+ other = hg.peer(repo, opts, source)
+ ui.status(_('pulling from %s\n') % util.hidepassword(source))
+ revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
+
+ if opts.get('bookmark'):
+ if not revs:
+ revs = []
+ rb = other.listkeys('bookmarks')
+ for b in opts['bookmark']:
+ if b not in rb:
+ raise util.Abort(_('remote bookmark %s not found!') % b)
+ revs.append(rb[b])
+
+ if revs:
+ try:
+ revs = [other.lookup(rev) for rev in revs]
+ except error.CapabilityError:
+ err = _("other repository doesn't support revision lookup, "
+ "so a rev cannot be specified.")
+ raise util.Abort(err)
+
+ modheads = repo.pull(other, heads=revs, force=opts.get('force'))
+ bookmarks.updatefromremote(ui, repo, other)
+ if checkout:
+ checkout = str(repo.changelog.rev(other.lookup(checkout)))
+ repo._subtoppath = source
+ try:
+ ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
+
+ finally:
+ del repo._subtoppath
+
+ # update specified bookmarks
+ if opts.get('bookmark'):
+ for b in opts['bookmark']:
+ # explicit pull overrides local bookmark if any
+ ui.status(_("importing bookmark %s\n") % b)
+ repo._bookmarks[b] = repo[rb[b]].node()
+ bookmarks.write(repo)
+
+ 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')),
+ ] + remoteopts,
+ _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
+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.
+
+ Use -f/--force to override the default behavior and push all
+ changesets on all branches.
+
+ If -r/--rev is used, the specified revision and all its ancestors
+ will be pushed to the remote repository.
+
+ Please see :hg:`help urls` for important details about ``ssh://``
+ URLs. If DESTINATION is omitted, a default path will be used.
+
+ Returns 0 if push was successful, 1 if nothing to push.
+ """
+
+ if opts.get('bookmark'):
+ for b in opts['bookmark']:
+ # translate -B options to -r so changesets get pushed
+ 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")
+
+ dest = ui.expandpath(dest or 'default-push', dest or 'default')
+ dest, branches = hg.parseurl(dest, opts.get('branch'))
+ 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.lookup(rev) for rev in revs]
+
+ 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):
+ if not c.sub(s).push(opts.get('force')):
+ return False
+ finally:
+ del repo._subtoppath
+ result = repo.push(other, opts.get('force'), revs=revs,
+ newbranch=opts.get('new_branch'))
+
+ result = (result == 0)
+
+ if opts.get('bookmark'):
+ rb = other.listkeys('bookmarks')
+ for b in opts['bookmark']:
+ # explicit push overrides remote bookmark if any
+ if b in repo._bookmarks:
+ ui.status(_("exporting bookmark %s\n") % b)
+ new = repo[b].hex()
+ elif b in rb:
+ ui.status(_("deleting remote bookmark %s\n") % b)
+ new = '' # delete
+ else:
+ ui.warn(_('bookmark %s does not exist on the local '
+ 'or remote repository!\n') % b)
+ return 2
+ old = rb.get(b, '')
+ r = other.pushkey('bookmarks', b, old, new)
+ if not r:
+ ui.warn(_('updating bookmark %s failed!\n') % b)
+ if not result:
+ result = 2
+
+ return result
+
+@command('recover', [])
+def recover(ui, repo):
+ """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.
+ """
+ if repo.recover():
+ return hg.verify(repo)
+ return 1
+
+@command('^remove|rm',
+ [('A', 'after', None, _('record delete for missing files')),
+ ('f', 'force', None,
+ _('remove (and delete) file even if added or modified')),
+ ] + walkopts,
+ _('[OPTION]... FILE...'))
+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):
+
+ ======= == == == ==
+ A C M !
+ ======= == == == ==
+ none W RD W R
+ -f R RD RD R
+ -A W W W R
+ -Af R R R R
+ ======= == == == ==
+
+ Note that remove never deletes files in Added [A] state from the
+ working directory, not even if option --force is specified.
+
+ Returns 0 on success, 1 if any warnings encountered.
+ """
+
+ ret = 0
+ after, force = opts.get('after'), opts.get('force')
+ if not pats and not after:
+ raise util.Abort(_('no files specified'))
+
+ m = scmutil.match(repo[None], pats, opts)
+ s = repo.status(match=m, clean=True)
+ modified, added, deleted, clean = s[0], s[1], s[3], s[6]
+
+ for f in m.files():
+ if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
+ if os.path.exists(m.rel(f)):
+ ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
+ ret = 1
+
+ if force:
+ list = modified + deleted + clean + added
+ elif after:
+ list = deleted
+ for f in modified + added + clean:
+ ui.warn(_('not removing %s: file still exists (use -f'
+ ' to force removal)\n') % m.rel(f))
+ ret = 1
+ else:
+ list = deleted + clean
+ for f in modified:
+ ui.warn(_('not removing %s: file is modified (use -f'
+ ' to force removal)\n') % m.rel(f))
+ ret = 1
+ for f in added:
+ ui.warn(_('not removing %s: file has been marked for add'
+ ' (use forget to undo)\n') % m.rel(f))
+ ret = 1
+
+ for f in sorted(list):
+ if ui.verbose or not m.exact(f):
+ ui.status(_('removing %s\n') % m.rel(f))
+
+ wlock = repo.wlock()
+ try:
+ if not after:
+ for f in list:
+ if f in added:
+ continue # we never unlink added files on remove
+ try:
+ util.unlinkpath(repo.wjoin(f))
+ except OSError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+ repo[None].forget(list)
+ finally:
+ wlock.release()
+
+ return ret
+
+@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'))
+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.
+ """
+ wlock = repo.wlock(False)
+ try:
+ return cmdutil.copy(ui, repo, pats, opts, rename=True)
+ finally:
+ wlock.release()
+
+@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'))]
+ + mergetoolopts + walkopts,
+ _('[OPTION]... [FILE]...'))
+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).
+
+ The resolve command can be used in the following ways:
+
+ - :hg:`resolve [--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.
+
+ Note that 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.
+
+ Returns 0 on success, 1 if any files fail a resolve attempt.
+ """
+
+ all, mark, unmark, show, nostatus = \
+ [opts.get(o) for o in 'all mark unmark list no_status'.split()]
+
+ if (show and (mark or unmark)) or (mark and unmark):
+ raise util.Abort(_("too many options specified"))
+ if pats and all:
+ raise util.Abort(_("can't specify --all and patterns"))
+ if not (all or pats or show or mark or unmark):
+ raise util.Abort(_('no files or directories specified; '
+ 'use --all to remerge all files'))
+
+ ms = mergemod.mergestate(repo)
+ m = scmutil.match(repo[None], pats, opts)
+ ret = 0
+
+ for f in ms:
+ if m(f):
+ if show:
+ if nostatus:
+ ui.write("%s\n" % f)
+ else:
+ ui.write("%s %s\n" % (ms[f].upper(), f),
+ label='resolve.' +
+ {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
+ elif mark:
+ ms.mark(f, "r")
+ elif unmark:
+ ms.mark(f, "u")
+ else:
+ wctx = repo[None]
+ mctx = wctx.parents()[-1]
+
+ # backup pre-resolve (merge uses .orig for its own purposes)
+ a = repo.wjoin(f)
+ util.copyfile(a, a + ".resolve")
+
+ try:
+ # resolve file
+ ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ if ms.resolve(f, wctx, mctx):
+ ret = 1
+ finally:
+ ui.setconfig('ui', 'forcemerge', '')
+
+ # replace filemerge's .orig file with our resolve file
+ util.rename(a + ".resolve", a + ".orig")
+
+ ms.commit()
+ 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')),
+ ] + walkopts + dryrunopts,
+ _('[OPTION]... [-r REV] [NAME]...'))
+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 a merge (and lose your changes), use :hg:`update --clean .`.
+
+ 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.
+
+ See :hg:`help dates` for a list of formats valid for -d/--date.
+
+ Returns 0 on success.
+ """
+
+ if opts.get("date"):
+ if opts.get("rev"):
+ raise util.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 util.Abort(_('uncommitted merge with no revision specified'),
+ hint=_('use "hg update" or see "hg help revert"'))
+
+ ctx = scmutil.revsingle(repo, opts.get('rev'))
+ node = ctx.node()
+
+ if not pats and not opts.get('all'):
+ 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 util.Abort(msg, hint=hint)
+ dirty = util.any(repo.status())
+ if node != parent:
+ if dirty:
+ hint = _("uncommitted changes, use --all to discard all"
+ " changes, or 'hg update %s' to update") % ctx.rev()
+ else:
+ hint = _("use --all to revert all files,"
+ " or 'hg update %s' to update") % ctx.rev()
+ elif dirty:
+ hint = _("uncommitted changes, use --all to discard all changes")
+ else:
+ hint = _("use --all to revert all files")
+ raise util.Abort(msg, hint=hint)
+
+ mf = ctx.manifest()
+ if node == parent:
+ pmf = mf
+ else:
+ pmf = None
+
+ # need all matching names in dirstate and manifest of target rev,
+ # so have to walk both. do not print errors if files exist in one
+ # but not other.
+
+ names = {}
+
+ wlock = repo.wlock()
+ try:
+ # walk dirstate.
+
+ m = scmutil.match(repo[None], pats, opts)
+ m.bad = lambda x, y: False
+ for abs in repo.walk(m):
+ names[abs] = m.rel(abs), m.exact(abs)
+
+ # walk target manifest.
+
+ def badfn(path, msg):
+ if path in names:
+ return
+ if path in repo[node].substate:
+ ui.warn("%s: %s\n" % (m.rel(path),
+ 'reverting subrepos is unsupported'))
+ return
+ path_ = path + '/'
+ for f in names:
+ if f.startswith(path_):
+ return
+ ui.warn("%s: %s\n" % (m.rel(path), msg))
+
+ m = scmutil.match(repo[node], pats, opts)
+ m.bad = badfn
+ for abs in repo[node].walk(m):
+ if abs not in names:
+ names[abs] = m.rel(abs), m.exact(abs)
+
+ m = scmutil.matchfiles(repo, names)
+ changes = repo.status(match=m)[:4]
+ modified, added, removed, deleted = map(set, changes)
+
+ # if f is a rename, also revert the source
+ cwd = repo.getcwd()
+ for f in added:
+ src = repo.dirstate.copied(f)
+ if src and src not in names and repo.dirstate[src] == 'r':
+ removed.add(src)
+ names[src] = (repo.pathto(src, cwd), True)
+
+ def removeforget(abs):
+ if repo.dirstate[abs] == 'a':
+ return _('forgetting %s\n')
+ return _('removing %s\n')
+
+ revert = ([], _('reverting %s\n'))
+ add = ([], _('adding %s\n'))
+ remove = ([], removeforget)
+ undelete = ([], _('undeleting %s\n'))
+
+ disptable = (
+ # dispatch table:
+ # file state
+ # action if in target manifest
+ # action if not in target manifest
+ # make backup if in target manifest
+ # make backup if not in target manifest
+ (modified, revert, remove, True, True),
+ (added, revert, remove, True, False),
+ (removed, undelete, None, False, False),
+ (deleted, revert, remove, False, False),
+ )
+
+ for abs, (rel, exact) in sorted(names.items()):
+ mfentry = mf.get(abs)
+ target = repo.wjoin(abs)
+ def handle(xlist, dobackup):
+ xlist[0].append(abs)
+ if (dobackup and not opts.get('no_backup') and
+ os.path.lexists(target)):
+ bakname = "%s.orig" % rel
+ ui.note(_('saving current version of %s as %s\n') %
+ (rel, bakname))
+ if not opts.get('dry_run'):
+ util.rename(target, bakname)
+ if ui.verbose or not exact:
+ msg = xlist[1]
+ if not isinstance(msg, basestring):
+ msg = msg(abs)
+ ui.status(msg % rel)
+ for table, hitlist, misslist, backuphit, backupmiss in disptable:
+ if abs not in table:
+ continue
+ # file has changed in dirstate
+ if mfentry:
+ handle(hitlist, backuphit)
+ elif misslist is not None:
+ handle(misslist, backupmiss)
+ break
+ else:
+ if abs not in repo.dirstate:
+ if mfentry:
+ handle(add, True)
+ elif exact:
+ ui.warn(_('file not managed: %s\n') % rel)
+ continue
+ # file has not changed in dirstate
+ if node == parent:
+ if exact:
+ ui.warn(_('no changes needed to %s\n') % rel)
+ continue
+ if pmf is None:
+ # only need parent manifest in this unlikely case,
+ # so do not read by default
+ pmf = repo[parent].manifest()
+ if abs in pmf and mfentry:
+ # if version of file is same in parent and target
+ # manifests, do nothing
+ if (pmf[abs] != mfentry or
+ pmf.flags(abs) != mf.flags(abs)):
+ handle(revert, False)
+ else:
+ handle(remove, False)
+
+ if not opts.get('dry_run'):
+ def checkout(f):
+ fc = ctx[f]
+ repo.wwrite(f, fc.data(), fc.flags())
+
+ audit_path = scmutil.pathauditor(repo.root)
+ for f in remove[0]:
+ if repo.dirstate[f] == 'a':
+ repo.dirstate.drop(f)
+ continue
+ audit_path(f)
+ try:
+ util.unlinkpath(repo.wjoin(f))
+ except OSError:
+ pass
+ repo.dirstate.remove(f)
+
+ normal = None
+ if node == parent:
+ # We're reverting to our parent. If possible, we'd like status
+ # to report the file as clean. We have to use normallookup for
+ # merges to avoid losing information about merged/dirty files.
+ if p2 != nullid:
+ normal = repo.dirstate.normallookup
+ else:
+ normal = repo.dirstate.normal
+ for f in revert[0]:
+ checkout(f)
+ if normal:
+ normal(f)
+
+ for f in add[0]:
+ checkout(f)
+ repo.dirstate.add(f)
+
+ normal = repo.dirstate.normallookup
+ if node == parent and p2 == nullid:
+ normal = repo.dirstate.normal
+ for f in undelete[0]:
+ checkout(f)
+ normal(f)
+
+ finally:
+ wlock.release()
+
+@command('rollback', dryrunopts +
+ [('f', 'force', False, _('ignore safety measures'))])
+def rollback(ui, repo, **opts):
+ """roll back the last transaction (dangerous)
+
+ 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. 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
+
+ It's possible to lose data with rollback: commit, update back to
+ an older changeset, and then rollback. The update removes the
+ changes you committed from the working directory, and rollback
+ removes them from history. To avoid data loss, you must pass
+ --force in this case.
+
+ 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.
+ """
+ return repo.rollback(dryrun=opts.get('dry_run'),
+ force=opts.get('force'))
+
+@command('root', [])
+def root(ui, repo):
+ """print the root (top) of the current working directory
+
+ Print the root directory of the current repository.
+
+ Returns 0 on success.
+ """
+ ui.write(repo.root + "\n")
+
+@command('^serve',
+ [('A', 'accesslog', '', _('name of access log file to write to'),
+ _('FILE')),
+ ('d', 'daemon', None, _('run server in background')),
+ ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
+ ('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')),
+ ('', 'cmdserver', '', _('for remote clients'), _('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'))],
+ _('[OPTION]...'))
+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.
+ """
+
+ if opts["stdio"] and opts["cmdserver"]:
+ raise util.Abort(_("cannot use --stdio with --cmdserver"))
+
+ def checkrepo():
+ if repo is None:
+ raise error.RepoError(_("There is no Mercurial repository here"
+ " (.hg not found)"))
+
+ if opts["stdio"]:
+ checkrepo()
+ s = sshserver.sshserver(ui, repo)
+ s.serve_forever()
+
+ if opts["cmdserver"]:
+ checkrepo()
+ s = commandserver.server(ui, repo, opts["cmdserver"])
+ return s.serve()
+
+ # this way we can check if something was given in the command-line
+ if opts.get('port'):
+ opts['port'] = util.getport(opts.get('port'))
+
+ baseui = repo and repo.baseui or ui
+ optlist = ("name templates style address port prefix ipv6"
+ " accesslog errorlog certificate encoding")
+ for o in optlist.split():
+ val = opts.get(o, '')
+ if val in (None, ''): # should check against default options instead
+ continue
+ baseui.setconfig("web", o, val)
+ if repo and repo.ui != baseui:
+ repo.ui.setconfig("web", o, val)
+
+ o = opts.get('web_conf') or opts.get('webdir_conf')
+ if not o:
+ if not repo:
+ raise error.RepoError(_("There is no Mercurial repository"
+ " here (.hg not found)"))
+ o = repo.root
+
+ app = hgweb.hgweb(o, baseui=ui)
+
+ class service(object):
+ def init(self):
+ util.setsignalhandler()
+ self.httpd = hgweb.server.create_server(ui, app)
+
+ if opts['port'] and not ui.verbose:
+ return
+
+ if self.httpd.prefix:
+ prefix = self.httpd.prefix.strip('/') + '/'
+ else:
+ prefix = ''
+
+ port = ':%d' % self.httpd.port
+ if port == ':80':
+ port = ''
+
+ bindaddr = self.httpd.addr
+ if bindaddr == '0.0.0.0':
+ bindaddr = '*'
+ elif ':' in bindaddr: # IPv6
+ bindaddr = '[%s]' % bindaddr
+
+ fqaddr = self.httpd.fqaddr
+ if ':' in fqaddr:
+ fqaddr = '[%s]' % fqaddr
+ if opts['port']:
+ write = ui.status
+ else:
+ write = ui.write
+ write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
+ (fqaddr, port, prefix, bindaddr, self.httpd.port))
+
+ def run(self):
+ self.httpd.serve_forever()
+
+ service = service()
+
+ cmdutil.service(opts, initfn=service.init, runfn=service.run)
+
+@command('showconfig|debugconfig',
+ [('u', 'untrusted', None, _('show untrusted configuration options'))],
+ _('[-u] [NAME]...'))
+def showconfig(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.
+
+ With --debug, the source (filename and line number) is printed
+ for each config item.
+
+ Returns 0 on success.
+ """
+
+ for f in scmutil.rcpath():
+ ui.debug('read config from: %s\n' % f)
+ untrusted = bool(opts.get('untrusted'))
+ if values:
+ sections = [v for v in values if '.' not in v]
+ items = [v for v in values if '.' in v]
+ if len(items) > 1 or items and sections:
+ raise util.Abort(_('only one config item permitted'))
+ for section, name, value in ui.walkconfig(untrusted=untrusted):
+ value = str(value).replace('\n', '\\n')
+ sectname = section + '.' + name
+ if values:
+ for v in values:
+ if v == section:
+ ui.debug('%s: ' %
+ ui.configsource(section, name, untrusted))
+ ui.write('%s=%s\n' % (sectname, value))
+ elif v == sectname:
+ ui.debug('%s: ' %
+ ui.configsource(section, name, untrusted))
+ ui.write(value, '\n')
+ else:
+ ui.debug('%s: ' %
+ ui.configsource(section, name, untrusted))
+ ui.write('%s=%s\n' % (sectname, value))
+
+@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')),
+ ('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,
+ _('[OPTION]... [FILE]...'))
+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::
+ 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 listed as A (added)
+
+ .. container:: verbose
+
+ Examples:
+
+ - show changes in the working directory relative to a changeset:
+
+ hg status --rev 9353
+
+ - 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
+
+ Returns 0 on success.
+ """
+
+ revs = opts.get('rev')
+ change = opts.get('change')
+
+ if revs and change:
+ msg = _('cannot specify --rev and --change at the same time')
+ raise util.Abort(msg)
+ elif change:
+ node2 = repo.lookup(change)
+ node1 = repo[node2].p1().node()
+ else:
+ node1, node2 = scmutil.revpair(repo, revs)
+
+ cwd = (pats and repo.getcwd()) or ''
+ end = opts.get('print0') and '\0' or '\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:
+ show = ui.quiet and states[:4] or states[:5]
+
+ stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
+ 'ignored' in show, 'clean' in show, 'unknown' in show,
+ opts.get('subrepos'))
+ changestates = zip(states, 'MAR!?IC', stat)
+
+ if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
+ ctxn = repo[nullid]
+ ctx1 = repo[node1]
+ ctx2 = repo[node2]
+ added = stat[1]
+ if node2 is None:
+ added = stat[0] + stat[1] # merged?
+
+ for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
+ if k in added:
+ copy[k] = v
+ elif v in added:
+ copy[v] = k
+
+ for state, char, files in changestates:
+ if state in show:
+ format = "%s %%s%s" % (char, end)
+ if opts.get('no_status'):
+ format = "%%s%s" % end
+
+ for f in files:
+ ui.write(format % repo.pathto(f, cwd),
+ label='status.' + state)
+ if f in copy:
+ ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
+ label='status.copied')
+
+@command('^summary|sum',
+ [('', 'remote', None, _('check for push and pull'))], '[--remote]')
+def summary(ui, repo, **opts):
+ """summarize working directory state
+
+ This generates a brief summary of the working directory state,
+ including parents, branch, commit status, 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.
+ """
+
+ ctx = repo[None]
+ parents = ctx.parents()
+ pnode = parents[0].node()
+ marks = []
+
+ for p in parents:
+ # label with log.changeset (instead of log.parent) since this
+ # shows a working directory parent *changeset*:
+ ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
+ label='log.changeset')
+ 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)'))
+ ui.write('\n')
+ if p.description():
+ ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
+ label='log.summary')
+
+ branch = ctx.branch()
+ bheads = repo.branchheads(branch)
+ m = _('branch: %s\n') % branch
+ if branch != 'default':
+ ui.write(m, label='log.branch')
+ else:
+ ui.status(m, label='log.branch')
+
+ if marks:
+ current = repo._bookmarkcurrent
+ ui.write(_('bookmarks:'), label='log.bookmark')
+ if current is not None:
+ try:
+ marks.remove(current)
+ ui.write(' *' + current, label='bookmarks.current')
+ except ValueError:
+ # current bookmark not in parent ctx marks
+ pass
+ for m in marks:
+ ui.write(' ' + m, label='log.bookmark')
+ ui.write('\n', label='log.bookmark')
+
+ st = list(repo.status(unknown=True))[:6]
+
+ c = repo.dirstate.copies()
+ copied, renamed = [], []
+ for d, s in c.iteritems():
+ if s in st[2]:
+ st[2].remove(s)
+ renamed.append(d)
+ else:
+ copied.append(d)
+ if d in st[1]:
+ st[1].remove(d)
+ st.insert(3, renamed)
+ st.insert(4, copied)
+
+ ms = mergemod.mergestate(repo)
+ st.append([f for f in ms if ms[f] == 'u'])
+
+ subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
+ st.append(subs)
+
+ labels = [ui.label(_('%d modified'), 'status.modified'),
+ ui.label(_('%d added'), 'status.added'),
+ ui.label(_('%d removed'), 'status.removed'),
+ ui.label(_('%d renamed'), 'status.copied'),
+ ui.label(_('%d copied'), 'status.copied'),
+ ui.label(_('%d deleted'), 'status.deleted'),
+ ui.label(_('%d unknown'), 'status.unknown'),
+ ui.label(_('%d ignored'), 'status.ignored'),
+ ui.label(_('%d unresolved'), 'resolve.unresolved'),
+ ui.label(_('%d subrepos'), 'status.modified')]
+ t = []
+ for s, l in zip(st, labels):
+ if s:
+ t.append(l % len(s))
+
+ t = ', '.join(t)
+ cleanworkdir = False
+
+ if len(parents) > 1:
+ t += _(' (merge)')
+ elif branch != parents[0].branch():
+ t += _(' (new branch)')
+ elif (parents[0].extra().get('close') and
+ pnode in repo.branchheads(branch, closed=True)):
+ t += _(' (head closed)')
+ elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
+ t += _(' (clean)')
+ cleanworkdir = True
+ elif pnode not in bheads:
+ t += _(' (new branch head)')
+
+ if cleanworkdir:
+ ui.status(_('commit: %s\n') % t.strip())
+ else:
+ ui.write(_('commit: %s\n') % t.strip())
+
+ # all ancestors of branch heads - all ancestors of parent = new csets
+ new = [0] * len(repo)
+ cl = repo.changelog
+ for a in [cl.rev(n) for n in bheads]:
+ new[a] = 1
+ for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
+ new[a] = 1
+ for a in [p.rev() for p in parents]:
+ if a >= 0:
+ new[a] = 0
+ for a in cl.ancestors(*[p.rev() for p in parents]):
+ new[a] = 0
+ new = sum(new)
+
+ if new == 0:
+ ui.status(_('update: (current)\n'))
+ elif pnode not in bheads:
+ ui.write(_('update: %d new changesets (update)\n') % new)
+ else:
+ ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
+ (new, len(bheads)))
+
+ if opts.get('remote'):
+ t = []
+ source, branches = hg.parseurl(ui.expandpath('default'))
+ other = hg.peer(repo, {}, source)
+ revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
+ ui.debug('comparing with %s\n' % util.hidepassword(source))
+ repo.ui.pushbuffer()
+ commoninc = discovery.findcommonincoming(repo, other)
+ _common, incoming, _rheads = commoninc
+ repo.ui.popbuffer()
+ if incoming:
+ t.append(_('1 or more incoming'))
+
+ dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
+ revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
+ if source != dest:
+ other = hg.peer(repo, {}, dest)
+ commoninc = None
+ ui.debug('comparing with %s\n' % util.hidepassword(dest))
+ repo.ui.pushbuffer()
+ common, outheads = discovery.findcommonoutgoing(repo, other,
+ commoninc=commoninc)
+ repo.ui.popbuffer()
+ o = repo.changelog.findmissing(common=common, heads=outheads)
+ if o:
+ t.append(_('%d outgoing') % len(o))
+ if 'bookmarks' in other.listkeys('namespaces'):
+ lmarks = repo.listkeys('bookmarks')
+ rmarks = other.listkeys('bookmarks')
+ diff = set(rmarks) - set(lmarks)
+ if len(diff) > 0:
+ t.append(_('%d incoming bookmarks') % len(diff))
+ diff = set(lmarks) - set(rmarks)
+ if len(diff) > 0:
+ t.append(_('%d outgoing bookmarks') % len(diff))
+
+ if t:
+ ui.write(_('remote: %s\n') % (', '.join(t)))
+ else:
+ ui.status(_('remote: (synced)\n'))
+
+@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, _('edit commit message')),
+ ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
+ ] + commitopts2,
+ _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
+def tag(ui, repo, name1, *names, **opts):
+ """add one or more tags for the current or given revision
+
+ Name a particular revision using <name>.
+
+ 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, or tip if no revision is checked out.
+
+ 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.
+ """
+
+ rev_ = "."
+ names = [t.strip() for t in (name1,) + names]
+ if len(names) != len(set(names)):
+ raise util.Abort(_('tag names must be unique'))
+ for n in names:
+ if n in ['tip', '.', 'null']:
+ raise util.Abort(_("the name '%s' is reserved") % n)
+ if not n:
+ raise util.Abort(_('tag names cannot consist entirely of whitespace'))
+ if opts.get('rev') and opts.get('remove'):
+ raise util.Abort(_("--rev and --remove are incompatible"))
+ if opts.get('rev'):
+ rev_ = opts['rev']
+ message = opts.get('message')
+ if opts.get('remove'):
+ expectedtype = opts.get('local') and 'local' or 'global'
+ for n in names:
+ if not repo.tagtype(n):
+ raise util.Abort(_("tag '%s' does not exist") % n)
+ if repo.tagtype(n) != expectedtype:
+ if expectedtype == 'global':
+ raise util.Abort(_("tag '%s' is not a global tag") % n)
+ else:
+ raise util.Abort(_("tag '%s' is not a local tag") % n)
+ rev_ = nullid
+ 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 util.Abort(_("tag '%s' already exists "
+ "(use -f to force)") % n)
+ if not opts.get('local'):
+ p1, p2 = repo.dirstate.parents()
+ if p2 != nullid:
+ raise util.Abort(_('uncommitted merge'))
+ bheads = repo.branchheads()
+ if not opts.get('force') and bheads and p1 not in bheads:
+ raise util.Abort(_('not at a branch head (use -f to force)'))
+ r = scmutil.revsingle(repo, rev_).node()
+
+ if not message:
+ # we don't translate commit messages
+ message = ('Added tag %s for changeset %s' %
+ (', '.join(names), short(r)))
+
+ date = opts.get('date')
+ if date:
+ date = util.parsedate(date)
+
+ if opts.get('edit'):
+ message = ui.edit(message, ui.username())
+
+ repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
+
+@command('tags', [], '')
+def tags(ui, repo):
+ """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.
+
+ Returns 0 on success.
+ """
+
+ hexfunc = ui.debugflag and hex or short
+ tagtype = ""
+
+ for t, n in reversed(repo.tagslist()):
+ if ui.quiet:
+ ui.write("%s\n" % t, label='tags.normal')
+ continue
+
+ hn = hexfunc(n)
+ r = "%5d:%s" % (repo.changelog.rev(n), hn)
+ rev = ui.label(r, 'log.changeset')
+ spaces = " " * (30 - encoding.colwidth(t))
+
+ tag = ui.label(t, 'tags.normal')
+ if ui.verbose:
+ if repo.tagtype(t) == 'local':
+ tagtype = " local"
+ tag = ui.label(t, 'tags.local')
+ else:
+ tagtype = ""
+ ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
+
+@command('tip',
+ [('p', 'patch', None, _('show patch')),
+ ('g', 'git', None, _('use git extended diff format')),
+ ] + templateopts,
+ _('[-p] [-g]'))
+def tip(ui, repo, **opts):
+ """show the tip revision
+
+ 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.
+
+ Returns 0 on success.
+ """
+ displayer = cmdutil.show_changeset(ui, repo, opts)
+ displayer.show(repo[len(repo) - 1])
+ displayer.close()
+
+@command('unbundle',
+ [('u', 'update', None,
+ _('update to new branch head if changesets were unbundled'))],
+ _('[-u] FILE...'))
+def unbundle(ui, repo, fname1, *fnames, **opts):
+ """apply one or more changegroup files
+
+ Apply one or more compressed changegroup files generated by the
+ bundle command.
+
+ Returns 0 on success, 1 if an update has unresolved files.
+ """
+ fnames = (fname1,) + fnames
+
+ lock = repo.lock()
+ wc = repo['.']
+ try:
+ for fname in fnames:
+ f = url.open(ui, fname)
+ gen = changegroup.readbundle(f, fname)
+ modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
+ lock=lock)
+ bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
+ finally:
+ lock.release()
+ return postincoming(ui, repo, modheads, opts.get('update'), None)
+
+@command('^update|up|checkout|co',
+ [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
+ ('c', 'check', None,
+ _('update across branches if no uncommitted changes')),
+ ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
+ ('r', 'rev', '', _('revision'), _('REV'))],
+ _('[-c] [-C] [-d DATE] [[-r] REV]'))
+def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
+ """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.
+
+ If the changeset is not a descendant of the working directory's
+ parent, 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.
+
+ Update sets the working directory's parent revison to the specified
+ changeset (see :hg:`help parents`).
+
+ The following rules apply when the working directory contains
+ uncommitted changes:
+
+ 1. If neither -c/--check nor -C/--clean 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 -c/--check option, the update is aborted and the
+ uncommitted changes are preserved.
+
+ 3. With the -C/--clean option, uncommitted changes are discarded and
+ the working directory is updated to the requested changeset.
+
+ 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.
+ """
+ if rev and node:
+ raise util.Abort(_("please specify just one revision"))
+
+ if rev is None or rev == '':
+ rev = node
+
+ # if we defined a bookmark, we have to remember the original bookmark name
+ brev = rev
+ rev = scmutil.revsingle(repo, rev, rev).rev()
+
+ if check and clean:
+ raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
+
+ if check:
+ # we could use dirty() but we can ignore merge and branch trivia
+ c = repo[None]
+ if c.modified() or c.added() or c.removed():
+ raise util.Abort(_("uncommitted local changes"))
+
+ if date:
+ if rev is not None:
+ raise util.Abort(_("you can't specify a revision and a date"))
+ rev = cmdutil.finddate(ui, repo, date)
+
+ if clean or check:
+ ret = hg.clean(repo, rev)
+ else:
+ ret = hg.update(repo, rev)
+
+ if brev in repo._bookmarks:
+ bookmarks.setcurrent(repo, brev)
+
+ return ret
+
+@command('verify', [])
+def verify(ui, repo):
+ """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.
+
+ Returns 0 on success, 1 if errors are encountered.
+ """
+ return hg.verify(repo)
+
+@command('version', [])
+def version_(ui):
+ """output version and copyright information"""
+ ui.write(_("Mercurial Distributed SCM (version %s)\n")
+ % util.version())
+ ui.status(_(
+ "(see http://mercurial.selenic.com for more information)\n"
+ "\nCopyright (C) 2005-2011 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"
+ ))
+
+norepo = ("clone init version help debugcommands debugcomplete"
+ " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
+ " debugknown debuggetbundle debugbundle")
+optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
+ " debugdata debugindex debugindexdot debugrevlog")