/mercurial/commands.py
Python | 6031 lines | 5849 code | 94 blank | 88 comment | 167 complexity | 3fc03cff89d81608bde73ee2eb8b4e16 MD5 | raw file
Possible License(s): GPL-2.0
Large files files are truncated, but you can click here to view the full 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 _
- import os, re, difflib, time, tempfile, errno
- import sys
- import hg, scmutil, util, revlog, copies, error, bookmarks
- import patch, help, encoding, templatekw, discovery
- import archival, changegroup, cmdutil, hbisect
- import sshserver, hgweb, commandserver
- import extensions
- from hgweb import server as hgweb_server
- import merge as mergemod
- import minirst, revset, fileset
- import dagparser, context, simplemerge, graphmod
- import random
- import setdiscovery, treediscovery, dagutil, pvec, localrepo
- import phases, obsolete, exchange
- table = {}
- command = cmdutil.command(table)
- # Space delimited list of commands that don't require local repositories.
- # This should be populated by passing norepo=True into the @command decorator.
- norepo = ''
- # Space delimited list of commands that optionally require local repositories.
- # This should be populated by passing optionalrepo=True into the @command
- # decorator.
- optionalrepo = ''
- # Space delimited list of commands that will examine arguments looking for
- # a repository. This should be populated by passing inferrepo=True into the
- # @command decorator.
- inferrepo = ''
- # 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')),
- ('', 'hidden', False, _('consider hidden changesets')),
- ]
- 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 (DEPRECATED)'), _('STYLE')),
- ('T', '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')),
- ('G', 'graph', None, _("show the revision DAG")),
- ] + 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'))
- ]
- diffwsopts = [
- ('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')),
- ]
- diffopts2 = [
- ('p', 'show-function', None, _('show which function each change is in')),
- ('', 'reverse', None, _('produce a diff that undoes the changes')),
- ] + diffwsopts + [
- ('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]...'),
- inferrepo=True)
- def add(ui, repo, *pats, **opts):
- """add the specified files on the next commit
- Schedule files to be version controlled and added to the
- repository.
- The files will be added to the repository at the next commit. To
- undo an add before that, see :hg:`forget`.
- If no names are given, add all files to the repository.
- .. 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="", explicitonly=False)
- return rejected and 1 or 0
- @command('addremove',
- similarityopts + walkopts + dryrunopts,
- _('[OPTION]... [FILE]...'),
- inferrepo=True)
- def addremove(ui, repo, *pats, **opts):
- """add all new files, delete all missing files
- Add all new files and remove all missing files from the
- repository.
- New files are ignored if they match any of the patterns in
- ``.hgignore``. As with add, these changes take effect at the next
- commit.
- Use the -s/--similarity option to detect renamed files. This
- option takes a percentage between 0 (disabled) and 100 (files must
- be identical) as its parameter. With a parameter greater than 0,
- this compares every removed file with every added file and records
- those similar enough as renames. Detecting renamed files this way
- can be expensive. After using this option, :hg:`status -C` can be
- used to check which files were identified as moved or renamed. If
- not specified, -s/--similarity defaults to 100 and only renames of
- identical files are detected.
- 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'))
- ] + diffwsopts + walkopts,
- _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
- inferrepo=True)
- def annotate(ui, repo, *pats, **opts):
- """show changeset information by line for each file
- List changes in files, showing the revision id responsible for
- each line
- This command is useful for discovering when a change was made and
- by whom.
- 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'))
- hexfn = ui.debugflag and hex or short
- opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
- ('number', ' ', lambda x: str(x[0].rev())),
- ('changeset', ' ', lambda x: hexfn(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')
- diffopts = patch.diffopts(ui, opts, section='annotate')
- 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,
- diffopts=diffopts)
- 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]))
- if lines and not lines[-1][1].endswith('\n'):
- ui.write('\n')
- @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')),
- ('e', 'edit', False, _('invoke editor on commit messages')),
- ] + 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, 1 if nothing to backout or there are unresolved
- files.
- '''
- 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.checkunfinished(repo)
- cmdutil.bailifchanged(repo)
- node = scmutil.revsingle(repo, rev).node()
- op1, op2 = repo.dirstate.parents()
- if node not in repo.changelog.commonancestorsheads(op1, node):
- raise util.Abort(_('cannot backout change that is not an ancestor'))
- 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
- wlock = repo.wlock()
- try:
- branch = repo.dirstate.branch()
- bheads = repo.branchheads(branch)
- rctx = scmutil.revsingle(repo, hex(parent))
- if not opts.get('merge') and op1 != node:
- try:
- ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
- 'backout')
- stats = mergemod.update(repo, parent, True, True, False,
- node, False)
- repo.setparents(op1, op2)
- hg._showstats(repo, stats)
- if stats[3]:
- repo.ui.status(_("use 'hg resolve' to retry unresolved "
- "file merges\n"))
- else:
- msg = _("changeset %s backed out, "
- "don't forget to commit.\n")
- ui.status(msg % short(node))
- return stats[3] > 0
- finally:
- ui.setconfig('ui', 'forcemerge', '', '')
- else:
- hg.clean(repo, node, show_stats=False)
- repo.dirstate.setbranch(branch)
- cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
- def commitfunc(ui, repo, message, match, opts):
- e = cmdutil.getcommiteditor(**opts)
- if not message:
- # we don't translate commit messages
- message = "Backed out changeset %s" % short(node)
- e = cmdutil.getcommiteditor(edit=True)
- return repo.commit(message, opts.get('user'), opts.get('date'),
- match, editor=e)
- newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
- if not newnode:
- ui.status(_("nothing changed\n"))
- return 1
- cmdutil.commitstatus(repo, newnode, branch, bheads)
- def nice(node):
- return '%d:%s' % (repo.changelog.rev(node), short(node))
- ui.status(_('changeset %s backs out changeset %s\n') %
- (nice(repo.changelog.tip()), nice(node)))
- if opts.get('merge') and op1 != node:
- hg.clean(repo, op1, show_stats=False)
- ui.status(_('merging with changeset %s\n')
- % nice(repo.changelog.tip()))
- try:
- ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
- 'backout')
- return hg.merge(repo, hex(repo.changelog.tip()))
- finally:
- ui.setconfig('ui', 'forcemerge', '', '')
- finally:
- wlock.release()
- 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.
- The environment variable HG_NODE will contain the ID of the
- changeset being tested. The exit status of the command will be
- used to mark revisions as good or bad: status 0 means good, 125
- means to skip the revision, 127 (command not found) will abort the
- bisection, and any other non-zero exit status means the revision
- is bad.
- .. container:: verbose
- Some examples:
- - start a bisection with known bad revision 34, and good revision 12::
- hg bisect --bad 34
- hg bisect --good 12
- - advance the current bisection by marking current revision as good or
- bad::
- hg bisect --good
- hg bisect --bad
- - mark the current revision, or a known revision, to be skipped (e.g. if
- that revision is not usable because of another issue)::
- hg bisect --skip
- hg bisect --skip 23
- - skip all revisions that do not touch directories ``foo`` or ``bar``::
- hg bisect --skip "!( file('path:foo') & file('path:bar') )"
- - forget the current bisection::
- hg bisect --reset
- - use 'make && make tests' to automatically find the first broken
- revision::
- hg bisect --reset
- hg bisect --bad 34
- hg bisect --good 12
- hg bisect --command "make && make tests"
- - see all changesets whose states are already known in the current
- bisection::
- hg log -r "bisect(pruned)"
- - see the changeset currently being bisected (especially useful
- if running with -U/--noupdate)::
- hg log -r "bisect(current)"
- - see all changesets that took part in the current bisection::
- hg log -r "bisect(range)"
- - you can even get a nice graph::
- hg log --graph -r "bisect(range)"
- See :hg:`help 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'))
- cmdutil.checkunfinished(repo)
- 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
- if noupdate:
- try:
- node = state['current'][0]
- except LookupError:
- raise util.Abort(_('current bisect revision is unknown - '
- 'start a new bisect to fix'))
- else:
- node, p2 = repo.dirstate.parents()
- if p2 != nullid:
- raise util.Abort(_('current bisect revision is a merge'))
- try:
- while changesets:
- # update state
- state['current'] = [node]
- hbisect.save_state(repo, state)
- status = util.system(command,
- environ={'HG_NODE': hex(node)},
- 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, node)
- 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, bgood = hbisect.bisect(repo.changelog, state)
- # update to next check
- node = nodes[0]
- if not noupdate:
- cmdutil.bailifchanged(repo)
- hg.clean(repo, node, show_stats=False)
- finally:
- state['current'] = [node]
- hbisect.save_state(repo, state)
- print_result(nodes, bgood)
- 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))
- state['current'] = [extendnode.node()]
- hbisect.save_state(repo, state)
- 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))
- state['current'] = [node]
- hbisect.save_state(repo, state)
- if not noupdate:
- cmdutil.bailifchanged(repo)
- return hg.clean(repo, node)
- @command('bookmarks|bookmark',
- [('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, _('mark a bookmark inactive'))],
- _('hg bookmarks [OPTIONS]... [NAME]...'))
- def bookmark(ui, repo, *names, **opts):
- '''create a new bookmark or list existing bookmarks
- Bookmarks are labels on changesets to help track lines of development.
- Bookmarks are unversioned and can be moved, renamed and deleted.
- Deleting or moving a bookmark has no effect on the associated changesets.
- Creating or updating to a bookmark causes it to be marked as 'active'.
- Active bookmarks are indicated with a '*'.
- When a commit is made, an active bookmark will advance to the new commit.
- A plain :hg:`update` will also advance an active bookmark, if possible.
- Updating away from a bookmark will cause it to be deactivated.
- Bookmarks can be pushed and pulled between repositories (see
- :hg:`help push` and :hg:`help pull`). If a shared bookmark has
- diverged, a new 'divergent bookmark' of the form 'name@path' will
- be created. Using :hg:'merge' will resolve the divergence.
- A bookmark named '@' has the special property that :hg:`clone` will
- check it out by default if it exists.
- .. container:: verbose
- Examples:
- - create an active bookmark for a new line of development::
- hg book new-feature
- - create an inactive bookmark as a place marker::
- hg book -i reviewed
- - create an inactive bookmark on another changeset::
- hg book -r .^ tested
- - move the '@' bookmark from another branch::
- hg book -f @
- '''
- force = opts.get('force')
- rev = opts.get('rev')
- delete = opts.get('delete')
- rename = opts.get('rename')
- inactive = opts.get('inactive')
- def checkformat(mark):
- mark = mark.strip()
- if not mark:
- raise util.Abort(_("bookmark names cannot consist entirely of "
- "whitespace"))
- scmutil.checknewlabel(repo, mark, 'bookmark')
- return mark
- def checkconflict(repo, mark, cur, force=False, target=None):
- if mark in marks and not force:
- if target:
- if marks[mark] == target and target == cur:
- # re-activating a bookmark
- return
- anc = repo.changelog.ancestors([repo[target].rev()])
- bmctx = repo[marks[mark]]
- divs = [repo[b].node() for b in marks
- if b.split('@', 1)[0] == mark.split('@', 1)[0]]
- # allow resolving a single divergent bookmark even if moving
- # the bookmark across branches when a revision is specified
- # that contains a divergent bookmark
- if bmctx.rev() not in anc and target in divs:
- bookmarks.deletedivergent(repo, [target], mark)
- return
- deletefrom = [b for b in divs
- if repo[b].rev() in anc or b == target]
- bookmarks.deletedivergent(repo, deletefrom, mark)
- if bookmarks.validdest(repo, bmctx, repo[target]):
- ui.status(_("moving bookmark '%s' forward from %s\n") %
- (mark, short(bmctx.node())))
- return
- raise util.Abort(_("bookmark '%s' already exists "
- "(use -f to force)") % mark)
- if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
- and not force):
- raise util.Abort(
- _("a bookmark cannot have the name of an existing branch"))
- if delete and rename:
- raise util.Abort(_("--delete and --rename are incompatible"))
- if delete and rev:
- raise util.Abort(_("--rev is incompatible with --delete"))
- if rename and rev:
- raise util.Abort(_("--rev is incompatible with --rename"))
- if not names and (delete or rev):
- raise util.Abort(_("bookmark name required"))
- if delete or rename or names or inactive:
- wlock = repo.wlock()
- try:
- cur = repo.changectx('.').node()
- marks = repo._bookmarks
- if delete:
- for mark in names:
- if mark not in marks:
- raise util.Abort(_("bookmark '%s' does not exist") %
- mark)
- if mark == repo._bookmarkcurrent:
- bookmarks.unsetcurrent(repo)
- del marks[mark]
- marks.write()
- elif rename:
- if not names:
- raise util.Abort(_("new bookmark name required"))
- elif len(names) > 1:
- raise util.Abort(_("only one new bookmark name allowed"))
- mark = checkformat(names[0])
- if rename not in marks:
- raise util.Abort(_("bookmark '%s' does not exist") % rename)
- checkconflict(repo, mark, cur, force)
- marks[mark] = marks[rename]
- if repo._bookmarkcurrent == rename and not inactive:
- bookmarks.setcurrent(repo, mark)
- del marks[rename]
- marks.write()
- elif names:
- newact = None
- for mark in names:
- mark = checkformat(mark)
- if newact is None:
- newact = mark
- if inactive and mark == repo._bookmarkcurrent:
- bookmarks.unsetcurrent(repo)
- return
- tgt = cur
- if rev:
- tgt = scmutil.revsingle(repo, rev).node()
- checkconflict(repo, mark, cur, force, tgt)
- marks[mark] = tgt
- if not inactive and cur == marks[newact] and not rev:
- bookmarks.setcurrent(repo, newact)
- elif cur != tgt and newact == repo._bookmarkcurrent:
- bookmarks.unsetcurrent(repo)
- marks.write()
- elif inactive:
- if len(marks) == 0:
- ui.status(_("no bookmarks set\n"))
- elif not repo._bookmarkcurrent:
- ui.status(_("no active bookmark\n"))
- else:
- bookmarks.unsetcurrent(repo)
- finally:
- wlock.release()
- else: # show bookmarks
- hexfn = ui.debugflag and hex or short
- marks = repo._bookmarks
- if len(marks) == 0:
- ui.status(_("no bookmarks set\n"))
- else:
- for bmark, n in sorted(marks.iteritems()):
- current = repo._bookmarkcurrent
- if bmark == current:
- prefix, label = '*', 'bookmarks.current'
- else:
- prefix, label = ' ', ''
- if ui.quiet:
- ui.write("%s\n" % bmark, label=label)
- else:
- pad = " " * (25 - encoding.colwidth(bmark))
- ui.write(" %s %s%s %d:%s\n" % (
- prefix, bmark, pad, repo.changelog.rev(n), hexfn(n)),
- label=label)
- @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
- .. note::
- Branch names are permanent and global. Use :hg:`bookmark` to create a
- light-weight bookmark instead. See :hg:`help glossary` for more
- information about named branches and bookmarks.
- With no argument, show the current branch name. With one argument,
- set the working directory branch name (the branch will not exist
- in the repository until the next commit). Standard practice
- recommends that primary development take place on the 'default'
- branch.
- Unless -f/--force is specified, branch will not let you set a
- branch name that already exists, 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.
- Returns 0 on success.
- """
- if label:
- label = label.strip()
- if not opts.get('clean') and not label:
- ui.write("%s\n" % repo.dirstate.branch())
- return
- wlock = repo.wlock()
- try:
- 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.branchmap():
- 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"))
- scmutil.checknewlabel(repo, label, 'branch')
- repo.dirstate.setbranch(label)
- ui.status(_('marked working directory as branch %s\n') % label)
- ui.status(_('(branches are permanent and global, '
- 'did you want a bookmark?)\n'))
- finally:
- wlock.release()
- @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
- allheads = set(repo.heads())
- branches = []
- for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
- isactive = not isclosed and bool(set(heads) & allheads)
- branches.append((tag, repo[tip], isactive, not isclosed))
- branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
- reverse=True)
- for tag, ctx, isactive, isopen in branches:
- if (not active) or isactive:
- if isactive:
- label = 'branches.active'
- notice = ''
- elif not isopen:
- 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(ctx.rev()).rjust(31 - encoding.colwidth(tag))
- rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
- 'log.changeset changeset.%s' % ctx.phasestr())
- labeledtag = ui.label(tag, label)
- if ui.quiet:
- ui.write("%s\n" % labeledtag)
- else:
- ui.write("%s %s%s\n" % (labeledtag, 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'])
- 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'))
- if opts.get('all'):
- base = ['null']
- else:
- base = scmutil.revrange(repo, opts.get('base'))
- # TODO: get desired bundlecaps from command line.
- bundlecaps = None
- 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
- cg = changegroup.getbundle(repo, 'bundle', heads=heads, common=common,
- bundlecaps=bundlecaps)
- outgoing = None
- 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, repo, branches, revs)
- heads = revs and map(repo.lookup, revs) or revs
- outgoing = discovery.findcommonoutgoing(repo, other,
- onlyheads=heads,
- force=opts.get('force'),
- portable=True)
- cg = changegroup.getlocalbundle(repo, 'bundle', outgoing, bundlecaps)
- if not cg:
- scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
- return 1
- 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...'),
- inferrepo=True)
- def cat(ui, repo, file1, *pats, **opts):
- """output the current or given revision of files
- Print the specified files as they were at the given revision. If
- no revision is given, the parent of the working directory is used.
- Output may be to a file, in which case the name of the file is
- given using a format string. The formatting rules as follows:
- :``%%``: literal "%" character
- :``%s``: basename of file being printed
- :``%d``: dirname of file being printed, or '.' if in repository root
- :``%p``: root-relative path name of file being printed
- :``%H``: changeset hash (40 hexadecimal digits)
- :``%R``: changeset revision number
- :``%h``: short-form changeset hash (12 hexadecimal digits)
- :``%r``: zero-padded changeset revision number
- :``%b``: basename of the exporting repository
- Returns 0 on success.
- """
- ctx = scmutil.revsingle(repo, opts.get('rev'))
- m = scmutil.match(ctx, (file1,) + pats, opts)
- return cmdutil.cat(ui, repo, ctx, m, '', **opts)
- @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]'),
- norepo=True)
- def clone(ui, source, dest=None, **opts):
- """make a copy of an existing repository
- Create a copy of an existing repository in a new directory.
- If no destination directory name is specified, it defaults to the
- basename of the source.
- The location of the source is added to the new repository's
- ``.hg/hgrc`` file, as the default to be used for future pulls.
- Only local paths and ``ssh://`` URLs are supported as
- destinations. For ``ssh://`` destinations, no working directory or
- ``.hg/hgrc`` will be created on the remote side.
- 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.
- If the source repository has a bookmark called '@' set, that
- revision will be checked out in the new repository by default.
- To check out a particular version, use -u/--update, or
- -U/--noupdate to create a clone with no working directory.
- .. 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 revision marked with the '@' bookmark, if present
- h) the tipmost head of the default branch
- i) 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…
Large files files are truncated, but you can click here to view the full file