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.'))
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.'))
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'))
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
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, [])
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, []
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, []
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,))]
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
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)
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)
'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.
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)
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)