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
    mergestate as mergestatemod,
50
    narrowspec,
Gregory Szorc's avatar
Gregory Szorc committed
51
    obsolete,
52
    obsutil,
Gregory Szorc's avatar
Gregory Szorc committed
53
54
    patch,
    phases,
55
    pycompat,
56
    rcutil,
57
    registrar,
58
    revsetlang,
59
    rewriteutil,
Gregory Szorc's avatar
Gregory Szorc committed
60
    scmutil,
61
    server,
62
    shelve as shelvemod,
63
    state as statemod,
Gregory Szorc's avatar
Gregory Szorc committed
64
    streamclone,
65
    tags as tagsmod,
Gregory Szorc's avatar
Gregory Szorc committed
66
67
    ui as uimod,
    util,
68
    verify as verifymod,
69
    wireprotoserver,
Gregory Szorc's avatar
Gregory Szorc committed
70
)
71
72
73
74
from .utils import (
    dateutil,
    stringutil,
)
Gregory Szorc's avatar
Gregory Szorc committed
75

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

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

# common command options

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

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

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

169
170

@command(
171
    b'abort',
172
173
174
175
    dryrunopts,
    helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
    helpbasic=True,
)
176
177
178
179
180
181
182
183
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.
    """
184
    dryrun = opts.get('dry_run')
185
186
    abortstate = cmdutil.getunfinishedstate(repo)
    if not abortstate:
187
        raise error.Abort(_(b'no operation in progress'))
188
    if not abortstate.abortfunc:
189
190
        raise error.Abort(
            (
191
                _(b"%s in progress but does not support 'hg abort'")
192
193
194
195
                % (abortstate._opname)
            ),
            hint=abortstate.hint(),
        )
196
    if dryrun:
197
198
199
        ui.status(
            _(b'%s in progress, will be aborted\n') % (abortstate._opname)
        )
200
201
202
        return
    return abortstate.abortfunc(ui, repo)

203
204

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

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

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

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

    .. container:: verbose

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

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

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

260
261

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

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

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

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

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

338
339

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

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

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

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

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

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

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

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

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

433
434
435
436
437
438
439
    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'))
440

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

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

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

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

468
        else:
469

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

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

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

486
    opmap = [
487
488
489
490
491
492
        (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),
493
494
    ]
    opnamemap = {
495
496
497
498
        b'rev': b'number',
        b'node': b'changeset',
        b'path': b'file',
        b'lineno': b'line_number',
499
    }
500

501
    if rootfm.isplain():
502

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

506
    else:
507

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

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

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

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

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

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

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

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

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

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

580
    rootfm.end()
581

582
583

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

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

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

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

623
    Valid types are:
624

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

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

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

    Returns 0 on success.
642
643
    '''

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

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

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

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


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

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

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

723
    .. note::
724

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

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

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

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

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

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

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

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

768

769
def _dobackout(ui, repo, node=None, rev=None, **opts):
770
    opts = pycompat.byteskwargs(opts)
771
772
773
774
    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"))
775

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

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

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

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

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

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

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

814
    # the backout should appear on the same branch
815
816
817
    branch = repo.dirstate.branch()
    bheads = repo.branchheads(branch)
    rctx = scmutil.revsingle(repo, hex(parent))
818
819
820
821
    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'):
822
823
824
825
826
827
828
829
                stats = mergemod.update(
                    repo,
                    parent,
                    branchmerge=True,
                    force=True,
                    ancestor=node,
                    mergeancestor=False,
                )
830
            repo.setparents(op1, op2)
831
832
        hg._showstats(repo, stats)
        if stats.unresolvedcount:
833
            repo.ui.status(
834
                _(b"use 'hg resolve' to retry unresolved file merges\n")
835
            )
836
            return 1
837
838
839
840
841
    else:
        hg.clean(repo, node, show_stats=False)
        repo.dirstate.setbranch(branch)
        cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())

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

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

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

    def nice(node):