commands.py 184 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

8
from node import hex, bin, nullid, nullrev, short
9
from lock import release
10
from i18n import _, gettext
Benoit Boissinot's avatar
Benoit Boissinot committed
11
import os, re, sys, difflib, time, tempfile
12
import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13
import patch, help, url, encoding, templatekw, discovery
14
import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15
import merge as mergemod
16
import minirst, revset
17
import dagparser, context, simplemerge
18
import random, setdiscovery, treediscovery, dagutil
19

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
table = {}

command = cmdutil.command(table)

# common command options

globalopts = [
    ('R', 'repository', '',
     _('repository root directory or name of overlay bundle file'),
     _('REPO')),
    ('', 'cwd', '',
     _('change working directory'), _('DIR')),
    ('y', 'noninteractive', None,
     _('do not prompt, assume \'yes\' for any required answers')),
    ('q', 'quiet', None, _('suppress output')),
    ('v', 'verbose', None, _('enable additional output')),
    ('', 'config', [],
     _('set/override config option (use \'section.name=value\')'),
     _('CONFIG')),
    ('', 'debug', None, _('enable debugging output')),
    ('', 'debugger', None, _('start debugger')),
    ('', 'encoding', encoding.encoding, _('set the charset encoding'),
     _('ENCODE')),
    ('', 'encodingmode', encoding.encodingmode,
     _('set the charset encoding mode'), _('MODE')),
    ('', 'traceback', None, _('always print a traceback on exception')),
    ('', 'time', None, _('time how long the command takes')),
    ('', 'profile', None, _('print command execution profile')),
    ('', 'version', None, _('output version information and exit')),
    ('h', 'help', None, _('display help and exit')),
]

dryrunopts = [('n', 'dry-run', None,
               _('do not perform actions, just print output'))]

remoteopts = [
    ('e', 'ssh', '',
     _('specify ssh command to use'), _('CMD')),
    ('', 'remotecmd', '',
     _('specify hg command to run on the remote side'), _('CMD')),
    ('', 'insecure', None,
     _('do not verify server certificate (ignoring web.cacerts config)')),
]

walkopts = [
    ('I', 'include', [],
     _('include names matching the given patterns'), _('PATTERN')),
    ('X', 'exclude', [],
     _('exclude names matching the given patterns'), _('PATTERN')),
]

commitopts = [
    ('m', 'message', '',
     _('use text as commit message'), _('TEXT')),
    ('l', 'logfile', '',
     _('read commit message from file'), _('FILE')),
]

commitopts2 = [
    ('d', 'date', '',
     _('record the specified date as commit date'), _('DATE')),
    ('u', 'user', '',
     _('record the specified user as committer'), _('USER')),
]

templateopts = [
    ('', 'style', '',
     _('display using template map file'), _('STYLE')),
    ('', 'template', '',
     _('display with template'), _('TEMPLATE')),
]

logopts = [
    ('p', 'patch', None, _('show patch')),
    ('g', 'git', None, _('use git extended diff format')),
    ('l', 'limit', '',
     _('limit number of changes displayed'), _('NUM')),
    ('M', 'no-merges', None, _('do not show merges')),
    ('', 'stat', None, _('output diffstat-style summary of changes')),
] + templateopts

diffopts = [
    ('a', 'text', None, _('treat all files as text')),
    ('g', 'git', None, _('use git extended diff format')),
    ('', 'nodates', None, _('omit dates from diff headers'))
]

diffopts2 = [
    ('p', 'show-function', None, _('show which function each change is in')),
    ('', 'reverse', None, _('produce a diff that undoes the changes')),
    ('w', 'ignore-all-space', None,
     _('ignore white space when comparing lines')),
    ('b', 'ignore-space-change', None,
     _('ignore changes in the amount of white space')),
    ('B', 'ignore-blank-lines', None,
     _('ignore changes whose lines are all blank')),
    ('U', 'unified', '',
     _('number of lines of context to show'), _('NUM')),
    ('', 'stat', None, _('output diffstat-style summary of changes')),
]

similarityopts = [
    ('s', 'similarity', '',
     _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
]

subrepoopts = [
    ('S', 'subrepos', None,
     _('recurse into subrepositories'))
]

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

133 134 135
@command('^add',
    walkopts + subrepoopts + dryrunopts,
    _('[OPTION]... [FILE]...'))
136
def add(ui, repo, *pats, **opts):
137 138
    """add the specified files on the next commit

139 140
    Schedule files to be version controlled and added to the
    repository.
141

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

145
    If no names are given, add all files to the repository.
146 147 148 149

    .. container:: verbose

       An example showing how new (unknown) files are added
Martin Geisler's avatar
Martin Geisler committed
150
       automatically by :hg:`add`::
151 152 153 154 155 156 157 158

         $ ls
         foo.c
         $ hg status
         ? foo.c
         $ hg add
         adding foo.c
         $ hg status
159
         A foo.c
160 161

    Returns 0 if all files are successfully added.
162 163
    """

164
    m = scmutil.match(repo, pats, opts)
165 166
    rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
                           opts.get('subrepos'), prefix="")
167
    return rejected and 1 or 0
168

169 170 171
@command('addremove',
    similarityopts + walkopts + dryrunopts,
    _('[OPTION]... [FILE]...'))
172
def addremove(ui, repo, *pats, **opts):
173
    """add all new files, delete all missing files
174

175 176
    Add all new files and remove all missing files from the
    repository.
177

178
    New files are ignored if they match any of the patterns in
179
    ``.hgignore``. As with add, these changes take effect at the next
180
    commit.
181

182 183 184 185 186
    Use the -s/--similarity option to detect renamed files. With a
    parameter greater than 0, this compares every removed file with
    every added file and records those similar enough as renames. This
    option takes a percentage between 0 (disabled) and 100 (files must
    be identical) as its parameter. Detecting renamed files this way
187 188
    can be expensive. After using this option, :hg:`status -C` can be
    used to check which files were identified as moved or renamed.
189 190

    Returns 0 if all files are successfully added.
191
    """
192
    try:
193
        sim = float(opts.get('similarity') or 100)
194 195
    except ValueError:
        raise util.Abort(_('similarity must be a number'))
196 197
    if sim < 0 or sim > 100:
        raise util.Abort(_('similarity must be between 0 and 100'))
198
    return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213
@command('^annotate|blame',
    [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
    ('', 'follow', None,
     _('follow copies/renames and list the filename (DEPRECATED)')),
    ('', 'no-follow', None, _("don't follow copies and renames")),
    ('a', 'text', None, _('treat all files as text')),
    ('u', 'user', None, _('list the author (long with -v)')),
    ('f', 'file', None, _('list the filename')),
    ('d', 'date', None, _('list the date (short with -q)')),
    ('n', 'number', None, _('list the revision number (default)')),
    ('c', 'changeset', None, _('list the changeset')),
    ('l', 'line-number', None, _('show line number at the first appearance'))
    ] + walkopts,
    _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
214
def annotate(ui, repo, *pats, **opts):
215
    """show changeset information by line for each file
216

217 218
    List changes in files, showing the revision id responsible for
    each line
219

220 221
    This command is useful for discovering when a change was made and
    by whom.
222

223 224 225 226
    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.
227 228

    Returns 0 on success.
229
    """
230 231 232
    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
233
        opts['file'] = True
234

235 236
    datefunc = ui.quiet and util.shortdate or util.datestr
    getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
237

238
    if not pats:
239
        raise util.Abort(_('at least one filename or pattern is required'))
240

241 242 243 244
    opmap = [('user', lambda x: ui.shortuser(x[0].user())),
             ('number', lambda x: str(x[0].rev())),
             ('changeset', lambda x: short(x[0].node())),
             ('date', getdate),
245
             ('file', lambda x: x[0].path()),
246 247
            ]

248
    if (not opts.get('user') and not opts.get('changeset')
249
        and not opts.get('date') and not opts.get('file')):
250
        opts['number'] = True
251

252
    linenumber = opts.get('line_number') is not None
253
    if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
254 255 256 257 258 259 260
        raise util.Abort(_('at least one of -n/-c is required for -l'))

    funcmap = [func for op, func in opmap if opts.get(op)]
    if linenumber:
        lastfunc = funcmap[-1]
        funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])

261 262 263
    def bad(x, y):
        raise util.Abort("%s: %s" % (x, y))

264
    ctx = scmutil.revsingle(repo, opts.get('rev'))
265
    m = scmutil.match(repo, pats, opts)
266
    m.bad = bad
267
    follow = not opts.get('no_follow')
Matt Mackall's avatar
Matt Mackall committed
268 269
    for abs in ctx.walk(m):
        fctx = ctx[abs]
270
        if not opts.get('text') and util.binary(fctx.data()):
271
            ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
272 273
            continue

274
        lines = fctx.annotate(follow=follow, linenumber=linenumber)
275 276
        pieces = []

277 278 279
        for f in funcmap:
            l = [f(n) for n, dummy in lines]
            if l:
280 281 282
                sized = [(x, encoding.colwidth(x)) for x in l]
                ml = max([w for x, w in sized])
                pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
283

mpm@selenic.com's avatar
mpm@selenic.com committed
284 285 286
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
287

288 289 290 291 292 293 294 295
@command('archive',
    [('', 'no-decode', None, _('do not pass files through decoders')),
    ('p', 'prefix', '', _('directory prefix for files in archive'),
     _('PREFIX')),
    ('r', 'rev', '', _('revision to distribute'), _('REV')),
    ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
    ] + subrepoopts + walkopts,
    _('[OPTION]... DEST'))
296
def archive(ui, repo, dest, **opts):
297
    '''create an unversioned archive of a repository revision
298

299 300
    By default, the revision used is the parent of the working
    directory; use -r/--rev to specify a different revision.
301

302 303 304 305
    The archive type is automatically detected based on file
    extension (or override using -t/--type).

    Valid types are:
306

307 308 309 310 311 312
    :``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
313

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

317 318 319 320
    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.
321 322

    Returns 0 on success.
323 324
    '''

325
    ctx = scmutil.revsingle(repo, opts.get('rev'))
326
    if not ctx:
327
        raise util.Abort(_('no working directory: please specify a revision'))
328
    node = ctx.node()
329
    dest = cmdutil.makefilename(repo, dest, node)
330 331
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
332

333
    kind = opts.get('type') or archival.guesskind(dest) or 'files'
334
    prefix = opts.get('prefix')
335

336 337 338 339
    if dest == '-':
        if kind == 'files':
            raise util.Abort(_('cannot archive plain files to stdout'))
        dest = sys.stdout
340 341
        if not prefix:
            prefix = os.path.basename(repo.root) + '-%h'
342

343
    prefix = cmdutil.makefilename(repo, prefix, node)
344
    matchfn = scmutil.match(repo, [], opts)
345
    archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
346
                     matchfn, prefix, subrepos=opts.get('subrepos'))
347

348 349 350 351 352 353 354
@command('backout',
    [('', 'merge', None, _('merge with old dirstate parent after backout')),
    ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')),
    ('t', 'tool', '', _('specify merge tool')),
    ('r', 'rev', '', _('revision to backout'), _('REV')),
    ] + walkopts + commitopts + commitopts2,
    _('[OPTION]... [-r] REV'))
Daniel Holth's avatar
Daniel Holth committed
355
def backout(ui, repo, node=None, rev=None, **opts):
Vadim Gelfer's avatar
Vadim Gelfer committed
356 357
    '''reverse effect of earlier changeset

358 359 360
    Prepare a new changeset with the effect of REV undone in the
    current working directory.

361
    If REV is the parent of the working directory, then this new changeset
362 363 364 365 366 367
    is committed automatically. Otherwise, hg needs to merge the
    changes and the merged result is left uncommitted.

    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
368
    directory and a new child of REV that simply undoes REV.
369

370
    Before version 1.7, the behavior without --merge was equivalent to
371 372 373
    specifying --merge followed by :hg:`update --clean .` to cancel
    the merge and leave the child of REV as a head to be merged
    separately.
374

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

    Returns 0 on success.
378
    '''
Daniel Holth's avatar
Daniel Holth committed
379 380 381 382 383
    if rev and node:
        raise util.Abort(_("please specify just one revision"))

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

385 386 387
    if not rev:
        raise util.Abort(_("please specify a revision to backout"))

388 389 390 391
    date = opts.get('date')
    if date:
        opts['date'] = util.parsedate(date)

392
    cmdutil.bailifchanged(repo)
393
    node = scmutil.revsingle(repo, rev).node()
394

395
    op1, op2 = repo.dirstate.parents()
396 397
    a = repo.changelog.ancestor(op1, node)
    if a != node:
398
        raise util.Abort(_('cannot backout change on a different branch'))
399

400 401
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
402
        raise util.Abort(_('cannot backout a change with no parents'))
Vadim Gelfer's avatar
Vadim Gelfer committed
403
    if p2 != nullid:
404
        if not opts.get('parent'):
405
            raise util.Abort(_('cannot backout a merge changeset without '
406 407 408
                               '--parent'))
        p = repo.lookup(opts['parent'])
        if p not in (p1, p2):
409
            raise util.Abort(_('%s is not a parent of %s') %
410
                             (short(p), short(node)))
411 412
        parent = p
    else:
413
        if opts.get('parent'):
414 415
            raise util.Abort(_('cannot use --parent on non-merge changeset'))
        parent = p1
416

417 418
    # the backout should appear on the same branch
    branch = repo.dirstate.branch()
419
    hg.clean(repo, node, show_stats=False)
420
    repo.dirstate.setbranch(branch)
Vadim Gelfer's avatar
Vadim Gelfer committed
421
    revert_opts = opts.copy()
422
    revert_opts['date'] = None
423
    revert_opts['all'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
424
    revert_opts['rev'] = hex(parent)
425
    revert_opts['no_backup'] = None
Vadim Gelfer's avatar
Vadim Gelfer committed
426
    revert(ui, repo, **revert_opts)
427
    if not opts.get('merge') and op1 != node:
428 429 430 431 432
        try:
            ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
            return hg.update(repo, op1)
        finally:
            ui.setconfig('ui', 'forcemerge', '')
433

Vadim Gelfer's avatar
Vadim Gelfer committed
434 435
    commit_opts = opts.copy()
    commit_opts['addremove'] = False
436
    if not commit_opts['message'] and not commit_opts['logfile']:
437
        # we don't translate commit messages
438
        commit_opts['message'] = "Backed out changeset %s" % short(node)
439
        commit_opts['force_editor'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
440 441 442 443 444
    commit(ui, repo, **commit_opts)
    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)))
445
    if opts.get('merge') and op1 != node:
446
        hg.clean(repo, op1, show_stats=False)
447 448
        ui.status(_('merging with changeset %s\n')
                  % nice(repo.changelog.tip()))
449 450 451 452 453
        try:
            ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
            return hg.merge(repo, hex(repo.changelog.tip()))
        finally:
            ui.setconfig('ui', 'forcemerge', '')
454
    return 0
Vadim Gelfer's avatar
Vadim Gelfer committed
455

456 457 458 459 460 461 462 463 464
@command('bisect',
    [('r', 'reset', False, _('reset bisect state')),
    ('g', 'good', False, _('mark changeset good')),
    ('b', 'bad', False, _('mark changeset bad')),
    ('s', 'skip', False, _('skip testing changeset')),
    ('e', 'extend', False, _('extend the bisect range')),
    ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
    ('U', 'noupdate', False, _('do not update to target'))],
    _("[-gbsr] [-U] [-c CMD] [REV]"))
465
def bisect(ui, repo, rev=None, extra=None, command=None,
466 467
               reset=None, good=None, bad=None, skip=None, extend=None,
               noupdate=None):
468 469
    """subdivision search of changesets

470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
    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.
    Its exit status 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.
487 488

    Returns 0 on success.
489
    """
490 491 492 493 494 495 496 497
    def extendbisectrange(nodes, good):
        # bisect is incomplete when it ends on a merge node and
        # one of the parent was not checked.
        parents = repo[nodes[0]].parents()
        if len(parents) > 1:
            side = good and state['bad'] or state['good']
            num = len(set(i.node() for i in parents) & set(side))
            if num == 1:
Yuya Nishihara's avatar
Yuya Nishihara committed
498
                return parents[0].ancestor(parents[1])
499 500
        return None

501 502 503 504
    def print_result(nodes, good):
        displayer = cmdutil.show_changeset(ui, repo, {})
        if len(nodes) == 1:
            # narrowed it down to a single revision
505 506 507 508
            if good:
                ui.write(_("The first good revision is:\n"))
            else:
                ui.write(_("The first bad revision is:\n"))
509
            displayer.show(repo[nodes[0]])
510 511 512 513 514
            extendnode = extendbisectrange(nodes, good)
            if extendnode is not None:
                ui.write(_('Not all ancestors of this changeset have been'
                           ' checked.\nUse bisect --extend to continue the '
                           'bisection from\nthe common ancestor, %s.\n')
515
                         % extendnode)
516 517
        else:
            # multiple possible revisions
518 519 520 521 522 523
            if good:
                ui.write(_("Due to skipped revisions, the first "
                        "good revision could be any of:\n"))
            else:
                ui.write(_("Due to skipped revisions, the first "
                        "bad revision could be any of:\n"))
524
            for n in nodes:
525
                displayer.show(repo[n])
526
        displayer.close()
527 528 529 530 531 532 533 534 535 536 537

    def check_state(state, interactive=True):
        if not state['good'] or not state['bad']:
            if (good or bad or skip or reset) and interactive:
                return
            if not state['good']:
                raise util.Abort(_('cannot bisect (no known good revisions)'))
            else:
                raise util.Abort(_('cannot bisect (no known bad revisions)'))
        return True

538 539 540 541 542 543 544 545 546 547
    # 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
548
    elif extra or good + bad + skip + reset + extend + bool(command) > 1:
549
        raise util.Abort(_('incompatible arguments'))
550 551 552 553 554 555 556

    if reset:
        p = repo.join("bisect.state")
        if os.path.exists(p):
            os.unlink(p)
        return

557 558 559 560
    state = hbisect.load_state(repo)

    if command:
        changesets = 1
561 562 563
        try:
            while changesets:
                # update state
564
                status = util.system(command)
565 566 567 568 569 570 571 572 573 574 575
                if status == 125:
                    transition = "skip"
                elif status == 0:
                    transition = "good"
                # status < 0 means process was killed
                elif status == 127:
                    raise util.Abort(_("failed to execute %s") % command)
                elif status < 0:
                    raise util.Abort(_("%s killed") % command)
                else:
                    transition = "bad"
576
                ctx = scmutil.revsingle(repo, rev)
577
                rev = None # clear for future iterations
578 579
                state[transition].append(ctx.node())
                ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
580 581 582 583
                check_state(state, interactive=False)
                # bisect
                nodes, changesets, good = hbisect.bisect(repo.changelog, state)
                # update to next check
584
                cmdutil.bailifchanged(repo)
585 586 587
                hg.clean(repo, nodes[0], show_stats=False)
        finally:
            hbisect.save_state(repo, state)
588 589
        print_result(nodes, good)
        return
590 591

    # update state
592 593

    if rev:
594
        nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
595 596 597
    else:
        nodes = [repo.lookup('.')]

598 599
    if good or bad or skip:
        if good:
600
            state['good'] += nodes
601
        elif bad:
602
            state['bad'] += nodes
603
        elif skip:
604
            state['skip'] += nodes
605
        hbisect.save_state(repo, state)
606

607 608
    if not check_state(state):
        return
609 610

    # actually bisect
611
    nodes, changesets, good = hbisect.bisect(repo.changelog, state)
612 613 614 615 616
    if extend:
        if not changesets:
            extendnode = extendbisectrange(nodes, good)
            if extendnode is not None:
                ui.write(_("Extending search to changeset %d:%s\n"
617
                         % (extendnode.rev(), extendnode)))
618 619
                if noupdate:
                    return
620
                cmdutil.bailifchanged(repo)
621 622 623
                return hg.clean(repo, extendnode.node())
        raise util.Abort(_("nothing to extend"))

624
    if changesets == 0:
625
        print_result(nodes, good)
626 627 628
    else:
        assert len(nodes) == 1 # only a single node can be tested next
        node = nodes[0]
629 630 631 632 633
        # compute the approximate number of remaining tests
        tests, size = 0, 2
        while size <= changesets:
            tests, size = tests + 1, size * 2
        rev = repo.changelog.rev(node)
634 635
        ui.write(_("Testing changeset %d:%s "
                   "(%d changesets remaining, ~%d tests)\n")
636
                 % (rev, short(node), changesets, tests))
637
        if not noupdate:
638
            cmdutil.bailifchanged(repo)
639 640
            return hg.clean(repo, node)

641 642 643 644 645 646 647
@command('bookmarks',
    [('f', 'force', False, _('force')),
    ('r', 'rev', '', _('revision'), _('REV')),
    ('d', 'delete', False, _('delete a given bookmark')),
    ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
    ('i', 'inactive', False, _('do not mark a new bookmark active'))],
    _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
648 649
def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
             rename=None, inactive=False):
650 651 652 653 654 655 656 657 658 659 660 661 662
    '''track a line of development with movable markers

    Bookmarks are pointers to certain commits that move when
    committing. Bookmarks are local. They can be renamed, copied and
    deleted. It is possible to use bookmark names in :hg:`merge` and
    :hg:`update` to merge and update respectively to a given bookmark.

    You can use :hg:`bookmark NAME` to set a bookmark on the working
    directory's parent revision with the given name. If you specify
    a revision using -r REV (where REV may be an existing bookmark),
    the bookmark is assigned to that revision.

    Bookmarks can be pushed and pulled between repositories (see :hg:`help
663 664 665
    push` and :hg:`help pull`). This requires both the local and remote
    repositories to support bookmarks. For versions prior to 1.8, this means
    the bookmarks extension must be enabled.
666 667 668 669 670 671 672
    '''
    hexfn = ui.debugflag and hex or short
    marks = repo._bookmarks
    cur   = repo.changectx('.').node()

    if rename:
        if rename not in marks:
673
            raise util.Abort(_("bookmark '%s' does not exist") % rename)
674
        if mark in marks and not force:
675 676
            raise util.Abort(_("bookmark '%s' already exists "
                               "(use -f to force)") % mark)
677 678 679
        if mark is None:
            raise util.Abort(_("new bookmark name required"))
        marks[mark] = marks[rename]
680
        if repo._bookmarkcurrent == rename and not inactive:
681
            bookmarks.setcurrent(repo, mark)
682
        del marks[rename]
683 684 685 686 687 688 689
        bookmarks.write(repo)
        return

    if delete:
        if mark is None:
            raise util.Abort(_("bookmark name required"))
        if mark not in marks:
690
            raise util.Abort(_("bookmark '%s' does not exist") % mark)
691 692 693 694 695 696 697 698 699 700 701 702 703
        if mark == repo._bookmarkcurrent:
            bookmarks.setcurrent(repo, None)
        del marks[mark]
        bookmarks.write(repo)
        return

    if mark is not None:
        if "\n" in mark:
            raise util.Abort(_("bookmark name cannot contain newlines"))
        mark = mark.strip()
        if not mark:
            raise util.Abort(_("bookmark names cannot consist entirely of "
                               "whitespace"))
704 705 706
        if inactive and mark == repo._bookmarkcurrent:
            bookmarks.setcurrent(repo, None)
            return
707
        if mark in marks and not force:
708 709
            raise util.Abort(_("bookmark '%s' already exists "
                               "(use -f to force)") % mark)
710 711 712 713 714 715 716 717
        if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
            and not force):
            raise util.Abort(
                _("a bookmark cannot have the name of an existing branch"))
        if rev:
            marks[mark] = repo.lookup(rev)
        else:
            marks[mark] = repo.changectx('.').node()
718
        if not inactive and repo.changectx('.').node() == marks[mark]:
719
            bookmarks.setcurrent(repo, mark)
720 721 722 723 724 725 726 727 728
        bookmarks.write(repo)
        return

    if mark is None:
        if rev:
            raise util.Abort(_("bookmark name required"))
        if len(marks) == 0:
            ui.status(_("no bookmarks set\n"))
        else:
729
            for bmark, n in sorted(marks.iteritems()):
730 731 732
                current = repo._bookmarkcurrent
                if bmark == current and n == cur:
                    prefix, label = '*', 'bookmarks.current'
733
                else:
734
                    prefix, label = ' ', ''
735 736 737 738 739 740 741 742 743

                if ui.quiet:
                    ui.write("%s\n" % bmark, label=label)
                else:
                    ui.write(" %s %-25s %d:%s\n" % (
                        prefix, bmark, repo.changelog.rev(n), hexfn(n)),
                        label=label)
        return

744 745 746 747 748
@command('branch',
    [('f', 'force', None,
     _('set branch name even if it shadows an existing branch')),
    ('C', 'clean', None, _('reset branch name to parent branch name'))],
    _('[-fC] [NAME]'))
749
def branch(ui, repo, label=None, **opts):
750 751
    """set or show the current branch name

752 753 754 755 756
    With no argument, show the current branch name. With one argument,
    set the working directory branch name (the branch will not exist
    in the repository until the next commit). Standard practice
    recommends that primary development take place on the 'default'
    branch.
757

758 759
    Unless -f/--force is specified, branch will not let you set a
    branch name that already exists, even if it's inactive.
760

761 762 763
    Use -C/--clean to reset the working directory branch to that of
    the parent of the working directory, negating a previous branch
    change.
764

Martin Geisler's avatar
Martin Geisler committed
765 766
    Use the command :hg:`update` to switch to an existing branch. Use
    :hg:`commit --close-branch` to mark this branch as closed.
767 768

    Returns 0 on success.
769 770
    """

771
    if opts.get('clean'):
772
        label = repo[None].p1().branch()
773 774 775
        repo.dirstate.setbranch(label)
        ui.status(_('reset working directory to branch %s\n') % label)
    elif label:
776
        if not opts.get('force') and label in repo.branchtags():
777
            if label not in [p.branch() for p in repo.parents()]:
778
                raise util.Abort(_('a branch of the same name already exists'),
779
                                 # i18n: "it" refers to an existing branch
780
                                 hint=_("use 'hg update' to switch to it"))
781
        repo.dirstate.setbranch(label)
782
        ui.status(_('marked working directory as branch %s\n') % label)
783
    else:
784
        ui.write("%s\n" % repo.dirstate.branch())
785

786 787 788 789
@command('branches',
    [('a', 'active', False, _('show only branches that have unmerged heads')),
    ('c', 'closed', False, _('show normal and closed branches'))],
    _('[-ac]'))
790
def branches(ui, repo, active=False, closed=False):
791 792
    """list repository named branches

793 794
    List the repository's named branches, indicating which ones are
    inactive. If -c/--closed is specified, also list branches which have
795
    been marked closed (see :hg:`commit --close-branch`).
796

797 798
    If -a/--active is specified, only show active branches. A branch
    is considered active if it contains repository heads.
799

Martin Geisler's avatar
Martin Geisler committed
800
    Use the command :hg:`update` to switch to an existing branch.
801 802

    Returns 0.
803
    """
804

805
    hexfunc = ui.debugflag and hex or short
806
    activebranches = [repo[n].branch() for n in repo.heads()]
807 808 809 810 811
    def testactive(tag, node):
        realhead = tag in activebranches
        open = node in repo.branchheads(tag, closed=False)
        return realhead and open
    branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
812 813
                          for tag, node in repo.branchtags().items()],
                      reverse=True)
814 815 816

    for isactive, node, tag in branches:
        if (not active) or isactive:
817
            if ui.quiet:
818
                ui.write("%s\n" % tag)
819
            else:
820 821
                hn = repo.lookup(node)
                if isactive:
822
                    label = 'branches.active'
823 824
                    notice = ''
                elif hn not in repo.branchheads(tag, closed=False):
825 826
                    if not closed:
                        continue
827
                    label = 'branches.closed'
828
                    notice = _(' (closed)')
829
                else:
830
                    label = 'branches.inactive'
831
                    notice = _(' (inactive)')
832 833
                if tag == repo.dirstate.branch():
                    label = 'branches.current'
834
                rev = str(node).rjust(31 - encoding.colwidth(tag))
835
                rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
836 837
                tag = ui.label(tag, label)
                ui.write("%s %s%s\n" % (tag, rev, notice))
838

839 840 841 842 843 844 845 846 847 848 849 850 851
@command('bundle',
    [('f', 'force', None, _('run even when the destination is unrelated')),
    ('r', 'rev', [], _('a changeset intended to be added to the destination'),
     _('REV')),
    ('b', 'branch', [], _('a specific branch you would like to bundle'),
     _('BRANCH')),
    ('', 'base', [],
     _('a base changeset assumed to be available at the destination'),
     _('REV')),
    ('a', 'all', None, _('bundle all changesets in the repository')),
    ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
    ] + remoteopts,
    _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
852
def bundle(ui, repo, fname, dest=None, **opts):
853 854
    """create a changegroup file

855 856
    Generate a compressed changegroup file collecting changesets not
    known to be in another repository.
857

timeless's avatar
timeless committed
858 859
    If you omit the destination repository, then hg assumes the
    destination will have all the nodes you specify with --base
860 861
    parameters. To create a bundle containing all changesets, use
    -a/--all (or --base null).
862