commands.py 228 KB
Newer Older
mpm@selenic.com's avatar
mpm@selenic.com committed
1 2
# commands.py - command processing for mercurial
#
3
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
mpm@selenic.com's avatar
mpm@selenic.com committed
4
#
5
# This software may be used and distributed according to the terms of the
Matt Mackall's avatar
Matt Mackall committed
6
# GNU General Public License version 2 or any later version.
mpm@selenic.com's avatar
mpm@selenic.com committed
7

Gregory Szorc's avatar
Gregory Szorc committed
8 9 10 11 12 13
from __future__ import absolute_import

import difflib
import errno
import os
import re
14
import sys
Gregory Szorc's avatar
Gregory Szorc committed
15 16 17 18 19 20 21

from .i18n import _
from .node import (
    hex,
    nullid,
    nullrev,
    short,
22 23
    wdirhex,
    wdirrev,
Gregory Szorc's avatar
Gregory Szorc committed
24 25 26 27 28 29 30 31
)
from . import (
    archival,
    bookmarks,
    bundle2,
    changegroup,
    cmdutil,
    copies,
32
    debugcommands as debugcommandsmod,
Gregory Szorc's avatar
Gregory Szorc committed
33
    destutil,
34
    dirstateguard,
Gregory Szorc's avatar
Gregory Szorc committed
35 36 37 38 39
    discovery,
    encoding,
    error,
    exchange,
    extensions,
40
    filemerge,
Yuya Nishihara's avatar
Yuya Nishihara committed
41
    formatter,
Gregory Szorc's avatar
Gregory Szorc committed
42 43 44 45
    graphmod,
    hbisect,
    help,
    hg,
46
    logcmdutil,
Gregory Szorc's avatar
Gregory Szorc committed
47
    merge as mergemod,
48
    narrowspec,
Gregory Szorc's avatar
Gregory Szorc committed
49
    obsolete,
50
    obsutil,
Gregory Szorc's avatar
Gregory Szorc committed
51 52
    patch,
    phases,
53
    pycompat,
54
    rcutil,
55
    registrar,
56
    repair,
57
    revsetlang,
58
    rewriteutil,
Gregory Szorc's avatar
Gregory Szorc committed
59
    scmutil,
60
    server,
61
    state as statemod,
Gregory Szorc's avatar
Gregory Szorc committed
62
    streamclone,
63
    tags as tagsmod,
Gregory Szorc's avatar
Gregory Szorc committed
64 65 66
    templatekw,
    ui as uimod,
    util,
67
    wireprotoserver,
Gregory Szorc's avatar
Gregory Szorc committed
68
)
69 70 71 72
from .utils import (
    dateutil,
    stringutil,
)
Gregory Szorc's avatar
Gregory Szorc committed
73

74
table = {}
75
table.update(debugcommandsmod.command._table)
76

77
command = registrar.command(table)
78
INTENT_READONLY = registrar.INTENT_READONLY
79 80 81 82 83 84 85 86 87 88

# common command options

globalopts = [
    ('R', 'repository', '',
     _('repository root directory or name of overlay bundle file'),
     _('REPO')),
    ('', 'cwd', '',
     _('change working directory'), _('DIR')),
    ('y', 'noninteractive', None,
89
     _('do not prompt, automatically pick the first choice for all prompts')),
90 91
    ('q', 'quiet', None, _('suppress output')),
    ('v', 'verbose', None, _('enable additional output')),
92
    ('', 'color', '',
93 94
     # i18n: 'always', 'auto', 'never', and 'debug' are keywords
     # and should not be translated
95
     _("when to colorize (boolean, always, auto, never, or debug)"),
96
     _('TYPE')),
97 98 99 100 101 102 103 104 105 106 107 108 109 110
    ('', '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')),
111
    ('', 'hidden', False, _('consider hidden changesets')),
112 113
    ('', 'pager', 'auto',
     _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 115
]

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
dryrunopts = cmdutil.dryrunopts
remoteopts = cmdutil.remoteopts
walkopts = cmdutil.walkopts
commitopts = cmdutil.commitopts
commitopts2 = cmdutil.commitopts2
formatteropts = cmdutil.formatteropts
templateopts = cmdutil.templateopts
logopts = cmdutil.logopts
diffopts = cmdutil.diffopts
diffwsopts = cmdutil.diffwsopts
diffopts2 = cmdutil.diffopts2
mergetoolopts = cmdutil.mergetoolopts
similarityopts = cmdutil.similarityopts
subrepoopts = cmdutil.subrepoopts
debugrevlogopts = cmdutil.debugrevlogopts
131

mpm@selenic.com's avatar
mpm@selenic.com committed
132
# Commands start here, listed alphabetically
133

134
@command('add',
135
    walkopts + subrepoopts + dryrunopts,
136
    _('[OPTION]... [FILE]...'),
137
    helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138
    helpbasic=True, inferrepo=True)
139
def add(ui, repo, *pats, **opts):
140 141
    """add the specified files on the next commit

142 143
    Schedule files to be version controlled and added to the
    repository.
144

145
    The files will be added to the repository at the next commit. To
146
    undo an add before that, see :hg:`forget`.
147

timeless's avatar
timeless committed
148 149
    If no names are given, add all files to the repository (except
    files matching ``.hgignore``).
150 151 152

    .. container:: verbose

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
       Examples:

         - New (unknown) files are added
           automatically by :hg:`add`::

             $ ls
             foo.c
             $ hg status
             ? foo.c
             $ hg add
             adding foo.c
             $ hg status
             A foo.c

         - Specific files to be added can be specified::

             $ ls
             bar.c  foo.c
             $ hg status
             ? bar.c
             ? foo.c
             $ hg add bar.c
             $ hg status
             A bar.c
             ? foo.c
178 179

    Returns 0 if all files are successfully added.
180 181
    """

182
    m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 184
    uipathfn = scmutil.getuipathfn(repo, forcerelativevalue=True)
    rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185
    return rejected and 1 or 0
186

187
@command('addremove',
188
    similarityopts + subrepoopts + walkopts + dryrunopts,
189
    _('[OPTION]... [FILE]...'),
190
    helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191
    inferrepo=True)
192
def addremove(ui, repo, *pats, **opts):
193
    """add all new files, delete all missing files
194

195 196
    Add all new files and remove all missing files from the
    repository.
197

timeless's avatar
timeless committed
198 199 200
    Unless names are given, new files are ignored if they match any of
    the patterns in ``.hgignore``. As with add, these changes take
    effect at the next commit.
201

202
    Use the -s/--similarity option to detect renamed files. This
203
    option takes a percentage between 0 (disabled) and 100 (files must
204 205 206
    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
207
    can be expensive. After using this option, :hg:`status -C` can be
208 209 210
    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.
211

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    .. container:: verbose

       Examples:

         - A number of files (bar.c and foo.c) are new,
           while foobar.c has been removed (without using :hg:`remove`)
           from the repository::

             $ ls
             bar.c foo.c
             $ hg status
             ! foobar.c
             ? bar.c
             ? foo.c
             $ hg addremove
             adding bar.c
             adding foo.c
             removing foobar.c
             $ hg status
             A bar.c
             A foo.c
             R foobar.c

         - A file foobar.c was moved to foo.c without using :hg:`rename`.
           Afterwards, it was edited slightly::

             $ ls
             foo.c
             $ hg status
             ! foobar.c
             ? foo.c
             $ hg addremove --similarity 90
             removing foobar.c
             adding foo.c
             recording removal of foobar.c as rename to foo.c (94% similar)
             $ hg status -C
             A foo.c
               foobar.c
             R foobar.c

252
    Returns 0 if all files are successfully added.
253
    """
254
    opts = pycompat.byteskwargs(opts)
255 256
    if not opts.get('similarity'):
        opts['similarity'] = '100'
257
    matcher = scmutil.match(repo[None], pats, opts)
258 259 260
    relative = scmutil.anypats(pats, opts)
    uipathfn = scmutil.getuipathfn(repo, forcerelativevalue=relative)
    return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261

262
@command('annotate|blame',
263 264 265 266 267 268 269 270 271 272
    [('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')),
273 274
    ('l', 'line-number', None, _('show line number at the first appearance')),
    ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275
    ] + diffwsopts + walkopts + formatteropts,
276
    _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277
    helpcategory=command.CATEGORY_FILE_CONTENTS,
278
    helpbasic=True, inferrepo=True)
279
def annotate(ui, repo, *pats, **opts):
280
    """show changeset information by line for each file
281

282
    List changes in files, showing the revision id responsible for
283
    each line.
284

285 286
    This command is useful for discovering when a change was made and
    by whom.
287

288 289 290
    If you include --file, --user, or --date, the revision number is
    suppressed unless you also include --number.

291 292 293 294
    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.
295

296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
    .. container:: verbose

      Template:

      The following keywords are supported in addition to the common template
      keywords and functions. See also :hg:`help templates`.

      :lines:   List of lines with annotation data.
      :path:    String. Repository-absolute path of the specified file.

      And each entry of ``{lines}`` provides the following sub-keywords in
      addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.

      :line:    String. Line content.
      :lineno:  Integer. Line number at that revision.
      :path:    String. Repository-absolute path of the file at that revision.

      See :hg:`help templates.operators` for the list expansion syntax.

315
    Returns 0 on success.
316
    """
317
    opts = pycompat.byteskwargs(opts)
318
    if not pats:
319
        raise error.Abort(_('at least one filename or pattern is required'))
320

321 322 323
    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
324
        opts['file'] = True
325

326 327 328 329 330 331 332 333
    if (not opts.get('user') and not opts.get('changeset')
        and not opts.get('date') and not opts.get('file')):
        opts['number'] = True

    linenumber = opts.get('line_number') is not None
    if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
        raise error.Abort(_('at least one of -n/-c is required for -l'))

334 335 336 337
    rev = opts.get('rev')
    if rev:
        repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
    ctx = scmutil.revsingle(repo, rev)
338

339
    ui.pager('annotate')
340
    rootfm = ui.formatter('annotate', opts)
341 342 343 344 345
    if ui.debugflag:
        shorthex = pycompat.identity
    else:
        def shorthex(h):
            return h[:12]
346
    if ui.quiet:
347
        datefunc = dateutil.shortdate
348
    else:
349
        datefunc = dateutil.datestr
350 351 352 353
    if ctx.rev() is None:
        if opts.get('changeset'):
            # omit "+" suffix which is appended to node hex
            def formatrev(rev):
354
                if rev == wdirrev:
355 356 357 358 359
                    return '%d' % ctx.p1().rev()
                else:
                    return '%d' % rev
        else:
            def formatrev(rev):
360
                if rev == wdirrev:
361 362 363
                    return '%d+' % ctx.p1().rev()
                else:
                    return '%d ' % rev
364
        def formathex(h):
365
            if h == wdirhex:
366
                return '%s+' % shorthex(hex(ctx.p1().node()))
367
            else:
368
                return '%s ' % shorthex(h)
369
    else:
370 371
        formatrev = b'%d'.__mod__
        formathex = shorthex
372

373 374 375 376 377 378 379 380 381 382 383 384 385 386
    opmap = [
        ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
        ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
        ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
        ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
        ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
        ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
    ]
    opnamemap = {
        'rev': 'number',
        'node': 'changeset',
        'path': 'file',
        'lineno': 'line_number',
    }
387

388
    if rootfm.isplain():
389
        def makefunc(get, fmt):
390
            return lambda x: fmt(get(x))
391 392
    else:
        def makefunc(get, fmt):
393
            return get
394
    datahint = rootfm.datahint()
395
    funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396
               if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397
    funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398
    fields = ' '.join(fn for fn, sep, get, fmt in opmap
399
                      if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400

401
    def bad(x, y):
402
        raise error.Abort("%s: %s" % (x, y))
403

404 405
    m = scmutil.match(ctx, pats, opts, badfn=bad)

406
    follow = not opts.get('no_follow')
407 408
    diffopts = patch.difffeatureopts(ui, opts, section='annotate',
                                     whitespace=True)
409 410 411 412
    skiprevs = opts.get('skip')
    if skiprevs:
        skiprevs = scmutil.revrange(repo, skiprevs)

413
    uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
Matt Mackall's avatar
Matt Mackall committed
414 415
    for abs in ctx.walk(m):
        fctx = ctx[abs]
416
        rootfm.startitem()
417
        rootfm.data(path=abs)
Jun Wu's avatar
Jun Wu committed
418
        if not opts.get('text') and fctx.isbinary():
419
            rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 421
            continue

422
        fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 424
        lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
                              diffopts=diffopts)
425
        if not lines:
426
            fm.end()
427
            continue
428
        formats = []
429 430
        pieces = []

431
        for f, sep in funcmap:
432
            l = [f(n) for n in lines]
433
            if fm.isplain():
434 435 436
                sizes = [encoding.colwidth(x) for x in l]
                ml = max(sizes)
                formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 438
            else:
                formats.append(['%s' for x in l])
439
            pieces.append(l)
440

441
        for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442
            fm.startitem()
443
            fm.context(fctx=n.fctx)
444
            fm.write(fields, "".join(f), *p)
445
            if n.skip:
446 447 448
                fmt = "* %s"
            else:
                fmt = ": %s"
449
            fm.write('line', fmt, n.text)
450

451
        if not lines[-1].text.endswith('\n'):
452
            fm.plain('\n')
453
        fm.end()
454

455
    rootfm.end()
456

457 458 459 460 461 462 463
@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,
464 465
    _('[OPTION]... DEST'),
    helpcategory=command.CATEGORY_IMPORT_EXPORT)
466
def archive(ui, repo, dest, **opts):
467
    '''create an unversioned archive of a repository revision
468

469 470
    By default, the revision used is the parent of the working
    directory; use -r/--rev to specify a different revision.
471

472
    The archive type is automatically detected based on file
timeless's avatar
timeless committed
473
    extension (to override, use -t/--type).
474

Matt Mackall's avatar
Matt Mackall committed
475 476 477 478 479 480 481 482 483 484 485 486
    .. 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*"

487
    Valid types are:
488

489 490 491 492 493 494
    :``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
495

496
    The exact name of the destination archive or directory is given
Martin Geisler's avatar
Martin Geisler committed
497
    using a format string; see :hg:`help export` for details.
498

499 500 501 502
    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.
503 504

    Returns 0 on success.
505 506
    '''

507
    opts = pycompat.byteskwargs(opts)
508 509 510 511
    rev = opts.get('rev')
    if rev:
        repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
    ctx = scmutil.revsingle(repo, rev)
512
    if not ctx:
513
        raise error.Abort(_('no working directory: please specify a revision'))
514
    node = ctx.node()
515
    dest = cmdutil.makefilename(ctx, dest)
516
    if os.path.realpath(dest) == repo.root:
517
        raise error.Abort(_('repository root cannot be destination'))
518

519
    kind = opts.get('type') or archival.guesskind(dest) or 'files'
520
    prefix = opts.get('prefix')
521

522 523
    if dest == '-':
        if kind == 'files':
524
            raise error.Abort(_('cannot archive plain files to stdout'))
525
        dest = cmdutil.makefileobj(ctx, dest)
526 527
        if not prefix:
            prefix = os.path.basename(repo.root) + '-%h'
528

529
    prefix = cmdutil.makefilename(ctx, prefix)
530
    match = scmutil.match(ctx, [], opts)
531
    archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532
                     match, prefix, subrepos=opts.get('subrepos'))
533

534 535
@command('backout',
    [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 537 538
    ('', 'commit', None,
     _('commit if no conflicts were encountered (DEPRECATED)')),
    ('', 'no-commit', None, _('do not commit')),
539 540
    ('', 'parent', '',
     _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541
    ('r', 'rev', '', _('revision to backout'), _('REV')),
542
    ('e', 'edit', False, _('invoke editor on commit messages')),
543
    ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 545
    _('[OPTION]... [-r] REV'),
    helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546
def backout(ui, repo, node=None, rev=None, **opts):
Vadim Gelfer's avatar
Vadim Gelfer committed
547 548
    '''reverse effect of earlier changeset

549
    Prepare a new changeset with the effect of REV undone in the
550 551
    current working directory. If no conflicts were encountered,
    it will be committed immediately.
552

553
    If REV is the parent of the working directory, then this new changeset
554
    is committed automatically (unless --no-commit is specified).
555

556
    .. note::
557

558
       :hg:`backout` cannot be used to fix either an unwanted or
559
       incorrect merge.
560

Matt Mackall's avatar
Matt Mackall committed
561 562
    .. container:: verbose

563 564 565 566 567 568 569 570 571 572 573 574
      Examples:

      - Reverse the effect of the parent of the working directory.
        This backout will be committed immediately::

          hg backout -r .

      - Reverse the effect of previous bad revision 23::

          hg backout -r 23

      - Reverse the effect of previous bad revision 23 and
575
        leave changes uncommitted::
576

577 578
          hg backout -r 23 --no-commit
          hg commit -m "Backout revision 23"
579

Matt Mackall's avatar
Matt Mackall committed
580 581 582 583 584 585 586 587 588
      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.
589

Martin Geisler's avatar
Martin Geisler committed
590
    See :hg:`help dates` for a list of formats valid for -d/--date.
591

592 593 594
    See :hg:`help revert` for a way to restore files to the state
    of another revision.

595 596
    Returns 0 on success, 1 if nothing to backout or there are unresolved
    files.
597
    '''
598
    with repo.wlock(), repo.lock():
599
        return _dobackout(ui, repo, node, rev, **opts)
600

601
def _dobackout(ui, repo, node=None, rev=None, **opts):
602
    opts = pycompat.byteskwargs(opts)
603 604
    if opts.get('commit') and opts.get('no_commit'):
        raise error.Abort(_("cannot use --commit with --no-commit"))
605 606
    if opts.get('merge') and opts.get('no_commit'):
        raise error.Abort(_("cannot use --merge with --no-commit"))
607

Daniel Holth's avatar
Daniel Holth committed
608
    if rev and node:
609
        raise error.Abort(_("please specify just one revision"))
Daniel Holth's avatar
Daniel Holth committed
610 611 612

    if not rev:
        rev = node
Vadim Gelfer's avatar
Vadim Gelfer committed
613

614
    if not rev:
615
        raise error.Abort(_("please specify a revision to backout"))
616

617 618
    date = opts.get('date')
    if date:
619
        opts['date'] = dateutil.parsedate(date)
620

621
    cmdutil.checkunfinished(repo)
622
    cmdutil.bailifchanged(repo)
623
    node = scmutil.revsingle(repo, rev).node()
624

625
    op1, op2 = repo.dirstate.parents()
626
    if not repo.changelog.isancestor(node, op1):
627
        raise error.Abort(_('cannot backout change that is not an ancestor'))
628

629 630
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
631
        raise error.Abort(_('cannot backout a change with no parents'))
Vadim Gelfer's avatar
Vadim Gelfer committed
632
    if p2 != nullid:
633
        if not opts.get('parent'):
634
            raise error.Abort(_('cannot backout a merge changeset'))
635 636
        p = repo.lookup(opts['parent'])
        if p not in (p1, p2):
637
            raise error.Abort(_('%s is not a parent of %s') %
638
                             (short(p), short(node)))
639 640
        parent = p
    else:
641
        if opts.get('parent'):
642
            raise error.Abort(_('cannot use --parent on non-merge changeset'))
643
        parent = p1
644

645
    # the backout should appear on the same branch
646 647 648 649
    branch = repo.dirstate.branch()
    bheads = repo.branchheads(branch)
    rctx = scmutil.revsingle(repo, hex(parent))
    if not opts.get('merge') and op1 != node:
650
        with dirstateguard.dirstateguard(repo, 'backout'):
651 652
            overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
            with ui.configoverride(overrides, 'backout'):
653 654 655
                stats = mergemod.update(repo, parent, branchmerge=True,
                                        force=True, ancestor=node,
                                        mergeancestor=False)
656
            repo.setparents(op1, op2)
657 658 659 660 661
        hg._showstats(repo, stats)
        if stats.unresolvedcount:
            repo.ui.status(_("use 'hg resolve' to retry unresolved "
                             "file merges\n"))
            return 1
662 663 664 665 666
    else:
        hg.clean(repo, node, show_stats=False)
        repo.dirstate.setbranch(branch)
        cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())

667 668 669 670 671
    if opts.get('no_commit'):
        msg = _("changeset %s backed out, "
                "don't forget to commit.\n")
        ui.status(msg % short(node))
        return 0
672 673 674

    def commitfunc(ui, repo, message, match, opts):
        editform = 'backout'
675 676
        e = cmdutil.getcommiteditor(editform=editform,
                                    **pycompat.strkwargs(opts))
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
        if not message:
            # we don't translate commit messages
            message = "Backed out changeset %s" % short(node)
            e = cmdutil.getcommiteditor(edit=True, editform=editform)
        return repo.commit(message, opts.get('user'), opts.get('date'),
                           match, editor=e)
    newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
    if not newnode:
        ui.status(_("nothing changed\n"))
        return 1
    cmdutil.commitstatus(repo, newnode, branch, bheads)

    def nice(node):
        return '%d:%s' % (repo.changelog.rev(node), short(node))
    ui.status(_('changeset %s backs out changeset %s\n') %
              (nice(repo.changelog.tip()), nice(node)))
    if opts.get('merge') and op1 != node:
        hg.clean(repo, op1, show_stats=False)
        ui.status(_('merging with changeset %s\n')
                  % nice(repo.changelog.tip()))
697 698
        overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
        with ui.configoverride(overrides, 'backout'):
699
            return hg.merge(repo, hex(repo.changelog.tip()))
700
    return 0
Vadim Gelfer's avatar
Vadim Gelfer committed
701

702 703 704 705 706 707 708 709
@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'))],
710 711
    _("[-gbsr] [-U] [-c CMD] [REV]"),
    helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712
def bisect(ui, repo, rev=None, extra=None, command=None,
713 714
               reset=None, good=None, bad=None, skip=None, extend=None,
               noupdate=None):
715 716
    """subdivision search of changesets

717 718 719 720 721 722 723 724 725 726 727 728 729
    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.
730 731 732 733 734 735
    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.
736

737 738 739 740
    .. container:: verbose

      Some examples:

741
      - start a bisection with known bad revision 34, and good revision 12::
742 743 744 745 746 747 748 749 750 751

          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

kiilerix's avatar
kiilerix committed
752
      - mark the current revision, or a known revision, to be skipped (e.g. if
753 754 755 756 757
        that revision is not usable because of another issue)::

          hg bisect --skip
          hg bisect --skip 23

758
      - skip all revisions that do not touch directories ``foo`` or ``bar``::
759

760
          hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761

762 763 764 765 766 767 768 769 770 771
      - 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
772
          hg bisect --command "make && make tests"
773 774 775 776 777 778

      - see all changesets whose states are already known in the current
        bisection::

          hg log -r "bisect(pruned)"

779 780 781 782 783
      - see the changeset currently being bisected (especially useful
        if running with -U/--noupdate)::

          hg log -r "bisect(current)"

784 785 786 787
      - see all changesets that took part in the current bisection::

          hg log -r "bisect(range)"

788
      - you can even get a nice graph::
789 790 791

          hg log --graph -r "bisect(range)"

792
      See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793

794
    Returns 0 on success.
795 796 797 798 799 800 801 802 803 804 805
    """
    # 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
806
    elif extra:
807
        raise error.Abort(_('incompatible arguments'))
808

809 810 811 812 813 814 815 816 817
    incompatibles = {
        '--bad': bad,
        '--command': bool(command),
        '--extend': extend,
        '--good': good,
        '--reset': reset,
        '--skip': skip,
    }

818 819 820 821 822
    enabled = [x for x in incompatibles if incompatibles[x]]

    if len(enabled) > 1:
        raise error.Abort(_('%s and %s are incompatible') %
                          tuple(sorted(enabled)[0:2]))
823

824
    if reset:
825
        hbisect.resetstate(repo)
826 827
        return

828 829
    state = hbisect.load_state(repo)

830 831 832
    # update state
    if good or bad or skip:
        if rev:
833
            nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]