예제 #1
0
def edit(ui, repo, ctx, ha, opts):
    oldctx = repo[ha]
    hg.update(repo, ctx.node())
    applychanges(ui, repo, oldctx, opts)
    raise error.InterventionRequired(
        _('Make changes as needed, you may commit or record as needed now.\n'
          'When you are finished, run hg histedit --continue to resume.'))
예제 #2
0
 def run(self):
     repo = self.repo
     rulectx = repo[self.node]
     hg.update(repo, self.state.parentctxnode)
     applychanges(repo.ui, repo, rulectx, {})
     raise error.InterventionRequired(
         _('Make changes as needed, you may commit or record as needed '
           'now.\nWhen you are finished, run hg histedit --continue to '
           'resume.'))
예제 #3
0
 def applychange(self):
     """Applies the changes from this action's rulectx onto the current
     parentctx, but does not commit them."""
     repo = self.repo
     rulectx = repo[self.node]
     hg.update(repo, self.state.parentctxnode)
     stats = applychanges(repo.ui, repo, rulectx, {})
     if stats and stats[3] > 0:
         raise error.InterventionRequired(_('Fix up the change and run '
                                         'hg histedit --continue'))
예제 #4
0
파일: shelve.py 프로젝트: pombredanne/hg
def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
                          tmpwctx, shelvectx, branchtorestore, activebookmark):
    """Rebase restored commit from its original location to a destination"""
    # If the shelve is not immediately on top of the commit
    # we'll be merging with, rebase it to be on top.
    if tmpwctx.node() == shelvectx.parents()[0].node():
        return shelvectx

    overrides = {
        ('ui', 'forcemerge'): opts.get('tool', ''),
        ('phases', 'new-commit'): phases.secret,
    }
    with repo.ui.configoverride(overrides, 'unshelve'):
        ui.status(_('rebasing shelved changes\n'))
        stats = merge.graft(repo,
                            shelvectx,
                            shelvectx.p1(),
                            labels=['shelve', 'working-copy'],
                            keepconflictparent=True)
        if stats.unresolvedcount:
            tr.close()

            nodestoremove = [
                repo.changelog.node(rev)
                for rev in pycompat.xrange(oldtiprev, len(repo))
            ]
            shelvedstate.save(repo, basename, pctx,
                              tmpwctx, nodestoremove, branchtorestore,
                              opts.get('keep'), activebookmark)
            raise error.InterventionRequired(
                _("unresolved conflicts (see 'hg resolve', then "
                  "'hg unshelve --continue')"))

        with repo.dirstate.parentchange():
            repo.setparents(tmpwctx.node(), nodemod.nullid)
            newnode = repo.commit(text=shelvectx.description(),
                                  extra=shelvectx.extra(),
                                  user=shelvectx.user(),
                                  date=shelvectx.date())

        if newnode is None:
            # If it ended up being a no-op commit, then the normal
            # merge state clean-up path doesn't happen, so do it
            # here. Fix issue5494
            merge.mergestate.clean(repo)
            shelvectx = tmpwctx
            msg = _('note: unshelved changes already existed '
                    'in the working copy\n')
            ui.status(msg)
        else:
            shelvectx = repo[newnode]
            hg.updaterepo(repo, tmpwctx.node(), False)

    return shelvectx
예제 #5
0
def fold(ui, state, ha, opts):
    repo, ctx = state.repo, state.parentctx
    oldctx = repo[ha]
    hg.update(repo, ctx.node())
    stats = applychanges(ui, repo, oldctx, opts)
    if stats and stats[3] > 0:
        raise error.InterventionRequired(
            _('Fix up the change and run hg histedit --continue'))
    n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
                    date=oldctx.date(), extra=oldctx.extra())
    if n is None:
        ui.warn(_('%s: empty changeset') % node.hex(ha))
        return ctx, []
    return finishfold(ui, repo, ctx, oldctx, n, opts, [])
예제 #6
0
def message(ui, state, ha, opts):
    repo, ctx = state.repo, state.parentctx
    oldctx = repo[ha]
    hg.update(repo, ctx.node())
    stats = applychanges(ui, repo, oldctx, opts)
    if stats and stats[3] > 0:
        raise error.InterventionRequired(
            _('Fix up the change and run hg histedit --continue'))
    message = oldctx.description()
    commit = commitfuncfor(repo, oldctx)
    editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
    new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
                 extra=oldctx.extra(), editor=editor)
    newctx = repo[new]
    if oldctx.node() != newctx.node():
        return newctx, [(oldctx.node(), (new,))]
    # We didn't make an edit, so just indicate no replaced nodes
    return newctx, []
예제 #7
0
def message(ui, repo, ctx, ha, opts):
    oldctx = repo[ha]
    hg.update(repo, ctx.node())
    stats = applychanges(ui, repo, oldctx, opts)
    if stats and stats[3] > 0:
        raise error.InterventionRequired(
            _('Fix up the change and run hg histedit --continue'))
    message = oldctx.description() + '\n'
    message = ui.edit(message, ui.username())
    commit = commitfuncfor(repo, oldctx)
    new = commit(text=message,
                 user=oldctx.user(),
                 date=oldctx.date(),
                 extra=oldctx.extra())
    newctx = repo[new]
    if oldctx.node() != newctx.node():
        return newctx, [(oldctx.node(), (new, ))]
    # We didn't make an edit, so just indicate no replaced nodes
    return newctx, []
예제 #8
0
def pick(ui, state, ha, opts):
    repo, ctx = state.repo, state.parentctx
    oldctx = repo[ha]
    if oldctx.parents()[0] == ctx:
        ui.debug('node %s unchanged\n' % ha)
        return oldctx, []
    hg.update(repo, ctx.node())
    stats = applychanges(ui, repo, oldctx, opts)
    if stats and stats[3] > 0:
        raise error.InterventionRequired(_('Fix up the change and run '
                                           'hg histedit --continue'))
    # drop the second merge parent
    commit = commitfuncfor(repo, oldctx)
    n = commit(text=oldctx.description(), user=oldctx.user(),
               date=oldctx.date(), extra=oldctx.extra())
    if n is None:
        ui.warn(_('%s: empty changeset\n') % node.hex(ha))
        return ctx, []
    new = repo[n]
    return new, [(oldctx.node(), (n,))]
예제 #9
0
def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
                          tmpwctx, shelvectx, branchtorestore, activebookmark):
    """Rebase restored commit from its original location to a destination"""
    # If the shelve is not immediately on top of the commit
    # we'll be merging with, rebase it to be on top.
    if tmpwctx.node() == shelvectx.parents()[0].node():
        return shelvectx

    ui.status(_('rebasing shelved changes\n'))
    try:
        rebase.rebase(
            ui, repo, **{
                'rev': [shelvectx.rev()],
                'dest': str(tmpwctx.rev()),
                'keep': True,
                'tool': opts.get('tool', ''),
            })
    except error.InterventionRequired:
        tr.close()

        nodestoremove = [
            repo.changelog.node(rev) for rev in xrange(oldtiprev, len(repo))
        ]
        shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
                          branchtorestore, opts.get('keep'), activebookmark)

        repo.vfs.rename('rebasestate', 'unshelverebasestate')
        raise error.InterventionRequired(
            _("unresolved conflicts (see 'hg resolve', then "
              "'hg unshelve --continue')"))

    # refresh ctx after rebase completes
    shelvectx = repo['tip']

    if tmpwctx not in shelvectx.parents():
        # rebase was a no-op, so it produced no child commit
        shelvectx = tmpwctx
    return shelvectx
예제 #10
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.

    .. container:: verbose

      Examples:

      - move "local changes" (current commit back to branching point)
        to the current branch tip after a pull::

          hg rebase

      - move a single changeset to the stable branch::

          hg rebase -r 5f493448 -d stable

      - splice a commit and all its descendants onto another part of history::

          hg rebase --source c0c3 --dest 4cf9

      - rebase everything on a branch marked by a bookmark onto the
        default branch::

          hg rebase --base myfeature --dest default

      - collapse a sequence of changes into a single commit::

          hg rebase --collapse -r 1520:1525 -d .

      - move a named branch while preserving its name::

          hg rebase -r "branch(featureX)" -d 1.3 --keepbranches

    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()


    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 opts.get('interactive'):
            msg = _("interactive history editing is supported by the "
                    "'histedit' extension (see 'hg help histedit')")
            raise util.Abort(msg)

        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
                commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
                if commonanc is not None:
                    rebaseset = repo.revs('(%d::(%ld) - %d)::',
                                          commonanc, base, commonanc)
                else:
                    rebaseset = []

                if not rebaseset:
                    # transform to list because smartsets are not comparable to
                    # lists. This should be improved to honor lazyness of
                    # smartset.
                    if list(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

            allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
            if (not (keepf or allowunstable)
                  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,
                                           target)
                        if stats and stats[3] > 0:
                            raise error.InterventionRequired(
                                _('unresolved conflicts (see hg '
                                  'resolve, then hg rebase --continue)'))
                    finally:
                        ui.setconfig('ui', 'forcemerge', '', 'rebase')
                if not collapsef:
                    merging = repo[p2].rev() != nullrev
                    editform = cmdutil.mergeeditform(merging, 'rebase')
                    editor = cmdutil.getcommiteditor(editform=editform, **opts)
                    newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
                                          editor=editor)
                else:
                    # Skip commit if we are collapsing
                    repo.dirstate.beginparentchange()
                    repo.setparents(repo[p1].node())
                    repo.dirstate.endparentchange()
                    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)
            editopt = opts.get('edit')
            editform = 'rebase.collapse'
            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()
                editopt = True
            editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
            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 < 0:
            # original directory is a parent of rebase set root or ignored
            newwd = 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)
예제 #11
0
def _dounshelve(ui, repo, *shelved, **opts):
    abortf = opts.get('abort')
    continuef = opts.get('continue')
    if not abortf and not continuef:
        cmdutil.checkunfinished(repo)

    if abortf or continuef:
        if abortf and continuef:
            raise error.Abort(_('cannot use both abort and continue'))
        if shelved:
            raise error.Abort(
                _('cannot combine abort/continue with '
                  'naming a shelved change'))
        if abortf and opts.get('tool', False):
            ui.warn(_('tool option will be ignored\n'))

        try:
            state = shelvedstate.load(repo)
        except IOError as err:
            if err.errno != errno.ENOENT:
                raise
            cmdutil.wrongtooltocontinue(repo, _('unshelve'))
        except error.CorruptedState as err:
            ui.debug(str(err) + '\n')
            if continuef:
                msg = _('corrupted shelved state file')
                hint = _('please run hg unshelve --abort to abort unshelve '
                         'operation')
                raise error.Abort(msg, hint=hint)
            elif abortf:
                msg = _('could not read shelved state file, your working copy '
                        'may be in an unexpected state\nplease update to some '
                        'commit\n')
                ui.warn(msg)
                shelvedstate.clear(repo)
            return

        if abortf:
            return unshelveabort(ui, repo, state, opts)
        elif continuef:
            return unshelvecontinue(ui, repo, state, opts)
    elif len(shelved) > 1:
        raise error.Abort(_('can only unshelve one change at a time'))
    elif not shelved:
        shelved = listshelves(repo)
        if not shelved:
            raise error.Abort(_('no shelved changes to apply!'))
        basename = util.split(shelved[0][1])[1]
        ui.status(_("unshelving change '%s'\n") % basename)
    else:
        basename = shelved[0]

    if not shelvedfile(repo, basename, 'patch').exists():
        raise error.Abort(_("shelved change '%s' not found") % basename)

    oldquiet = ui.quiet
    lock = tr = None
    forcemerge = ui.backupconfig('ui', 'forcemerge')
    try:
        ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
        lock = repo.lock()

        tr = repo.transaction('unshelve', report=lambda x: None)
        oldtiprev = len(repo)

        pctx = repo['.']
        tmpwctx = pctx
        # The goal is to have a commit structure like so:
        # ...-> pctx -> tmpwctx -> shelvectx
        # where tmpwctx is an optional commit with the user's pending changes
        # and shelvectx is the unshelved changes. Then we merge it all down
        # to the original pctx.

        # Store pending changes in a commit and remember added in case a shelve
        # contains unknown files that are part of the pending change
        s = repo.status()
        addedbefore = frozenset(s.added)
        if s.modified or s.added or s.removed or s.deleted:
            ui.status(
                _("temporarily committing pending changes "
                  "(restore with 'hg unshelve --abort')\n"))

            def commitfunc(ui, repo, message, match, opts):
                hasmq = util.safehasattr(repo, 'mq')
                if hasmq:
                    saved, repo.mq.checkapplied = repo.mq.checkapplied, False

                backup = repo.ui.backupconfig('phases', 'new-commit')
                try:
                    repo.ui.setconfig('phases', 'new-commit', phases.secret)
                    return repo.commit(message, 'shelve@localhost',
                                       opts.get('date'), match)
                finally:
                    repo.ui.restoreconfig(backup)
                    if hasmq:
                        repo.mq.checkapplied = saved

            tempopts = {}
            tempopts['message'] = "pending changes temporary commit"
            tempopts['date'] = opts.get('date')
            ui.quiet = True
            node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
            tmpwctx = repo[node]

        ui.quiet = True
        shelvedfile(repo, basename, 'hg').applybundle()

        ui.quiet = oldquiet

        shelvectx = repo['tip']

        branchtorestore = ''
        if shelvectx.branch() != shelvectx.p1().branch():
            branchtorestore = shelvectx.branch()

        # If the shelve is not immediately on top of the commit
        # we'll be merging with, rebase it to be on top.
        if tmpwctx.node() != shelvectx.parents()[0].node():
            ui.status(_('rebasing shelved changes\n'))
            try:
                rebase.rebase(
                    ui, repo, **{
                        'rev': [shelvectx.rev()],
                        'dest': str(tmpwctx.rev()),
                        'keep': True,
                        'tool': opts.get('tool', ''),
                    })
            except error.InterventionRequired:
                tr.close()

                stripnodes = [
                    repo.changelog.node(rev)
                    for rev in xrange(oldtiprev, len(repo))
                ]
                shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
                                  branchtorestore)

                util.rename(repo.join('rebasestate'),
                            repo.join('unshelverebasestate'))
                raise error.InterventionRequired(
                    _("unresolved conflicts (see 'hg resolve', then "
                      "'hg unshelve --continue')"))

            # refresh ctx after rebase completes
            shelvectx = repo['tip']

            if not shelvectx in tmpwctx.children():
                # rebase was a no-op, so it produced no child commit
                shelvectx = tmpwctx

        mergefiles(ui, repo, pctx, shelvectx)
        restorebranch(ui, repo, branchtorestore)

        # Forget any files that were unknown before the shelve, unknown before
        # unshelve started, but are now added.
        shelveunknown = shelvectx.extra().get('shelve_unknown')
        if shelveunknown:
            shelveunknown = frozenset(shelveunknown.split('\0'))
            addedafter = frozenset(repo.status().added)
            toforget = (addedafter & shelveunknown) - addedbefore
            repo[None].forget(toforget)

        shelvedstate.clear(repo)

        # The transaction aborting will strip all the commits for us,
        # but it doesn't update the inmemory structures, so addchangegroup
        # hooks still fire and try to operate on the missing commits.
        # Clean up manually to prevent this.
        repo.unfiltered().changelog.strip(oldtiprev, tr)

        unshelvecleanup(ui, repo, basename, opts)

        _aborttransaction(repo)
    finally:
        ui.quiet = oldquiet
        if tr:
            tr.release()
        lockmod.release(lock)
        ui.restoreconfig(forcemerge)
예제 #12
0
                        'dest': str(tmpwctx.rev()),
                        'keep': True,
                    })
            except error.InterventionRequired:
                tr.close()

                stripnodes = [
                    repo.changelog.node(rev)
                    for rev in xrange(oldtiprev, len(repo))
                ]
                shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)

                util.rename(repo.join('rebasestate'),
                            repo.join('unshelverebasestate'))
                raise error.InterventionRequired(
                    _("unresolved conflicts (see 'hg resolve', then "
                      "'hg unshelve --continue')"))

            # refresh ctx after rebase completes
            shelvectx = repo['tip']

            if not shelvectx in tmpwctx.children():
                # rebase was a no-op, so it produced no child commit
                shelvectx = tmpwctx

        mergefiles(ui, repo, pctx, shelvectx)
        shelvedstate.clear(repo)

        # The transaction aborting will strip all the commits for us,
        # but it doesn't update the inmemory structures, so addchangegroup
        # hooks still fire and try to operate on the missing commits.
예제 #13
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 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.

    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 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
    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)
        extrafn = opts.get('extrafn')  # internal, used by e.g. hgsubversion
        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'))

            (originalwd, target, state, skipped, collapsef, keepf,
             keepbranchesf, external, activebookmark) = 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'))

            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 = 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\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 = 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 = 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)

        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, activebookmark)
                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, 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)

        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()

        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['tip'].node() == repo._bookmarks[activebookmark]):
            bookmarks.setcurrent(repo, activebookmark)

    finally:
        release(lock, wlock)
예제 #14
0
def unshelve(ui, repo, *shelved, **opts):
    """restore a shelved change to the working directory

    This command accepts an optional name of a shelved change to
    restore. If none is given, the most recent shelved change is used.

    If a shelved change is applied successfully, the bundle that
    contains the shelved changes is moved to a backup location
    (.hg/shelve-backup).

    Since you can restore a shelved change on top of an arbitrary
    commit, it is possible that unshelving will result in a conflict
    between your changes and the commits you are unshelving onto. If
    this occurs, you must resolve the conflict, then use
    ``--continue`` to complete the unshelve operation. (The bundle
    will not be moved until you successfully complete the unshelve.)

    (Alternatively, you can use ``--abort`` to abandon an unshelve
    that causes a conflict. This reverts the unshelved changes, and
    leaves the bundle in place.)

    After a successful unshelve, the shelved changes are stored in a
    backup directory. Only the N most recent backups are kept. N
    defaults to 10 but can be overridden using the ``shelve.maxbackups``
    configuration option.

    .. container:: verbose

       Timestamp in seconds is used to decide order of backups. More
       than ``maxbackups`` backups are kept, if same timestamp
       prevents from deciding exact order of them, for safety.
    """
    abortf = opts['abort']
    continuef = opts['continue']
    if not abortf and not continuef:
        cmdutil.checkunfinished(repo)

    if abortf or continuef:
        if abortf and continuef:
            raise error.Abort(_('cannot use both abort and continue'))
        if shelved:
            raise error.Abort(
                _('cannot combine abort/continue with '
                  'naming a shelved change'))

        try:
            state = shelvedstate.load(repo)
        except IOError as err:
            if err.errno != errno.ENOENT:
                raise
            raise error.Abort(_('no unshelve operation underway'))

        if abortf:
            return unshelveabort(ui, repo, state, opts)
        elif continuef:
            return unshelvecontinue(ui, repo, state, opts)
    elif len(shelved) > 1:
        raise error.Abort(_('can only unshelve one change at a time'))
    elif not shelved:
        shelved = listshelves(repo)
        if not shelved:
            raise error.Abort(_('no shelved changes to apply!'))
        basename = util.split(shelved[0][1])[1]
        ui.status(_("unshelving change '%s'\n") % basename)
    else:
        basename = shelved[0]

    if not shelvedfile(repo, basename, 'patch').exists():
        raise error.Abort(_("shelved change '%s' not found") % basename)

    oldquiet = ui.quiet
    wlock = lock = tr = None
    try:
        wlock = repo.wlock()
        lock = repo.lock()

        tr = repo.transaction('unshelve', report=lambda x: None)
        oldtiprev = len(repo)

        pctx = repo['.']
        tmpwctx = pctx
        # The goal is to have a commit structure like so:
        # ...-> pctx -> tmpwctx -> shelvectx
        # where tmpwctx is an optional commit with the user's pending changes
        # and shelvectx is the unshelved changes. Then we merge it all down
        # to the original pctx.

        # Store pending changes in a commit
        s = repo.status()
        if s.modified or s.added or s.removed or s.deleted:
            ui.status(
                _("temporarily committing pending changes "
                  "(restore with 'hg unshelve --abort')\n"))

            def commitfunc(ui, repo, message, match, opts):
                hasmq = util.safehasattr(repo, 'mq')
                if hasmq:
                    saved, repo.mq.checkapplied = repo.mq.checkapplied, False

                backup = repo.ui.backupconfig('phases', 'new-commit')
                try:
                    repo.ui.setconfig('phases', 'new-commit', phases.secret)
                    return repo.commit(message, 'shelve@localhost',
                                       opts.get('date'), match)
                finally:
                    repo.ui.restoreconfig(backup)
                    if hasmq:
                        repo.mq.checkapplied = saved

            tempopts = {}
            tempopts['message'] = "pending changes temporary commit"
            tempopts['date'] = opts.get('date')
            ui.quiet = True
            node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
            tmpwctx = repo[node]

        ui.quiet = True
        shelvedfile(repo, basename, 'hg').applybundle()

        ui.quiet = oldquiet

        shelvectx = repo['tip']

        # If the shelve is not immediately on top of the commit
        # we'll be merging with, rebase it to be on top.
        if tmpwctx.node() != shelvectx.parents()[0].node():
            ui.status(_('rebasing shelved changes\n'))
            try:
                rebase.rebase(
                    ui, repo, **{
                        'rev': [shelvectx.rev()],
                        'dest': str(tmpwctx.rev()),
                        'keep': True,
                    })
            except error.InterventionRequired:
                tr.close()

                stripnodes = [
                    repo.changelog.node(rev)
                    for rev in xrange(oldtiprev, len(repo))
                ]
                shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)

                util.rename(repo.join('rebasestate'),
                            repo.join('unshelverebasestate'))
                raise error.InterventionRequired(
                    _("unresolved conflicts (see 'hg resolve', then "
                      "'hg unshelve --continue')"))

            # refresh ctx after rebase completes
            shelvectx = repo['tip']

            if not shelvectx in tmpwctx.children():
                # rebase was a no-op, so it produced no child commit
                shelvectx = tmpwctx

        mergefiles(ui, repo, pctx, shelvectx)
        shelvedstate.clear(repo)

        # The transaction aborting will strip all the commits for us,
        # but it doesn't update the inmemory structures, so addchangegroup
        # hooks still fire and try to operate on the missing commits.
        # Clean up manually to prevent this.
        repo.unfiltered().changelog.strip(oldtiprev, tr)

        unshelvecleanup(ui, repo, basename, opts)

        _aborttransaction(repo)
    finally:
        ui.quiet = oldquiet
        if tr:
            tr.release()
        lockmod.release(lock, wlock)