commands.py 253 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
from .pycompat import open
Gregory Szorc's avatar
Gregory Szorc committed
26
27
28
29
30
31
32
from . import (
    archival,
    bookmarks,
    bundle2,
    changegroup,
    cmdutil,
    copies,
33
    debugcommands as debugcommandsmod,
Gregory Szorc's avatar
Gregory Szorc committed
34
    destutil,
35
    dirstateguard,
Gregory Szorc's avatar
Gregory Szorc committed
36
37
38
39
40
    discovery,
    encoding,
    error,
    exchange,
    extensions,
41
    filemerge,
Yuya Nishihara's avatar
Yuya Nishihara committed
42
    formatter,
Gregory Szorc's avatar
Gregory Szorc committed
43
44
45
46
    graphmod,
    hbisect,
    help,
    hg,
47
    logcmdutil,
Gregory Szorc's avatar
Gregory Szorc committed
48
    merge as mergemod,
49
    narrowspec,
Gregory Szorc's avatar
Gregory Szorc committed
50
    obsolete,
51
    obsutil,
Gregory Szorc's avatar
Gregory Szorc committed
52
53
    patch,
    phases,
54
    pycompat,
55
    rcutil,
56
    registrar,
57
    revsetlang,
58
    rewriteutil,
Gregory Szorc's avatar
Gregory Szorc committed
59
    scmutil,
60
    server,
61
    shelve as shelvemod,
62
    state as statemod,
Gregory Szorc's avatar
Gregory Szorc committed
63
    streamclone,
64
    tags as tagsmod,
Gregory Szorc's avatar
Gregory Szorc committed
65
66
    ui as uimod,
    util,
67
    verify as verifymod,
68
    wireprotoserver,
Gregory Szorc's avatar
Gregory Szorc committed
69
)
70
71
72
73
from .utils import (
    dateutil,
    stringutil,
)
Gregory Szorc's avatar
Gregory Szorc committed
74

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

78
command = registrar.command(table)
79
INTENT_READONLY = registrar.INTENT_READONLY
80
81
82
83

# common command options

globalopts = [
84
    (
85
86
87
88
89
        b'R',
        b'repository',
        b'',
        _(b'repository root directory or name of overlay bundle file'),
        _(b'REPO'),
90
    ),
91
    (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
92
    (
93
94
        b'y',
        b'noninteractive',
95
        None,
96
97
98
        _(
            b'do not prompt, automatically pick the first choice for all prompts'
        ),
99
    ),
100
101
    (b'q', b'quiet', None, _(b'suppress output')),
    (b'v', b'verbose', None, _(b'enable additional output')),
102
    (
103
104
105
        b'',
        b'color',
        b'',
106
107
        # i18n: 'always', 'auto', 'never', and 'debug' are keywords
        # and should not be translated
108
109
        _(b"when to colorize (boolean, always, auto, never, or debug)"),
        _(b'TYPE'),
110
111
    ),
    (
112
113
        b'',
        b'config',
114
        [],
115
116
        _(b'set/override config option (use \'section.name=value\')'),
        _(b'CONFIG'),
117
    ),
118
119
    (b'', b'debug', None, _(b'enable debugging output')),
    (b'', b'debugger', None, _(b'start debugger')),
120
    (
121
122
        b'',
        b'encoding',
123
        encoding.encoding,
124
125
        _(b'set the charset encoding'),
        _(b'ENCODE'),
126
127
    ),
    (
128
129
        b'',
        b'encodingmode',
130
        encoding.encodingmode,
131
132
        _(b'set the charset encoding mode'),
        _(b'MODE'),
133
    ),
134
135
136
137
138
139
    (b'', b'traceback', None, _(b'always print a traceback on exception')),
    (b'', b'time', None, _(b'time how long the command takes')),
    (b'', b'profile', None, _(b'print command execution profile')),
    (b'', b'version', None, _(b'output version information and exit')),
    (b'h', b'help', None, _(b'display help and exit')),
    (b'', b'hidden', False, _(b'consider hidden changesets')),
140
    (
141
142
143
144
145
        b'',
        b'pager',
        b'auto',
        _(b"when to paginate (boolean, always, auto, or never)"),
        _(b'TYPE'),
146
    ),
147
148
]

149
150
151
152
153
dryrunopts = cmdutil.dryrunopts
remoteopts = cmdutil.remoteopts
walkopts = cmdutil.walkopts
commitopts = cmdutil.commitopts
commitopts2 = cmdutil.commitopts2
154
commitopts3 = cmdutil.commitopts3
155
156
157
158
159
160
161
162
163
164
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
165

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

168
169

@command(
170
    b'abort',
171
172
173
174
    dryrunopts,
    helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
    helpbasic=True,
)
175
176
177
178
179
180
181
182
def abort(ui, repo, **opts):
    """abort an unfinished operation (EXPERIMENTAL)

    Aborts a multistep operation like graft, histedit, rebase, merge,
    and unshelve if they are in an unfinished state.

    use --dry-run/-n to dry run the command.
    """
183
    dryrun = opts.get('dry_run')
184
185
    abortstate = cmdutil.getunfinishedstate(repo)
    if not abortstate:
186
        raise error.Abort(_(b'no operation in progress'))
187
    if not abortstate.abortfunc:
188
189
        raise error.Abort(
            (
190
                _(b"%s in progress but does not support 'hg abort'")
191
192
193
194
                % (abortstate._opname)
            ),
            hint=abortstate.hint(),
        )
195
    if dryrun:
196
197
198
        ui.status(
            _(b'%s in progress, will be aborted\n') % (abortstate._opname)
        )
199
200
201
        return
    return abortstate.abortfunc(ui, repo)

202
203

@command(
204
    b'add',
205
    walkopts + subrepoopts + dryrunopts,
206
    _(b'[OPTION]... [FILE]...'),
207
    helpcategory=command.CATEGORY_WORKING_DIRECTORY,
208
209
210
    helpbasic=True,
    inferrepo=True,
)
211
def add(ui, repo, *pats, **opts):
212
213
    """add the specified files on the next commit

214
215
    Schedule files to be version controlled and added to the
    repository.
216

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

timeless's avatar
timeless committed
220
221
    If no names are given, add all files to the repository (except
    files matching ``.hgignore``).
222
223
224

    .. container:: verbose

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
       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
250
251

    Returns 0 if all files are successfully added.
252
253
    """

254
    m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
255
    uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
256
    rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
257
    return rejected and 1 or 0
258

259
260

@command(
261
    b'addremove',
262
    similarityopts + subrepoopts + walkopts + dryrunopts,
263
    _(b'[OPTION]... [FILE]...'),
264
    helpcategory=command.CATEGORY_WORKING_DIRECTORY,
265
266
    inferrepo=True,
)
267
def addremove(ui, repo, *pats, **opts):
268
    """add all new files, delete all missing files
269

270
271
    Add all new files and remove all missing files from the
    repository.
272

timeless's avatar
timeless committed
273
274
275
    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.
276

277
    Use the -s/--similarity option to detect renamed files. This
278
    option takes a percentage between 0 (disabled) and 100 (files must
279
280
281
    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
282
    can be expensive. After using this option, :hg:`status -C` can be
283
284
285
    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.
286

287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
    .. 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

327
    Returns 0 if all files are successfully added.
328
    """
329
    opts = pycompat.byteskwargs(opts)
330
331
    if not opts.get(b'similarity'):
        opts[b'similarity'] = b'100'
332
    matcher = scmutil.match(repo[None], pats, opts)
333
    relative = scmutil.anypats(pats, opts)
334
    uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
335
    return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
336

337
338

@command(
339
    b'annotate|blame',
340
    [
341
        (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
342
        (
343
344
            b'',
            b'follow',
345
            None,
346
            _(b'follow copies/renames and list the filename (DEPRECATED)'),
347
        ),
348
349
350
351
352
353
354
        (b'', b'no-follow', None, _(b"don't follow copies and renames")),
        (b'a', b'text', None, _(b'treat all files as text')),
        (b'u', b'user', None, _(b'list the author (long with -v)')),
        (b'f', b'file', None, _(b'list the filename')),
        (b'd', b'date', None, _(b'list the date (short with -q)')),
        (b'n', b'number', None, _(b'list the revision number (default)')),
        (b'c', b'changeset', None, _(b'list the changeset')),
355
        (
356
357
            b'l',
            b'line-number',
358
            None,
359
360
361
362
363
364
            _(b'show line number at the first appearance'),
        ),
        (
            b'',
            b'skip',
            [],
365
            _(b'revset to not display (EXPERIMENTAL)'),
366
            _(b'REV'),
367
368
369
370
371
        ),
    ]
    + diffwsopts
    + walkopts
    + formatteropts,
372
    _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
373
    helpcategory=command.CATEGORY_FILE_CONTENTS,
374
375
376
    helpbasic=True,
    inferrepo=True,
)
377
def annotate(ui, repo, *pats, **opts):
378
    """show changeset information by line for each file
379

380
    List changes in files, showing the revision id responsible for
381
    each line.
382

383
384
    This command is useful for discovering when a change was made and
    by whom.
385

386
387
388
    If you include --file, --user, or --date, the revision number is
    suppressed unless you also include --number.

389
390
391
392
    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.
393

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
    .. 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.

413
    Returns 0 on success.
414
    """
415
    opts = pycompat.byteskwargs(opts)
416
    if not pats:
417
        raise error.Abort(_(b'at least one filename or pattern is required'))
418

419
    if opts.get(b'follow'):
420
421
        # --follow is deprecated and now just an alias for -f/--file
        # to mimic the behavior of Mercurial before version 1.5
422
        opts[b'file'] = True
423

424
    if (
425
426
427
428
        not opts.get(b'user')
        and not opts.get(b'changeset')
        and not opts.get(b'date')
        and not opts.get(b'file')
429
    ):
430
        opts[b'number'] = True
431

432
433
434
435
436
437
438
    linenumber = opts.get(b'line_number') is not None
    if (
        linenumber
        and (not opts.get(b'changeset'))
        and (not opts.get(b'number'))
    ):
        raise error.Abort(_(b'at least one of -n/-c is required for -l'))
439

440
    rev = opts.get(b'rev')
441
    if rev:
442
        repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
443
    ctx = scmutil.revsingle(repo, rev)
444

445
446
    ui.pager(b'annotate')
    rootfm = ui.formatter(b'annotate', opts)
447
448
449
    if ui.debugflag:
        shorthex = pycompat.identity
    else:
450

451
452
        def shorthex(h):
            return h[:12]
453

454
    if ui.quiet:
455
        datefunc = dateutil.shortdate
456
    else:
457
        datefunc = dateutil.datestr
458
    if ctx.rev() is None:
459
        if opts.get(b'changeset'):
460
461
            # omit "+" suffix which is appended to node hex
            def formatrev(rev):
462
                if rev == wdirrev:
463
                    return b'%d' % ctx.p1().rev()
464
                else:
465
                    return b'%d' % rev
466

467
        else:
468

469
            def formatrev(rev):
470
                if rev == wdirrev:
471
                    return b'%d+' % ctx.p1().rev()
472
                else:
473
                    return b'%d ' % rev
474

475
        def formathex(h):
476
            if h == wdirhex:
477
                return b'%s+' % shorthex(hex(ctx.p1().node()))
478
            else:
479
                return b'%s ' % shorthex(h)
480

481
    else:
482
483
        formatrev = b'%d'.__mod__
        formathex = shorthex
484

485
    opmap = [
486
487
488
489
490
491
        (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
        (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
        (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
        (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
        (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
        (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
492
493
    ]
    opnamemap = {
494
495
496
497
        b'rev': b'number',
        b'node': b'changeset',
        b'path': b'file',
        b'lineno': b'line_number',
498
    }
499

500
    if rootfm.isplain():
501

502
        def makefunc(get, fmt):
503
            return lambda x: fmt(get(x))
504

505
    else:
506

507
        def makefunc(get, fmt):
508
            return get
509

510
    datahint = rootfm.datahint()
511
512
513
514
515
    funcmap = [
        (makefunc(get, fmt), sep)
        for fn, sep, get, fmt in opmap
        if opts.get(opnamemap.get(fn, fn)) or fn in datahint
    ]
516
517
    funcmap[0] = (funcmap[0][0], b'')  # no separator in front of first column
    fields = b' '.join(
518
519
520
521
        fn
        for fn, sep, get, fmt in opmap
        if opts.get(opnamemap.get(fn, fn)) or fn in datahint
    )
522

523
    def bad(x, y):
524
        raise error.Abort(b"%s: %s" % (x, y))
525

526
527
    m = scmutil.match(ctx, pats, opts, badfn=bad)

528
    follow = not opts.get(b'no_follow')
529
    diffopts = patch.difffeatureopts(
530
        ui, opts, section=b'annotate', whitespace=True
531
    )
532
    skiprevs = opts.get(b'skip')
533
534
535
    if skiprevs:
        skiprevs = scmutil.revrange(repo, skiprevs)

536
    uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
Matt Mackall's avatar
Matt Mackall committed
537
538
    for abs in ctx.walk(m):
        fctx = ctx[abs]
539
        rootfm.startitem()
540
        rootfm.data(path=abs)
541
542
        if not opts.get(b'text') and fctx.isbinary():
            rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
543
544
            continue

545
        fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
546
547
548
        lines = fctx.annotate(
            follow=follow, skiprevs=skiprevs, diffopts=diffopts
        )
549
        if not lines:
550
            fm.end()
551
            continue
552
        formats = []
553
554
        pieces = []

555
        for f, sep in funcmap:
556
            l = [f(n) for n in lines]
557
            if fm.isplain():
558
559
                sizes = [encoding.colwidth(x) for x in l]
                ml = max(sizes)
560
                formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
561
            else:
562
                formats.append([b'%s'] * len(l))
563
            pieces.append(l)
564

565
        for f, p, n in zip(zip(*formats), zip(*pieces), lines):
566
            fm.startitem()
567
            fm.context(fctx=n.fctx)
568
            fm.write(fields, b"".join(f), *p)
569
            if n.skip:
570
                fmt = b"* %s"
571
            else:
572
573
                fmt = b": %s"
            fm.write(b'line', fmt, n.text)
574

575
576
        if not lines[-1].text.endswith(b'\n'):
            fm.plain(b'\n')
577
        fm.end()
578

579
    rootfm.end()
580

581
582

@command(
583
    b'archive',
584
    [
585
        (b'', b'no-decode', None, _(b'do not pass files through decoders')),
586
        (
587
588
589
590
591
            b'p',
            b'prefix',
            b'',
            _(b'directory prefix for files in archive'),
            _(b'PREFIX'),
592
        ),
593
594
        (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
        (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
595
596
597
    ]
    + subrepoopts
    + walkopts,
598
    _(b'[OPTION]... DEST'),
599
600
    helpcategory=command.CATEGORY_IMPORT_EXPORT,
)
601
def archive(ui, repo, dest, **opts):
602
    '''create an unversioned archive of a repository revision
603

604
605
    By default, the revision used is the parent of the working
    directory; use -r/--rev to specify a different revision.
606

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

Matt Mackall's avatar
Matt Mackall committed
610
611
612
613
614
615
616
617
618
619
620
621
    .. 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*"

622
    Valid types are:
623

624
625
626
627
    :``files``: a directory full of files (default)
    :``tar``:   tar archive, uncompressed
    :``tbz2``:  tar archive, compressed using bzip2
    :``tgz``:   tar archive, compressed using gzip
628
    :``txz``:   tar archive, compressed using lzma (only in Python 3)
629
630
    :``uzip``:  zip archive, uncompressed
    :``zip``:   zip archive, compressed using deflate
631

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

635
636
637
638
    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.
639
640

    Returns 0 on success.
641
642
    '''

643
    opts = pycompat.byteskwargs(opts)
644
    rev = opts.get(b'rev')
645
    if rev:
646
        repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
647
    ctx = scmutil.revsingle(repo, rev)
648
    if not ctx:
649
        raise error.Abort(_(b'no working directory: please specify a revision'))
650
    node = ctx.node()
651
    dest = cmdutil.makefilename(ctx, dest)
652
    if os.path.realpath(dest) == repo.root:
653
        raise error.Abort(_(b'repository root cannot be destination'))
654

655
656
    kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
    prefix = opts.get(b'prefix')
657

658
659
660
    if dest == b'-':
        if kind == b'files':
            raise error.Abort(_(b'cannot archive plain files to stdout'))
661
        dest = cmdutil.makefileobj(ctx, dest)
662
        if not prefix:
663
            prefix = os.path.basename(repo.root) + b'-%h'
664

665
    prefix = cmdutil.makefilename(ctx, prefix)
666
    match = scmutil.match(ctx, [], opts)
667
668
669
670
671
    archival.archive(
        repo,
        dest,
        node,
        kind,
672
        not opts.get(b'no_decode'),
673
674
        match,
        prefix,
675
        subrepos=opts.get(b'subrepos'),
676
677
678
679
    )


@command(
680
    b'backout',
681
682
    [
        (
683
684
685
686
687
688
689
690
            b'',
            b'merge',
            None,
            _(b'merge with old dirstate parent after backout'),
        ),
        (
            b'',
            b'commit',
691
            None,
692
            _(b'commit if no conflicts were encountered (DEPRECATED)'),
693
        ),
694
        (b'', b'no-commit', None, _(b'do not commit')),
695
        (
696
697
698
699
700
            b'',
            b'parent',
            b'',
            _(b'parent to choose when backing out merge (DEPRECATED)'),
            _(b'REV'),
701
        ),
702
703
        (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
        (b'e', b'edit', False, _(b'invoke editor on commit messages')),
704
705
706
707
708
    ]
    + mergetoolopts
    + walkopts
    + commitopts
    + commitopts2,
709
    _(b'[OPTION]... [-r] REV'),
710
711
    helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
)
712
def backout(ui, repo, node=None, rev=None, **opts):
Vadim Gelfer's avatar
Vadim Gelfer committed
713
714
    '''reverse effect of earlier changeset

715
    Prepare a new changeset with the effect of REV undone in the
716
717
    current working directory. If no conflicts were encountered,
    it will be committed immediately.
718

719
    If REV is the parent of the working directory, then this new changeset
720
    is committed automatically (unless --no-commit is specified).
721

722
    .. note::
723

724
       :hg:`backout` cannot be used to fix either an unwanted or
725
       incorrect merge.
726

Matt Mackall's avatar
Matt Mackall committed
727
728
    .. container:: verbose

729
730
731
732
733
734
735
736
737
738
739
740
      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
741
        leave changes uncommitted::
742

743
744
          hg backout -r 23 --no-commit
          hg commit -m "Backout revision 23"
745

Matt Mackall's avatar
Matt Mackall committed
746
747
748
749
750
751
752
753
754
      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.
755

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

758
759
760
    See :hg:`help revert` for a way to restore files to the state
    of another revision.

761
762
    Returns 0 on success, 1 if nothing to backout or there are unresolved
    files.
763
    '''
764
    with repo.wlock(), repo.lock():
765
        return _dobackout(ui, repo, node, rev, **opts)
766

767

768
def _dobackout(ui, repo, node=None, rev=None, **opts):
769
    opts = pycompat.byteskwargs(opts)
770
771
772
773
    if opts.get(b'commit') and opts.get(b'no_commit'):
        raise error.Abort(_(b"cannot use --commit with --no-commit"))
    if opts.get(b'merge') and opts.get(b'no_commit'):
        raise error.Abort(_(b"cannot use --merge with --no-commit"))
774

Daniel Holth's avatar
Daniel Holth committed
775
    if rev and node:
776
        raise error.Abort(_(b"please specify just one revision"))
Daniel Holth's avatar
Daniel Holth committed
777
778
779

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

781
    if not rev:
782
        raise error.Abort(_(b"please specify a revision to backout"))
783

784
    date = opts.get(b'date')
785
    if date:
786
        opts[b'date'] = dateutil.parsedate(date)
787

788
    cmdutil.checkunfinished(repo)
789
    cmdutil.bailifchanged(repo)
790
    node = scmutil.revsingle(repo, rev).node()
791

792
    op1, op2 = repo.dirstate.parents()
793
    if not repo.changelog.isancestor(node, op1):
794
        raise error.Abort(_(b'cannot backout change that is not an ancestor'))
795

796
797
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
798
        raise error.Abort(_(b'cannot backout a change with no parents'))
Vadim Gelfer's avatar
Vadim Gelfer committed
799
    if p2 != nullid:
800
801
802
        if not opts.get(b'parent'):
            raise error.Abort(_(b'cannot backout a merge changeset'))
        p = repo.lookup(opts[b'parent'])
803
        if p not in (p1, p2):
804
            raise error.Abort(
805
                _(b'%s is not a parent of %s') % (short(p), short(node))
806
            )
807
808
        parent = p
    else:
809
810
        if opts.get(b'parent'):
            raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
811
        parent = p1
812

813
    # the backout should appear on the same branch
814
815
816
    branch = repo.dirstate.branch()
    bheads = repo.branchheads(branch)
    rctx = scmutil.revsingle(repo, hex(parent))
817
818
819
820
    if not opts.get(b'merge') and op1 != node:
        with dirstateguard.dirstateguard(repo, b'backout'):
            overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
            with ui.configoverride(overrides, b'backout'):
821
822
823
824
825
826
827
828
                stats = mergemod.update(
                    repo,
                    parent,
                    branchmerge=True,
                    force=True,
                    ancestor=node,
                    mergeancestor=False,
                )
829
            repo.setparents(op1, op2)
830
831
        hg._showstats(repo, stats)
        if stats.unresolvedcount:
832
            repo.ui.status(
833
                _(b"use 'hg resolve' to retry unresolved file merges\n")
834
            )
835
            return 1
836
837
838
839
840
    else:
        hg.clean(repo, node, show_stats=False)
        repo.dirstate.setbranch(branch)
        cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())

841
    if opts.get(b'no_commit'):
842
        msg = _(b"changeset %s backed out, don't forget to commit.\n")
843
844
        ui.status(msg % short(node))
        return 0
845
846

    def commitfunc(ui, repo, message, match, opts):
847
        editform = b'backout'
848
849
850
        e = cmdutil.getcommiteditor(
            editform=editform, **pycompat.strkwargs(opts)
        )
851
852
        if not message:
            # we don't translate commit messages
853
            message = b"Backed out changeset %s" % short(node)
854
            e = cmdutil.getcommiteditor(edit=True, editform=editform)
855
        return repo.commit(
856
            message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857
858
        )

859
860
    newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
    if not newnode:
861
        ui.status(_(b"nothing changed\n"))
862
863
864
865
        return 1
    cmdutil.commitstatus(repo, newnode, branch, bheads)

    def nice(node):
866
        return b'%d:%s' % (repo.changelog.rev(node), short(node))
867
868

    ui.status(
869
        _(b'changeset %s backs out changeset %s\n')