commands.py 250 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
from __future__ import absolute_import

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

from .i18n import _
from .node import (
    hex,
    nullid,
    nullrev,
    short,
21
22
    wdirhex,
    wdirrev,
Gregory Szorc's avatar
Gregory Szorc committed
23
)
24
from .pycompat import open
Gregory Szorc's avatar
Gregory Szorc committed
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
    graphmod,
43
    grep as grepmod,
Gregory Szorc's avatar
Gregory Szorc committed
44
45
46
    hbisect,
    help,
    hg,
47
    logcmdutil,
48
    match as matchmod,
Gregory Szorc's avatar
Gregory Szorc committed
49
    merge as mergemod,
50
    mergestate as mergestatemod,
51
    narrowspec,
Gregory Szorc's avatar
Gregory Szorc committed
52
    obsolete,
53
    obsutil,
Gregory Szorc's avatar
Gregory Szorc committed
54
55
    patch,
    phases,
56
    pycompat,
57
    rcutil,
58
    registrar,
59
    requirements,
60
    revsetlang,
61
    rewriteutil,
Gregory Szorc's avatar
Gregory Szorc committed
62
    scmutil,
63
    server,
64
    shelve as shelvemod,
65
    state as statemod,
Gregory Szorc's avatar
Gregory Szorc committed
66
    streamclone,
67
    tags as tagsmod,
Gregory Szorc's avatar
Gregory Szorc committed
68
69
    ui as uimod,
    util,
70
    verify as verifymod,
71
    vfs as vfsmod,
72
    wireprotoserver,
Gregory Szorc's avatar
Gregory Szorc committed
73
)
74
75
76
77
from .utils import (
    dateutil,
    stringutil,
)
Gregory Szorc's avatar
Gregory Szorc committed
78

79
table = {}
80
table.update(debugcommandsmod.command._table)
81

82
command = registrar.command(table)
83
INTENT_READONLY = registrar.INTENT_READONLY
84
85
86
87

# common command options

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

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

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

172
173

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

206
207

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

218
219
    Schedule files to be version controlled and added to the
    repository.
220

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

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

    .. container:: verbose

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
       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
254
255

    Returns 0 if all files are successfully added.
256
257
    """

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

263
264

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

274
275
    Add all new files and remove all missing files from the
    repository.
276

timeless's avatar
timeless committed
277
278
279
    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.
280

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

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

341
342

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

384
    List changes in files, showing the revision id responsible for
385
    each line.
386

387
388
    This command is useful for discovering when a change was made and
    by whom.
389

390
391
392
    If you include --file, --user, or --date, the revision number is
    suppressed unless you also include --number.

393
394
395
396
    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.
397

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

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

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

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

436
437
438
439
440
441
442
    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'))
443

444
    rev = opts.get(b'rev')
445
    if rev:
446
        repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
447
    ctx = scmutil.revsingle(repo, rev)
448

449
450
    ui.pager(b'annotate')
    rootfm = ui.formatter(b'annotate', opts)
451
452
453
    if ui.debugflag:
        shorthex = pycompat.identity
    else:
454

455
456
        def shorthex(h):
            return h[:12]
457

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

471
        else:
472

473
            def formatrev(rev):
474
                if rev == wdirrev:
475
                    return b'%d+' % ctx.p1().rev()
476
                else:
477
                    return b'%d ' % rev
478

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

485
    else:
486
487
        formatrev = b'%d'.__mod__
        formathex = shorthex
488

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

504
    if rootfm.isplain():
505

506
        def makefunc(get, fmt):
507
            return lambda x: fmt(get(x))
508

509
    else:
510

511
        def makefunc(get, fmt):
512
            return get
513

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

527
    def bad(x, y):
528
        raise error.Abort(b"%s: %s" % (x, y))
529

530
531
    m = scmutil.match(ctx, pats, opts, badfn=bad)

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

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

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

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

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

579
580
        if not lines[-1].text.endswith(b'\n'):
            fm.plain(b'\n')
581
        fm.end()
582

583
    rootfm.end()
584

585
586

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

608
609
    By default, the revision used is the parent of the working
    directory; use -r/--rev to specify a different revision.
610

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

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

626
    Valid types are:
627

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

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

639
640
641
642
    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.
643
644

    Returns 0 on success.
645
646
    '''

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

659
660
    kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
    prefix = opts.get(b'prefix')
661

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

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


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

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

723
    If REV is the parent of the working directory, then this new changeset
724
    is committed automatically (unless --no-commit is specified).
725

726
    .. note::
727

728
       :hg:`backout` cannot be used to fix either an unwanted or
729
       incorrect merge.
730

Matt Mackall's avatar
Matt Mackall committed
731
732
    .. container:: verbose

733
734
735
736
737
738
739
740
741
742
743
744
      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
745
        leave changes uncommitted::
746

747
748
          hg backout -r 23 --no-commit
          hg commit -m "Backout revision 23"
749

Matt Mackall's avatar
Matt Mackall committed
750
751
752
753
754
755
756
757
758
      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.
759

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

762
763
764
    See :hg:`help revert` for a way to restore files to the state
    of another revision.

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

771

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

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

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

785
    if not rev:
786
        raise error.Abort(_(b"please specify a revision to backout"))
787

788
    date = opts.get(b'date')
789
    if date:
790
        opts[b'date'] = dateutil.parsedate(date)
791

792
    cmdutil.checkunfinished(repo)
793
    cmdutil.bailifchanged(repo)
794
795
    ctx = scmutil.revsingle(repo, rev)
    node = ctx.node()
796

797
    op1, op2 = repo.dirstate.parents()
798
    if not repo.changelog.isancestor(node, op1):
799
        raise error.Abort(_(b'cannot backout change that is not an ancestor'))
800

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

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

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

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

857
858
    newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
    if not newnode: