diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -120,6 +120,53 @@ sourceset = revset.getset(repo, smartset.fullreposet(repo), x) return subset & smartset.baseset([_destrebase(repo, sourceset)]) +def _possibledestination(repo, rev): + """Return all changesets that may be a new parent for `rev`.""" + tonode = repo.changelog.node + parents = repo.changelog.parentrevs + torev = repo.changelog.rev + dest = set() + tovisit = list(parents(rev)) + while tovisit: + r = tovisit.pop() + succsets = obsutil.successorssets(repo, tonode(r)) + if not succsets: + # if there are no successors for r, r was probably pruned + # and we should walk up to r's parents to try and find + # some successors. + tovisit.extend(parents(r)) + else: + # We should probably pick only one destination from split + # (case where '1 < len(ss)'), This could be the currently + # tipmost, but the correct result is less clear when + # results of the split have been moved such that they + # reside on multiple branches. + for ss in succsets: + for n in ss: + dr = torev(n) + if dr != -1: + dest.add(dr) + return dest + +@revsetpredicate('_destautorebase') +def _revsetdestautorebase(repo, subset, x): + """automatic rebase destination for a single revision""" + unfi = repo.unfiltered() + obsoleted = unfi.revs('obsolete()') + + src = revset.getset(repo, subset, x).first() + + # Empty src or already obsoleted - Do not return a destination + if not src or src in obsoleted: + return smartset.baseset() + dests = _possibledestination(repo, src) + if len(dests) > 1: + raise error.Abort( + _("ambiguous automatic rebase: %r could end up on any of %r") % ( + src, dests)) + # We have zero or one destination, so we can just return here. + return smartset.baseset(dests) + def _ctxdesc(ctx): """short description for a context""" desc = '%d:%s "%s"' % (ctx.rev(), ctx, @@ -651,7 +698,10 @@ ('i', 'interactive', False, _('(DEPRECATED)')), ('t', 'tool', '', _('specify merge tool')), ('c', 'continue', False, _('continue an interrupted rebase')), - ('a', 'abort', False, _('abort an interrupted rebase'))] + + ('a', 'abort', False, _('abort an interrupted rebase')), + ('', 'auto', '', _('automatically rebase orphan revisions ' + 'in the specified revset (EXPERIMENTAL)')), + ] + cmdutil.formatteropts, _('[-s REV | -b REV] [-d REV] [OPTION]')) def rebase(ui, repo, **opts): @@ -783,6 +833,15 @@ # fail the entire transaction.) inmemory = False + if opts.get('auto'): + for disallowed in 'rev', 'source', 'base', 'dest': + if opts.get(disallowed): + raise error.Abort(_('--auto is incompatible with %s') % + ('--' + disallowed)) + userrevs = list(repo.revs(opts.get('auto'))) + opts['rev'] = [revsetlang.formatspec('%ld', userrevs)] + opts['dest'] = '_destautorebase(SRC)' + if inmemory: try: # in-memory merge doesn't support conflicts, so if we hit any, abort diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t --- a/tests/test-rebase-obsolete.t +++ b/tests/test-rebase-obsolete.t @@ -482,7 +482,31 @@ |/ o 0:cd010b8cd998 A + $ cd .. + $ cp -r hidden stabilize + $ cd stabilize + $ hg rebase --auto 'orphan()' + rebasing 9:cf44d2f5a9f4 "D" + $ hg log -G + o 12:7e3935feaa68 D + | + o 11:0d8f238b634c C + | + o 10:7c6027df6a99 B + | + @ 7:02de42196ebe H + | + | o 6:eea13746799a G + |/| + o | 5:24b6387c8c8c F + | | + | o 4:9520eea781bc E + |/ + o 0:cd010b8cd998 A + + $ cd ../hidden + $ rm -r ../stabilize Test multiple root handling ------------------------------------