Exemple #1
0
def applychanges(ui, repo, ctx, opts):
    """Merge changeset from ctx (only) in the current working directory"""
    wcpar = repo.dirstate.parents()[0]
    if ctx.p1().node() == wcpar:
        # edition ar "in place" we do not need to make any merge,
        # just applies changes on parent for edition
        cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
        stats = None
    else:
        try:
            # ui.forcemerge is an internal variable, do not document
            repo.ui.setconfig("ui", "forcemerge", opts.get("tool", ""))
            stats = mergemod.update(repo, ctx.node(), True, True, False, ctx.p1().node())
        finally:
            repo.ui.setconfig("ui", "forcemerge", "")
        repo.setparents(wcpar, node.nullid)
        repo.dirstate.write()
        # fix up dirstate for copies and renames
    cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
    return stats
Exemple #2
0
def applychanges(ui, repo, ctx, opts):
    """Merge changeset from ctx (only) in the current working directory"""
    wcpar = repo.dirstate.parents()[0]
    if ctx.p1().node() == wcpar:
        # edition ar "in place" we do not need to make any merge,
        # just applies changes on parent for edition
        cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
        stats = None
    else:
        try:
            # ui.forcemerge is an internal variable, do not document
            repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
            stats = mergemod.update(repo, ctx.node(), True, True, False,
                                    ctx.p1().node())
        finally:
            repo.ui.setconfig('ui', 'forcemerge', '')
        repo.setparents(wcpar, node.nullid)
        repo.dirstate.write()
        # fix up dirstate for copies and renames
    cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
    return stats
Exemple #3
0
def rebase(ui, repo, **opts):
    """move changeset (and descendants) to a different branch

    Rebase uses repeated merging to graft changesets from one part of
    history (the source) onto another (the destination). This can be
    useful for linearizing *local* changes relative to a master
    development tree.

    You should not rebase changesets that have already been shared
    with others. Doing so will force everybody else to perform the
    same rebase or they will end up with duplicated changesets after
    pulling in your rebased changesets.

    If you don't specify a destination changeset (``-d/--dest``),
    rebase uses the tipmost head of the current named branch as the
    destination. (The destination changeset is not modified by
    rebasing, but new changesets are added as its descendants.)

    You can specify which changesets to rebase in two ways: as a
    "source" changeset or as a "base" changeset. Both are shorthand
    for a topologically related set of changesets (the "source
    branch"). If you specify source (``-s/--source``), rebase will
    rebase that changeset and all of its descendants onto dest. If you
    specify base (``-b/--base``), rebase will select ancestors of base
    back to but not including the common ancestor with dest. Thus,
    ``-b`` is less precise but more convenient than ``-s``: you can
    specify any changeset in the source branch, and rebase will select
    the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
    uses the parent of the working directory as the base.

    By default, rebase recreates the changesets in the source branch
    as descendants of dest and then destroys the originals. Use
    ``--keep`` to preserve the original source changesets. Some
    changesets in the source branch (e.g. merges from the destination
    branch) may be dropped if they no longer contribute any change.

    One result of the rules for selecting the destination changeset
    and source branch is that, unlike ``merge``, rebase will do
    nothing if you are at the latest (tipmost) head of a named branch
    with two heads. You need to explicitly specify source and/or
    destination (or ``update`` to the other head, if it's the head of
    the intended source branch).

    If a rebase is interrupted to manually resolve a merge, it can be
    continued with --continue/-c or aborted with --abort/-a.

    Returns 0 on success, 1 if nothing to rebase.
    """
    originalwd = target = None
    external = nullrev
    state = {}
    skipped = set()
    targetancestors = set()

    editor = None
    if opts.get('edit'):
        editor = cmdutil.commitforceeditor

    lock = wlock = None
    try:
        lock = repo.lock()
        wlock = repo.wlock()

        # Validate input and define rebasing points
        destf = opts.get('dest', None)
        srcf = opts.get('source', None)
        basef = opts.get('base', None)
        revf = opts.get('rev', [])
        contf = opts.get('continue')
        abortf = opts.get('abort')
        collapsef = opts.get('collapse', False)
        collapsemsg = cmdutil.logmessage(ui, opts)
        extrafn = opts.get('extrafn')  # internal, used by e.g. hgsubversion
        keepf = opts.get('keep', False)
        keepbranchesf = opts.get('keepbranches', False)
        detachf = opts.get('detach', False)
        # keepopen is not meant for use on the command line, but by
        # other extensions
        keepopen = opts.get('keepopen', False)

        if collapsemsg and not collapsef:
            raise util.Abort(_('message can only be specified with collapse'))

        if contf or abortf:
            if contf and abortf:
                raise util.Abort(_('cannot use both abort and continue'))
            if collapsef:
                raise util.Abort(
                    _('cannot use collapse with continue or abort'))
            if detachf:
                raise util.Abort(_('cannot use detach with continue or abort'))
            if srcf or basef or destf:
                raise util.Abort(
                    _('abort and continue do not allow specifying revisions'))
            if opts.get('tool', False):
                ui.warn(_('tool option will be ignored\n'))

            (originalwd, target, state, skipped, collapsef, keepf,
             keepbranchesf, external) = restorestatus(repo)
            if abortf:
                return abort(repo, originalwd, target, state)
        else:
            if srcf and basef:
                raise util.Abort(
                    _('cannot specify both a '
                      'source and a base'))
            if revf and basef:
                raise util.Abort(
                    _('cannot specify both a '
                      'revision and a base'))
            if revf and srcf:
                raise util.Abort(
                    _('cannot specify both a '
                      'revision and a source'))
            if detachf:
                if not srcf:
                    raise util.Abort(
                        _('detach requires a revision to be specified'))
                if basef:
                    raise util.Abort(_('cannot specify a base with detach'))

            cmdutil.bailifchanged(repo)

            if not destf:
                # Destination defaults to the latest revision in the
                # current branch
                branch = repo[None].branch()
                dest = repo[branch]
            else:
                dest = repo[destf]

            if revf:
                revgen = repo.set('%lr', revf)
            elif srcf:
                revgen = repo.set('(%r)::', srcf)
            else:
                base = basef or '.'
                revgen = repo.set('(children(ancestor(%r, %d)) and ::(%r))::',
                                  base, dest, base)

            rebaseset = [c.rev() for c in revgen]

            if not rebaseset:
                repo.ui.debug('base is ancestor of destination')
                result = None
            elif not keepf and list(
                    repo.set('first(children(%ld) - %ld)', rebaseset,
                             rebaseset)):
                raise util.Abort(
                    _("can't remove original changesets with"
                      " unrebased descendants"),
                    hint=_('use --keep to keep original changesets'))
            else:
                result = buildstate(repo, dest, rebaseset, detachf)

            if not result:
                # Empty state built, nothing to rebase
                ui.status(_('nothing to rebase\n'))
                return 1
            else:
                originalwd, target, state = result
                if collapsef:
                    targetancestors = set(repo.changelog.ancestors(target))
                    external = checkexternal(repo, state, targetancestors)

        if keepbranchesf:
            assert not extrafn, 'cannot use both keepbranches and extrafn'

            def extrafn(ctx, extra):
                extra['branch'] = ctx.branch()

            if collapsef:
                branches = set()
                for rev in state:
                    branches.add(repo[rev].branch())
                    if len(branches) > 1:
                        raise util.Abort(
                            _('cannot collapse multiple named '
                              'branches'))

        # Rebase
        if not targetancestors:
            targetancestors = set(repo.changelog.ancestors(target))
            targetancestors.add(target)

        # Keep track of the current bookmarks in order to reset them later
        currentbookmarks = repo._bookmarks.copy()

        sortedstate = sorted(state)
        total = len(sortedstate)
        pos = 0
        for rev in sortedstate:
            pos += 1
            if state[rev] == -1:
                ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
                            _('changesets'), total)
                storestatus(repo, originalwd, target, state, collapsef, keepf,
                            keepbranchesf, external)
                p1, p2 = defineparents(repo, rev, target, state,
                                       targetancestors)
                if len(repo.parents()) == 2:
                    repo.ui.debug('resuming interrupted rebase\n')
                else:
                    try:
                        ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
                        stats = rebasenode(repo, rev, p1, state)
                        if stats and stats[3] > 0:
                            raise util.Abort(
                                _('unresolved conflicts (see hg '
                                  'resolve, then hg rebase --continue)'))
                    finally:
                        ui.setconfig('ui', 'forcemerge', '')
                cmdutil.duplicatecopies(repo, rev, target, p2)
                if not collapsef:
                    newrev = concludenode(repo,
                                          rev,
                                          p1,
                                          p2,
                                          extrafn=extrafn,
                                          editor=editor)
                else:
                    # Skip commit if we are collapsing
                    repo.dirstate.setparents(repo[p1].node())
                    newrev = None
                # Update the state
                if newrev is not None:
                    state[rev] = repo[newrev].rev()
                else:
                    if not collapsef:
                        ui.note(_('no changes, revision %d skipped\n') % rev)
                        ui.debug('next revision set to %s\n' % p1)
                        skipped.add(rev)
                    state[rev] = p1

        ui.progress(_('rebasing'), None)
        ui.note(_('rebase merging completed\n'))

        if collapsef and not keepopen:
            p1, p2 = defineparents(repo, min(state), target, state,
                                   targetancestors)
            if collapsemsg:
                commitmsg = collapsemsg
            else:
                commitmsg = 'Collapsed revision'
                for rebased in state:
                    if rebased not in skipped and state[rebased] != nullmerge:
                        commitmsg += '\n* %s' % repo[rebased].description()
                commitmsg = ui.edit(commitmsg, repo.ui.username())
            newrev = concludenode(repo,
                                  rev,
                                  p1,
                                  external,
                                  commitmsg=commitmsg,
                                  extrafn=extrafn,
                                  editor=editor)

        if 'qtip' in repo.tags():
            updatemq(repo, state, skipped, **opts)

        if currentbookmarks:
            # Nodeids are needed to reset bookmarks
            nstate = {}
            for k, v in state.iteritems():
                if v != nullmerge:
                    nstate[repo[k].node()] = repo[v].node()

        if not keepf:
            # Remove no more useful revisions
            rebased = [rev for rev in state if state[rev] != nullmerge]
            if rebased:
                if set(repo.changelog.descendants(min(rebased))) - set(state):
                    ui.warn(
                        _("warning: new changesets detected "
                          "on source branch, not stripping\n"))
                else:
                    # backup the old csets by default
                    repair.strip(ui, repo, repo[min(rebased)].node(), "all")

        if currentbookmarks:
            updatebookmarks(repo, nstate, currentbookmarks, **opts)

        clearstatus(repo)
        ui.note(_("rebase completed\n"))
        if os.path.exists(repo.sjoin('undo')):
            util.unlinkpath(repo.sjoin('undo'))
        if skipped:
            ui.note(_("%d revisions have been skipped\n") % len(skipped))
    finally:
        release(lock, wlock)
Exemple #4
0
def rebase(ui, repo, **opts):
    """move changeset (and descendants) to a different branch

    Rebase uses repeated merging to graft changesets from one part of
    history (the source) onto another (the destination). This can be
    useful for linearizing *local* changes relative to a master
    development tree.

    You should not rebase changesets that have already been shared
    with others. Doing so will force everybody else to perform the
    same rebase or they will end up with duplicated changesets after
    pulling in your rebased changesets.

    In its default configuration, Mercurial will prevent you from
    rebasing published changes. See :hg:`help phases` for details.

    If you don't specify a destination changeset (``-d/--dest``),
    rebase uses the current branch tip as the destination. (The
    destination changeset is not modified by rebasing, but new
    changesets are added as its descendants.)

    You can specify which changesets to rebase in two ways: as a
    "source" changeset or as a "base" changeset. Both are shorthand
    for a topologically related set of changesets (the "source
    branch"). If you specify source (``-s/--source``), rebase will
    rebase that changeset and all of its descendants onto dest. If you
    specify base (``-b/--base``), rebase will select ancestors of base
    back to but not including the common ancestor with dest. Thus,
    ``-b`` is less precise but more convenient than ``-s``: you can
    specify any changeset in the source branch, and rebase will select
    the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
    uses the parent of the working directory as the base.

    For advanced usage, a third way is available through the ``--rev``
    option. It allows you to specify an arbitrary set of changesets to
    rebase. Descendants of revs you specify with this option are not
    automatically included in the rebase.

    By default, rebase recreates the changesets in the source branch
    as descendants of dest and then destroys the originals. Use
    ``--keep`` to preserve the original source changesets. Some
    changesets in the source branch (e.g. merges from the destination
    branch) may be dropped if they no longer contribute any change.

    One result of the rules for selecting the destination changeset
    and source branch is that, unlike ``merge``, rebase will do
    nothing if you are at the branch tip of a named branch
    with two heads. You need to explicitly specify source and/or
    destination (or ``update`` to the other head, if it's the head of
    the intended source branch).

    If a rebase is interrupted to manually resolve a merge, it can be
    continued with --continue/-c or aborted with --abort/-a.

    Returns 0 on success, 1 if nothing to rebase or there are
    unresolved conflicts.
    """
    originalwd = target = None
    activebookmark = None
    external = nullrev
    state = {}
    skipped = set()
    targetancestors = set()

    editor = None
    if opts.get('edit'):
        editor = cmdutil.commitforceeditor

    lock = wlock = None
    try:
        wlock = repo.wlock()
        lock = repo.lock()

        # Validate input and define rebasing points
        destf = opts.get('dest', None)
        srcf = opts.get('source', None)
        basef = opts.get('base', None)
        revf = opts.get('rev', [])
        contf = opts.get('continue')
        abortf = opts.get('abort')
        collapsef = opts.get('collapse', False)
        collapsemsg = cmdutil.logmessage(ui, opts)
        e = opts.get('extrafn') # internal, used by e.g. hgsubversion
        extrafns = [_savegraft]
        if e:
            extrafns = [e]
        keepf = opts.get('keep', False)
        keepbranchesf = opts.get('keepbranches', False)
        # keepopen is not meant for use on the command line, but by
        # other extensions
        keepopen = opts.get('keepopen', False)

        if collapsemsg and not collapsef:
            raise util.Abort(
                _('message can only be specified with collapse'))

        if contf or abortf:
            if contf and abortf:
                raise util.Abort(_('cannot use both abort and continue'))
            if collapsef:
                raise util.Abort(
                    _('cannot use collapse with continue or abort'))
            if srcf or basef or destf:
                raise util.Abort(
                    _('abort and continue do not allow specifying revisions'))
            if opts.get('tool', False):
                ui.warn(_('tool option will be ignored\n'))

            try:
                (originalwd, target, state, skipped, collapsef, keepf,
                 keepbranchesf, external, activebookmark) = restorestatus(repo)
            except error.RepoLookupError:
                if abortf:
                    clearstatus(repo)
                    repo.ui.warn(_('rebase aborted (no revision is removed,'
                                   ' only broken state is cleared)\n'))
                    return 0
                else:
                    msg = _('cannot continue inconsistent rebase')
                    hint = _('use "hg rebase --abort" to clear borken state')
                    raise util.Abort(msg, hint=hint)
            if abortf:
                return abort(repo, originalwd, target, state)
        else:
            if srcf and basef:
                raise util.Abort(_('cannot specify both a '
                                   'source and a base'))
            if revf and basef:
                raise util.Abort(_('cannot specify both a '
                                   'revision and a base'))
            if revf and srcf:
                raise util.Abort(_('cannot specify both a '
                                   'revision and a source'))

            cmdutil.checkunfinished(repo)
            cmdutil.bailifchanged(repo)

            if not destf:
                # Destination defaults to the latest revision in the
                # current branch
                branch = repo[None].branch()
                dest = repo[branch]
            else:
                dest = scmutil.revsingle(repo, destf)

            if revf:
                rebaseset = scmutil.revrange(repo, revf)
            elif srcf:
                src = scmutil.revrange(repo, [srcf])
                rebaseset = repo.revs('(%ld)::', src)
            else:
                base = scmutil.revrange(repo, [basef or '.'])
                rebaseset = repo.revs(
                    '(children(ancestor(%ld, %d)) and ::(%ld))::',
                    base, dest, base)
            if rebaseset:
                root = min(rebaseset)
            else:
                root = None

            if not rebaseset:
                repo.ui.debug('base is ancestor of destination\n')
                result = None
            elif (not (keepf or obsolete._enabled)
                  and repo.revs('first(children(%ld) - %ld)',
                                rebaseset, rebaseset)):
                raise util.Abort(
                    _("can't remove original changesets with"
                      " unrebased descendants"),
                    hint=_('use --keep to keep original changesets'))
            else:
                result = buildstate(repo, dest, rebaseset, collapsef)

            if not result:
                # Empty state built, nothing to rebase
                ui.status(_('nothing to rebase\n'))
                return 1
            elif not keepf and not repo[root].mutable():
                raise util.Abort(_("can't rebase immutable changeset %s")
                                 % repo[root],
                                 hint=_('see hg help phases for details'))
            else:
                originalwd, target, state = result
                if collapsef:
                    targetancestors = repo.changelog.ancestors([target],
                                                               inclusive=True)
                    external = externalparent(repo, state, targetancestors)

        if keepbranchesf:
            # insert _savebranch at the start of extrafns so if
            # there's a user-provided extrafn it can clobber branch if
            # desired
            extrafns.insert(0, _savebranch)
            if collapsef:
                branches = set()
                for rev in state:
                    branches.add(repo[rev].branch())
                    if len(branches) > 1:
                        raise util.Abort(_('cannot collapse multiple named '
                            'branches'))


        # Rebase
        if not targetancestors:
            targetancestors = repo.changelog.ancestors([target], inclusive=True)

        # Keep track of the current bookmarks in order to reset them later
        currentbookmarks = repo._bookmarks.copy()
        activebookmark = activebookmark or repo._bookmarkcurrent
        if activebookmark:
            bookmarks.unsetcurrent(repo)

        extrafn = _makeextrafn(extrafns)

        sortedstate = sorted(state)
        total = len(sortedstate)
        pos = 0
        for rev in sortedstate:
            pos += 1
            if state[rev] == -1:
                ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
                            _('changesets'), total)
                p1, p2 = defineparents(repo, rev, target, state,
                                                        targetancestors)
                storestatus(repo, originalwd, target, state, collapsef, keepf,
                            keepbranchesf, external, activebookmark)
                if len(repo.parents()) == 2:
                    repo.ui.debug('resuming interrupted rebase\n')
                else:
                    try:
                        ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
                        stats = rebasenode(repo, rev, p1, state, collapsef)
                        if stats and stats[3] > 0:
                            raise error.InterventionRequired(
                                _('unresolved conflicts (see hg '
                                  'resolve, then hg rebase --continue)'))
                    finally:
                        ui.setconfig('ui', 'forcemerge', '')
                cmdutil.duplicatecopies(repo, rev, target)
                if not collapsef:
                    newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
                                          editor=editor)
                else:
                    # Skip commit if we are collapsing
                    repo.setparents(repo[p1].node())
                    newrev = None
                # Update the state
                if newrev is not None:
                    state[rev] = repo[newrev].rev()
                else:
                    if not collapsef:
                        ui.note(_('no changes, revision %d skipped\n') % rev)
                        ui.debug('next revision set to %s\n' % p1)
                        skipped.add(rev)
                    state[rev] = p1

        ui.progress(_('rebasing'), None)
        ui.note(_('rebase merging completed\n'))

        if collapsef and not keepopen:
            p1, p2 = defineparents(repo, min(state), target,
                                                        state, targetancestors)
            if collapsemsg:
                commitmsg = collapsemsg
            else:
                commitmsg = 'Collapsed revision'
                for rebased in state:
                    if rebased not in skipped and state[rebased] > nullmerge:
                        commitmsg += '\n* %s' % repo[rebased].description()
                commitmsg = ui.edit(commitmsg, repo.ui.username())
            newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
                                  extrafn=extrafn, editor=editor)
            for oldrev in state.iterkeys():
                if state[oldrev] > nullmerge:
                    state[oldrev] = newrev

        if 'qtip' in repo.tags():
            updatemq(repo, state, skipped, **opts)

        if currentbookmarks:
            # Nodeids are needed to reset bookmarks
            nstate = {}
            for k, v in state.iteritems():
                if v > nullmerge:
                    nstate[repo[k].node()] = repo[v].node()
            # XXX this is the same as dest.node() for the non-continue path --
            # this should probably be cleaned up
            targetnode = repo[target].node()

        # restore original working directory
        # (we do this before stripping)
        newwd = state.get(originalwd, originalwd)
        if newwd not in [c.rev() for c in repo[None].parents()]:
            ui.note(_("update back to initial working directory parent\n"))
            hg.updaterepo(repo, newwd, False)

        if not keepf:
            collapsedas = None
            if collapsef:
                collapsedas = newrev
            clearrebased(ui, repo, state, skipped, collapsedas)

        if currentbookmarks:
            updatebookmarks(repo, targetnode, nstate, currentbookmarks)

        clearstatus(repo)
        ui.note(_("rebase completed\n"))
        util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
        if skipped:
            ui.note(_("%d revisions have been skipped\n") % len(skipped))

        if (activebookmark and
            repo['.'].node() == repo._bookmarks[activebookmark]):
                bookmarks.setcurrent(repo, activebookmark)

    finally:
        release(lock, wlock)
Exemple #5
0
def rebase(ui, repo, **opts):
    """move changeset (and descendants) to a different branch

    Rebase uses repeated merging to graft changesets from one part of
    history (the source) onto another (the destination). This can be
    useful for linearizing *local* changes relative to a master
    development tree.

    You should not rebase changesets that have already been shared
    with others. Doing so will force everybody else to perform the
    same rebase or they will end up with duplicated changesets after
    pulling in your rebased changesets.

    If you don't specify a destination changeset (``-d/--dest``),
    rebase uses the tipmost head of the current named branch as the
    destination. (The destination changeset is not modified by
    rebasing, but new changesets are added as its descendants.)

    You can specify which changesets to rebase in two ways: as a
    "source" changeset or as a "base" changeset. Both are shorthand
    for a topologically related set of changesets (the "source
    branch"). If you specify source (``-s/--source``), rebase will
    rebase that changeset and all of its descendants onto dest. If you
    specify base (``-b/--base``), rebase will select ancestors of base
    back to but not including the common ancestor with dest. Thus,
    ``-b`` is less precise but more convenient than ``-s``: you can
    specify any changeset in the source branch, and rebase will select
    the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
    uses the parent of the working directory as the base.

    By default, rebase recreates the changesets in the source branch
    as descendants of dest and then destroys the originals. Use
    ``--keep`` to preserve the original source changesets. Some
    changesets in the source branch (e.g. merges from the destination
    branch) may be dropped if they no longer contribute any change.

    One result of the rules for selecting the destination changeset
    and source branch is that, unlike ``merge``, rebase will do
    nothing if you are at the latest (tipmost) head of a named branch
    with two heads. You need to explicitly specify source and/or
    destination (or ``update`` to the other head, if it's the head of
    the intended source branch).

    If a rebase is interrupted to manually resolve a merge, it can be
    continued with --continue/-c or aborted with --abort/-a.

    Returns 0 on success, 1 if nothing to rebase.
    """
    originalwd = target = None
    external = nullrev
    state = {}
    skipped = set()
    targetancestors = set()

    editor = None
    if opts.get('edit'):
        editor = cmdutil.commitforceeditor

    lock = wlock = None
    try:
        wlock = repo.wlock()
        lock = repo.lock()

        # Validate input and define rebasing points
        destf = opts.get('dest', None)
        srcf = opts.get('source', None)
        basef = opts.get('base', None)
        revf = opts.get('rev', [])
        contf = opts.get('continue')
        abortf = opts.get('abort')
        collapsef = opts.get('collapse', False)
        collapsemsg = cmdutil.logmessage(ui, opts)
        extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
        keepf = opts.get('keep', False)
        keepbranchesf = opts.get('keepbranches', False)
        detachf = opts.get('detach', False)
        # keepopen is not meant for use on the command line, but by
        # other extensions
        keepopen = opts.get('keepopen', False)

        if collapsemsg and not collapsef:
            raise util.Abort(
                _('message can only be specified with collapse'))

        if contf or abortf:
            if contf and abortf:
                raise util.Abort(_('cannot use both abort and continue'))
            if collapsef:
                raise util.Abort(
                    _('cannot use collapse with continue or abort'))
            if detachf:
                raise util.Abort(_('cannot use detach with continue or abort'))
            if srcf or basef or destf:
                raise util.Abort(
                    _('abort and continue do not allow specifying revisions'))
            if opts.get('tool', False):
                ui.warn(_('tool option will be ignored\n'))

            (originalwd, target, state, skipped, collapsef, keepf,
                                keepbranchesf, external) = restorestatus(repo)
            if abortf:
                return abort(repo, originalwd, target, state)
        else:
            if srcf and basef:
                raise util.Abort(_('cannot specify both a '
                                   'source and a base'))
            if revf and basef:
                raise util.Abort(_('cannot specify both a '
                                   'revision and a base'))
            if revf and srcf:
                raise util.Abort(_('cannot specify both a '
                                   'revision and a source'))
            if detachf:
                if not (srcf or revf):
                    raise util.Abort(
                        _('detach requires a revision to be specified'))
                if basef:
                    raise util.Abort(_('cannot specify a base with detach'))

            cmdutil.bailifchanged(repo)

            if not destf:
                # Destination defaults to the latest revision in the
                # current branch
                branch = repo[None].branch()
                dest = repo[branch]
            else:
                dest = repo[destf]

            if revf:
                rebaseset = repo.revs('%lr', revf)
            elif srcf:
                src = scmutil.revrange(repo, [srcf])
                rebaseset = repo.revs('(%ld)::', src)
            else:
                base = scmutil.revrange(repo, [basef or '.'])
                rebaseset = repo.revs(
                    '(children(ancestor(%ld, %d)) and ::(%ld))::',
                    base, dest, base)

            if rebaseset:
                root = min(rebaseset)
            else:
                root = None

            if not rebaseset:
                repo.ui.debug('base is ancestor of destination')
                result = None
            elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
                                              rebaseset, rebaseset)):
                raise util.Abort(
                    _("can't remove original changesets with"
                      " unrebased descendants"),
                    hint=_('use --keep to keep original changesets'))
            elif not keepf and not repo[root].mutable():
                raise util.Abort(_("can't rebase immutable changeset %s")
                                 % repo[root],
                                 hint=_('see hg help phases for details'))
            else:
                result = buildstate(repo, dest, rebaseset, detachf)

            if not result:
                # Empty state built, nothing to rebase
                ui.status(_('nothing to rebase\n'))
                return 1
            else:
                originalwd, target, state = result
                if collapsef:
                    targetancestors = set(repo.changelog.ancestors(target))
                    targetancestors.add(target)
                    external = checkexternal(repo, state, targetancestors)

        if keepbranchesf:
            assert not extrafn, 'cannot use both keepbranches and extrafn'
            def extrafn(ctx, extra):
                extra['branch'] = ctx.branch()
            if collapsef:
                branches = set()
                for rev in state:
                    branches.add(repo[rev].branch())
                    if len(branches) > 1:
                        raise util.Abort(_('cannot collapse multiple named '
                            'branches'))


        # Rebase
        if not targetancestors:
            targetancestors = set(repo.changelog.ancestors(target))
            targetancestors.add(target)

        # Keep track of the current bookmarks in order to reset them later
        currentbookmarks = repo._bookmarks.copy()
        activebookmark = repo._bookmarkcurrent
        if activebookmark:
            bookmarks.unsetcurrent(repo)

        sortedstate = sorted(state)
        total = len(sortedstate)
        pos = 0
        for rev in sortedstate:
            pos += 1
            if state[rev] == -1:
                ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
                            _('changesets'), total)
                storestatus(repo, originalwd, target, state, collapsef, keepf,
                                                    keepbranchesf, external)
                p1, p2 = defineparents(repo, rev, target, state,
                                                        targetancestors)
                if len(repo.parents()) == 2:
                    repo.ui.debug('resuming interrupted rebase\n')
                else:
                    try:
                        ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
                        stats = rebasenode(repo, rev, p1, state)
                        if stats and stats[3] > 0:
                            raise util.Abort(_('unresolved conflicts (see hg '
                                        'resolve, then hg rebase --continue)'))
                    finally:
                        ui.setconfig('ui', 'forcemerge', '')
                cmdutil.duplicatecopies(repo, rev, target)
                if not collapsef:
                    newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
                                          editor=editor)
                else:
                    # Skip commit if we are collapsing
                    repo.setparents(repo[p1].node())
                    newrev = None
                # Update the state
                if newrev is not None:
                    state[rev] = repo[newrev].rev()
                else:
                    if not collapsef:
                        ui.note(_('no changes, revision %d skipped\n') % rev)
                        ui.debug('next revision set to %s\n' % p1)
                        skipped.add(rev)
                    state[rev] = p1

        ui.progress(_('rebasing'), None)
        ui.note(_('rebase merging completed\n'))

        if collapsef and not keepopen:
            p1, p2 = defineparents(repo, min(state), target,
                                                        state, targetancestors)
            if collapsemsg:
                commitmsg = collapsemsg
            else:
                commitmsg = 'Collapsed revision'
                for rebased in state:
                    if rebased not in skipped and state[rebased] != nullmerge:
                        commitmsg += '\n* %s' % repo[rebased].description()
                commitmsg = ui.edit(commitmsg, repo.ui.username())
            newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
                                  extrafn=extrafn, editor=editor)

        if 'qtip' in repo.tags():
            updatemq(repo, state, skipped, **opts)

        if currentbookmarks:
            # Nodeids are needed to reset bookmarks
            nstate = {}
            for k, v in state.iteritems():
                if v != nullmerge:
                    nstate[repo[k].node()] = repo[v].node()

        if not keepf:
            # Remove no more useful revisions
            rebased = [rev for rev in state if state[rev] != nullmerge]
            if rebased:
                if set(repo.changelog.descendants(min(rebased))) - set(state):
                    ui.warn(_("warning: new changesets detected "
                              "on source branch, not stripping\n"))
                else:
                    # backup the old csets by default
                    repair.strip(ui, repo, repo[min(rebased)].node(), "all")

        if currentbookmarks:
            updatebookmarks(repo, nstate, currentbookmarks, **opts)

        clearstatus(repo)
        ui.note(_("rebase completed\n"))
        if os.path.exists(repo.sjoin('undo')):
            util.unlinkpath(repo.sjoin('undo'))
        if skipped:
            ui.note(_("%d revisions have been skipped\n") % len(skipped))

        if (activebookmark and
            repo['tip'].node() == repo._bookmarks[activebookmark]):
                bookmarks.setcurrent(repo, activebookmark)

    finally:
        release(lock, wlock)
Exemple #6
0
def rebase(ui, repo, **opts):
    """move changeset (and descendants) to a different branch

    Rebase uses repeated merging to graft changesets from one part of
    history (the source) onto another (the destination). This can be
    useful for linearizing *local* changes relative to a master
    development tree.

    You should not rebase changesets that have already been shared
    with others. Doing so will force everybody else to perform the
    same rebase or they will end up with duplicated changesets after
    pulling in your rebased changesets.

    In its default configuration, Mercurial will prevent you from
    rebasing published changes. See :hg:`help phases` for details.

    If you don't specify a destination changeset (``-d/--dest``),
    rebase uses the current branch tip as the destination. (The
    destination changeset is not modified by rebasing, but new
    changesets are added as its descendants.)

    You can specify which changesets to rebase in two ways: as a
    "source" changeset or as a "base" changeset. Both are shorthand
    for a topologically related set of changesets (the "source
    branch"). If you specify source (``-s/--source``), rebase will
    rebase that changeset and all of its descendants onto dest. If you
    specify base (``-b/--base``), rebase will select ancestors of base
    back to but not including the common ancestor with dest. Thus,
    ``-b`` is less precise but more convenient than ``-s``: you can
    specify any changeset in the source branch, and rebase will select
    the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
    uses the parent of the working directory as the base.

    For advanced usage, a third way is available through the ``--rev``
    option. It allows you to specify an arbitrary set of changesets to
    rebase. Descendants of revs you specify with this option are not
    automatically included in the rebase.

    By default, rebase recreates the changesets in the source branch
    as descendants of dest and then destroys the originals. Use
    ``--keep`` to preserve the original source changesets. Some
    changesets in the source branch (e.g. merges from the destination
    branch) may be dropped if they no longer contribute any change.

    One result of the rules for selecting the destination changeset
    and source branch is that, unlike ``merge``, rebase will do
    nothing if you are at the branch tip of a named branch
    with two heads. You need to explicitly specify source and/or
    destination (or ``update`` to the other head, if it's the head of
    the intended source branch).

    If a rebase is interrupted to manually resolve a merge, it can be
    continued with --continue/-c or aborted with --abort/-a.

    Returns 0 on success, 1 if nothing to rebase or there are
    unresolved conflicts.
    """
    originalwd = target = None
    activebookmark = None
    external = nullrev
    state = {}
    skipped = set()
    targetancestors = set()

    editor = None
    if opts.get('edit'):
        editor = cmdutil.commitforceeditor

    lock = wlock = None
    try:
        wlock = repo.wlock()
        lock = repo.lock()

        # Validate input and define rebasing points
        destf = opts.get('dest', None)
        srcf = opts.get('source', None)
        basef = opts.get('base', None)
        revf = opts.get('rev', [])
        contf = opts.get('continue')
        abortf = opts.get('abort')
        collapsef = opts.get('collapse', False)
        collapsemsg = cmdutil.logmessage(ui, opts)
        e = opts.get('extrafn')  # internal, used by e.g. hgsubversion
        extrafns = [_savegraft]
        if e:
            extrafns = [e]
        keepf = opts.get('keep', False)
        keepbranchesf = opts.get('keepbranches', False)
        # keepopen is not meant for use on the command line, but by
        # other extensions
        keepopen = opts.get('keepopen', False)

        if collapsemsg and not collapsef:
            raise util.Abort(_('message can only be specified with collapse'))

        if contf or abortf:
            if contf and abortf:
                raise util.Abort(_('cannot use both abort and continue'))
            if collapsef:
                raise util.Abort(
                    _('cannot use collapse with continue or abort'))
            if srcf or basef or destf:
                raise util.Abort(
                    _('abort and continue do not allow specifying revisions'))
            if opts.get('tool', False):
                ui.warn(_('tool option will be ignored\n'))

            try:
                (originalwd, target, state, skipped, collapsef, keepf,
                 keepbranchesf, external, activebookmark) = restorestatus(repo)
            except error.RepoLookupError:
                if abortf:
                    clearstatus(repo)
                    repo.ui.warn(
                        _('rebase aborted (no revision is removed,'
                          ' only broken state is cleared)\n'))
                    return 0
                else:
                    msg = _('cannot continue inconsistent rebase')
                    hint = _('use "hg rebase --abort" to clear broken state')
                    raise util.Abort(msg, hint=hint)
            if abortf:
                return abort(repo, originalwd, target, state)
        else:
            if srcf and basef:
                raise util.Abort(
                    _('cannot specify both a '
                      'source and a base'))
            if revf and basef:
                raise util.Abort(
                    _('cannot specify both a '
                      'revision and a base'))
            if revf and srcf:
                raise util.Abort(
                    _('cannot specify both a '
                      'revision and a source'))

            cmdutil.checkunfinished(repo)
            cmdutil.bailifchanged(repo)

            if not destf:
                # Destination defaults to the latest revision in the
                # current branch
                branch = repo[None].branch()
                dest = repo[branch]
            else:
                dest = scmutil.revsingle(repo, destf)

            if revf:
                rebaseset = scmutil.revrange(repo, revf)
                if not rebaseset:
                    ui.status(
                        _('empty "rev" revision set - '
                          'nothing to rebase\n'))
                    return 1
            elif srcf:
                src = scmutil.revrange(repo, [srcf])
                if not src:
                    ui.status(
                        _('empty "source" revision set - '
                          'nothing to rebase\n'))
                    return 1
                rebaseset = repo.revs('(%ld)::', src)
                assert rebaseset
            else:
                base = scmutil.revrange(repo, [basef or '.'])
                if not base:
                    ui.status(
                        _('empty "base" revision set - '
                          "can't compute rebase set\n"))
                    return 1
                rebaseset = repo.revs(
                    '(children(ancestor(%ld, %d)) and ::(%ld))::', base, dest,
                    base)
                if not rebaseset:
                    if base == [dest.rev()]:
                        if basef:
                            ui.status(
                                _('nothing to rebase - %s is both "base"'
                                  ' and destination\n') % dest)
                        else:
                            ui.status(
                                _('nothing to rebase - working directory '
                                  'parent is also destination\n'))
                    elif not repo.revs('%ld - ::%d', base, dest):
                        if basef:
                            ui.status(
                                _('nothing to rebase - "base" %s is '
                                  'already an ancestor of destination '
                                  '%s\n') %
                                ('+'.join(str(repo[r]) for r in base), dest))
                        else:
                            ui.status(
                                _('nothing to rebase - working '
                                  'directory parent is already an '
                                  'ancestor of destination %s\n') % dest)
                    else:  # can it happen?
                        ui.status(
                            _('nothing to rebase from %s to %s\n') %
                            ('+'.join(str(repo[r]) for r in base), dest))
                    return 1

            if (not (keepf or obsolete._enabled) and repo.revs(
                    'first(children(%ld) - %ld)', rebaseset, rebaseset)):
                raise util.Abort(
                    _("can't remove original changesets with"
                      " unrebased descendants"),
                    hint=_('use --keep to keep original changesets'))

            result = buildstate(repo, dest, rebaseset, collapsef)
            if not result:
                # Empty state built, nothing to rebase
                ui.status(_('nothing to rebase\n'))
                return 1

            root = min(rebaseset)
            if not keepf and not repo[root].mutable():
                raise util.Abort(_("can't rebase immutable changeset %s") %
                                 repo[root],
                                 hint=_('see hg help phases for details'))

            originalwd, target, state = result
            if collapsef:
                targetancestors = repo.changelog.ancestors([target],
                                                           inclusive=True)
                external = externalparent(repo, state, targetancestors)

            if dest.closesbranch() and not keepbranchesf:
                ui.status(_('reopening closed branch head %s\n') % dest)

        if keepbranchesf:
            # insert _savebranch at the start of extrafns so if
            # there's a user-provided extrafn it can clobber branch if
            # desired
            extrafns.insert(0, _savebranch)
            if collapsef:
                branches = set()
                for rev in state:
                    branches.add(repo[rev].branch())
                    if len(branches) > 1:
                        raise util.Abort(
                            _('cannot collapse multiple named '
                              'branches'))

        # Rebase
        if not targetancestors:
            targetancestors = repo.changelog.ancestors([target],
                                                       inclusive=True)

        # Keep track of the current bookmarks in order to reset them later
        currentbookmarks = repo._bookmarks.copy()
        activebookmark = activebookmark or repo._bookmarkcurrent
        if activebookmark:
            bookmarks.unsetcurrent(repo)

        extrafn = _makeextrafn(extrafns)

        sortedstate = sorted(state)
        total = len(sortedstate)
        pos = 0
        for rev in sortedstate:
            pos += 1
            if state[rev] == -1:
                ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
                            _('changesets'), total)
                p1, p2 = defineparents(repo, rev, target, state,
                                       targetancestors)
                storestatus(repo, originalwd, target, state, collapsef, keepf,
                            keepbranchesf, external, activebookmark)
                if len(repo.parents()) == 2:
                    repo.ui.debug('resuming interrupted rebase\n')
                else:
                    try:
                        ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                                     'rebase')
                        stats = rebasenode(repo, rev, p1, state, collapsef)
                        if stats and stats[3] > 0:
                            raise error.InterventionRequired(
                                _('unresolved conflicts (see hg '
                                  'resolve, then hg rebase --continue)'))
                    finally:
                        ui.setconfig('ui', 'forcemerge', '', 'rebase')
                cmdutil.duplicatecopies(repo, rev, target)
                if not collapsef:
                    newrev = concludenode(repo,
                                          rev,
                                          p1,
                                          p2,
                                          extrafn=extrafn,
                                          editor=editor)
                else:
                    # Skip commit if we are collapsing
                    repo.setparents(repo[p1].node())
                    newrev = None
                # Update the state
                if newrev is not None:
                    state[rev] = repo[newrev].rev()
                else:
                    if not collapsef:
                        ui.note(_('no changes, revision %d skipped\n') % rev)
                        ui.debug('next revision set to %s\n' % p1)
                        skipped.add(rev)
                    state[rev] = p1

        ui.progress(_('rebasing'), None)
        ui.note(_('rebase merging completed\n'))

        if collapsef and not keepopen:
            p1, p2 = defineparents(repo, min(state), target, state,
                                   targetancestors)
            if collapsemsg:
                commitmsg = collapsemsg
            else:
                commitmsg = 'Collapsed revision'
                for rebased in state:
                    if rebased not in skipped and state[rebased] > nullmerge:
                        commitmsg += '\n* %s' % repo[rebased].description()
                editor = cmdutil.commitforceeditor
            newrev = concludenode(repo,
                                  rev,
                                  p1,
                                  external,
                                  commitmsg=commitmsg,
                                  extrafn=extrafn,
                                  editor=editor)
            for oldrev in state.iterkeys():
                if state[oldrev] > nullmerge:
                    state[oldrev] = newrev

        if 'qtip' in repo.tags():
            updatemq(repo, state, skipped, **opts)

        if currentbookmarks:
            # Nodeids are needed to reset bookmarks
            nstate = {}
            for k, v in state.iteritems():
                if v > nullmerge:
                    nstate[repo[k].node()] = repo[v].node()
            # XXX this is the same as dest.node() for the non-continue path --
            # this should probably be cleaned up
            targetnode = repo[target].node()

        # restore original working directory
        # (we do this before stripping)
        newwd = state.get(originalwd, originalwd)
        if newwd not in [c.rev() for c in repo[None].parents()]:
            ui.note(_("update back to initial working directory parent\n"))
            hg.updaterepo(repo, newwd, False)

        if not keepf:
            collapsedas = None
            if collapsef:
                collapsedas = newrev
            clearrebased(ui, repo, state, skipped, collapsedas)

        if currentbookmarks:
            updatebookmarks(repo, targetnode, nstate, currentbookmarks)
            if activebookmark not in repo._bookmarks:
                # active bookmark was divergent one and has been deleted
                activebookmark = None

        clearstatus(repo)
        ui.note(_("rebase completed\n"))
        util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
        if skipped:
            ui.note(_("%d revisions have been skipped\n") % len(skipped))

        if (activebookmark
                and repo['.'].node() == repo._bookmarks[activebookmark]):
            bookmarks.setcurrent(repo, activebookmark)

    finally:
        release(lock, wlock)