hgext/rebase.py
changeset 34004 af609bb3487f
parent 34003 ba9d5d48bf95
child 34005 5e83a8fe6bc4
equal deleted inserted replaced
34003:ba9d5d48bf95 34004:af609bb3487f
    19 import errno
    19 import errno
    20 import os
    20 import os
    21 
    21 
    22 from mercurial.i18n import _
    22 from mercurial.i18n import _
    23 from mercurial.node import (
    23 from mercurial.node import (
    24     hex,
       
    25     nullid,
    24     nullid,
    26     nullrev,
    25     nullrev,
    27     short,
    26     short,
    28 )
    27 )
    29 from mercurial import (
    28 from mercurial import (
    58 # The following constants are used throughout the rebase module. The ordering of
    57 # The following constants are used throughout the rebase module. The ordering of
    59 # their values must be maintained.
    58 # their values must be maintained.
    60 
    59 
    61 # Indicates that a revision needs to be rebased
    60 # Indicates that a revision needs to be rebased
    62 revtodo = -1
    61 revtodo = -1
       
    62 revtodostr = '-1'
    63 
    63 
    64 # legacy revstates no longer needed in current code
    64 # legacy revstates no longer needed in current code
    65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
    65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
    66 legacystates = {'-2', '-3', '-4', '-5'}
    66 legacystates = {'-2', '-3', '-4', '-5'}
    67 
    67 
   144         # Mapping between the old revision id and either what is the new rebased
   144         # Mapping between the old revision id and either what is the new rebased
   145         # revision or what needs to be done with the old revision. The state
   145         # revision or what needs to be done with the old revision. The state
   146         # dict will be what contains most of the rebase progress state.
   146         # dict will be what contains most of the rebase progress state.
   147         self.state = {}
   147         self.state = {}
   148         self.activebookmark = None
   148         self.activebookmark = None
   149         self.dest = None
   149         self.destmap = {}
   150         self.skipped = set()
   150         self.skipped = set()
   151 
   151 
   152         self.collapsef = opts.get('collapse', False)
   152         self.collapsef = opts.get('collapse', False)
   153         self.collapsemsg = cmdutil.logmessage(ui, opts)
   153         self.collapsemsg = cmdutil.logmessage(ui, opts)
   154         self.date = opts.get('date', None)
   154         self.date = opts.get('date', None)
   175                 self._writestatus(f)
   175                 self._writestatus(f)
   176 
   176 
   177     def _writestatus(self, f):
   177     def _writestatus(self, f):
   178         repo = self.repo.unfiltered()
   178         repo = self.repo.unfiltered()
   179         f.write(repo[self.originalwd].hex() + '\n')
   179         f.write(repo[self.originalwd].hex() + '\n')
   180         f.write(repo[self.dest].hex() + '\n')
   180         # was "dest". we now write dest per src root below.
       
   181         f.write('\n')
   181         f.write(repo[self.external].hex() + '\n')
   182         f.write(repo[self.external].hex() + '\n')
   182         f.write('%d\n' % int(self.collapsef))
   183         f.write('%d\n' % int(self.collapsef))
   183         f.write('%d\n' % int(self.keepf))
   184         f.write('%d\n' % int(self.keepf))
   184         f.write('%d\n' % int(self.keepbranchesf))
   185         f.write('%d\n' % int(self.keepbranchesf))
   185         f.write('%s\n' % (self.activebookmark or ''))
   186         f.write('%s\n' % (self.activebookmark or ''))
       
   187         destmap = self.destmap
   186         for d, v in self.state.iteritems():
   188         for d, v in self.state.iteritems():
   187             oldrev = repo[d].hex()
   189             oldrev = repo[d].hex()
   188             if v >= 0:
   190             if v >= 0:
   189                 newrev = repo[v].hex()
   191                 newrev = repo[v].hex()
   190             elif v == revtodo:
       
   191                 # To maintain format compatibility, we have to use nullid.
       
   192                 # Please do remove this special case when upgrading the format.
       
   193                 newrev = hex(nullid)
       
   194             else:
   192             else:
   195                 newrev = v
   193                 newrev = v
   196             f.write("%s:%s\n" % (oldrev, newrev))
   194             destnode = repo[destmap[d]].hex()
       
   195             f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
   197         repo.ui.debug('rebase status stored\n')
   196         repo.ui.debug('rebase status stored\n')
   198 
   197 
   199     def restorestatus(self):
   198     def restorestatus(self):
   200         """Restore a previously stored status"""
   199         """Restore a previously stored status"""
   201         repo = self.repo
   200         repo = self.repo
   202         keepbranches = None
   201         keepbranches = None
   203         dest = None
   202         legacydest = None
   204         collapse = False
   203         collapse = False
   205         external = nullrev
   204         external = nullrev
   206         activebookmark = None
   205         activebookmark = None
   207         state = {}
   206         state = {}
       
   207         destmap = {}
   208 
   208 
   209         try:
   209         try:
   210             f = repo.vfs("rebasestate")
   210             f = repo.vfs("rebasestate")
   211             for i, l in enumerate(f.read().splitlines()):
   211             for i, l in enumerate(f.read().splitlines()):
   212                 if i == 0:
   212                 if i == 0:
   213                     originalwd = repo[l].rev()
   213                     originalwd = repo[l].rev()
   214                 elif i == 1:
   214                 elif i == 1:
   215                     dest = repo[l].rev()
   215                     # this line should be empty in newer version. but legacy
       
   216                     # clients may still use it
       
   217                     if l:
       
   218                         legacydest = repo[l].rev()
   216                 elif i == 2:
   219                 elif i == 2:
   217                     external = repo[l].rev()
   220                     external = repo[l].rev()
   218                 elif i == 3:
   221                 elif i == 3:
   219                     collapse = bool(int(l))
   222                     collapse = bool(int(l))
   220                 elif i == 4:
   223                 elif i == 4:
   225                     # line 6 is a recent addition, so for backwards
   228                     # line 6 is a recent addition, so for backwards
   226                     # compatibility check that the line doesn't look like the
   229                     # compatibility check that the line doesn't look like the
   227                     # oldrev:newrev lines
   230                     # oldrev:newrev lines
   228                     activebookmark = l
   231                     activebookmark = l
   229                 else:
   232                 else:
   230                     oldrev, newrev = l.split(':')
   233                     args = l.split(':')
       
   234                     oldrev = args[0]
       
   235                     newrev = args[1]
   231                     if newrev in legacystates:
   236                     if newrev in legacystates:
   232                         continue
   237                         continue
   233                     elif newrev == nullid:
   238                     if len(args) > 2:
       
   239                         destnode = args[2]
       
   240                     else:
       
   241                         destnode = legacydest
       
   242                     destmap[repo[oldrev].rev()] = repo[destnode].rev()
       
   243                     if newrev in (nullid, revtodostr):
   234                         state[repo[oldrev].rev()] = revtodo
   244                         state[repo[oldrev].rev()] = revtodo
   235                         # Legacy compat special case
   245                         # Legacy compat special case
   236                     else:
   246                     else:
   237                         state[repo[oldrev].rev()] = repo[newrev].rev()
   247                         state[repo[oldrev].rev()] = repo[newrev].rev()
   238 
   248 
   245             raise error.Abort(_('.hg/rebasestate is incomplete'))
   255             raise error.Abort(_('.hg/rebasestate is incomplete'))
   246 
   256 
   247         skipped = set()
   257         skipped = set()
   248         # recompute the set of skipped revs
   258         # recompute the set of skipped revs
   249         if not collapse:
   259         if not collapse:
   250             seen = {dest}
   260             seen = set(destmap.values())
   251             for old, new in sorted(state.items()):
   261             for old, new in sorted(state.items()):
   252                 if new != revtodo and new in seen:
   262                 if new != revtodo and new in seen:
   253                     skipped.add(old)
   263                     skipped.add(old)
   254                 seen.add(new)
   264                 seen.add(new)
   255         repo.ui.debug('computed skipped revs: %s\n' %
   265         repo.ui.debug('computed skipped revs: %s\n' %
   256                         (' '.join(str(r) for r in sorted(skipped)) or None))
   266                         (' '.join(str(r) for r in sorted(skipped)) or None))
   257         repo.ui.debug('rebase status resumed\n')
   267         repo.ui.debug('rebase status resumed\n')
   258         _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
   268         _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
   259 
   269 
   260         self.originalwd = originalwd
   270         self.originalwd = originalwd
   261         self.dest = dest
   271         self.destmap = destmap
   262         self.state = state
   272         self.state = state
   263         self.skipped = skipped
   273         self.skipped = skipped
   264         self.collapsef = collapse
   274         self.collapsef = collapse
   265         self.keepf = keep
   275         self.keepf = keep
   266         self.keepbranchesf = keepbranches
   276         self.keepbranchesf = keepbranches
   267         self.external = external
   277         self.external = external
   268         self.activebookmark = activebookmark
   278         self.activebookmark = activebookmark
   269 
   279 
   270     def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
   280     def _handleskippingobsolete(self, obsoleterevs, destmap):
   271         """Compute structures necessary for skipping obsolete revisions
   281         """Compute structures necessary for skipping obsolete revisions
   272 
   282 
   273         rebaserevs:     iterable of all revisions that are to be rebased
       
   274         obsoleterevs:   iterable of all obsolete revisions in rebaseset
   283         obsoleterevs:   iterable of all obsolete revisions in rebaseset
   275         dest:           a destination revision for the rebase operation
   284         destmap:        {srcrev: destrev} destination revisions
   276         """
   285         """
   277         self.obsoletenotrebased = {}
   286         self.obsoletenotrebased = {}
   278         if not self.ui.configbool('experimental', 'rebaseskipobsolete',
   287         if not self.ui.configbool('experimental', 'rebaseskipobsolete',
   279                                   default=True):
   288                                   default=True):
   280             return
   289             return
   281         obsoleteset = set(obsoleterevs)
   290         obsoleteset = set(obsoleterevs)
   282         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
   291         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
   283                                     obsoleteset, dest)
   292                                     obsoleteset, destmap)
   284         skippedset = set(self.obsoletenotrebased)
   293         skippedset = set(self.obsoletenotrebased)
   285         _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
   294         _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
   286 
   295 
   287     def _prepareabortorcontinue(self, isabort):
   296     def _prepareabortorcontinue(self, isabort):
   288         try:
   297         try:
   298             else:
   307             else:
   299                 msg = _('cannot continue inconsistent rebase')
   308                 msg = _('cannot continue inconsistent rebase')
   300                 hint = _('use "hg rebase --abort" to clear broken state')
   309                 hint = _('use "hg rebase --abort" to clear broken state')
   301                 raise error.Abort(msg, hint=hint)
   310                 raise error.Abort(msg, hint=hint)
   302         if isabort:
   311         if isabort:
   303             return abort(self.repo, self.originalwd, self.dest,
   312             return abort(self.repo, self.originalwd, self.destmap,
   304                          self.state, activebookmark=self.activebookmark)
   313                          self.state, activebookmark=self.activebookmark)
   305 
   314 
   306     def _preparenewrebase(self, dest, rebaseset):
   315     def _preparenewrebase(self, destmap):
   307         if dest is None:
   316         if not destmap:
   308             return _nothingtorebase()
   317             return _nothingtorebase()
   309 
   318 
       
   319         rebaseset = destmap.keys()
   310         allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
   320         allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
   311         if (not (self.keepf or allowunstable)
   321         if (not (self.keepf or allowunstable)
   312               and self.repo.revs('first(children(%ld) - %ld)',
   322               and self.repo.revs('first(children(%ld) - %ld)',
   313                                  rebaseset, rebaseset)):
   323                                  rebaseset, rebaseset)):
   314             raise error.Abort(
   324             raise error.Abort(
   315                 _("can't remove original changesets with"
   325                 _("can't remove original changesets with"
   316                   " unrebased descendants"),
   326                   " unrebased descendants"),
   317                 hint=_('use --keep to keep original changesets'))
   327                 hint=_('use --keep to keep original changesets'))
   318 
   328 
   319         obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
   329         obsrevs = _filterobsoleterevs(self.repo, rebaseset)
   320         self._handleskippingobsolete(rebaseset, obsrevs, dest.rev())
   330         self._handleskippingobsolete(obsrevs, destmap)
   321 
   331 
   322         result = buildstate(self.repo, dest, rebaseset, self.collapsef,
   332         result = buildstate(self.repo, destmap, self.collapsef,
   323                             self.obsoletenotrebased)
   333                             self.obsoletenotrebased)
   324 
   334 
   325         if not result:
   335         if not result:
   326             # Empty state built, nothing to rebase
   336             # Empty state built, nothing to rebase
   327             self.ui.status(_('nothing to rebase\n'))
   337             self.ui.status(_('nothing to rebase\n'))
   331             if not self.keepf and not root.mutable():
   341             if not self.keepf and not root.mutable():
   332                 raise error.Abort(_("can't rebase public changeset %s")
   342                 raise error.Abort(_("can't rebase public changeset %s")
   333                                   % root,
   343                                   % root,
   334                                   hint=_("see 'hg help phases' for details"))
   344                                   hint=_("see 'hg help phases' for details"))
   335 
   345 
   336         (self.originalwd, self.dest, self.state) = result
   346         (self.originalwd, self.destmap, self.state) = result
   337         if self.collapsef:
   347         if self.collapsef:
   338             destancestors = self.repo.changelog.ancestors([self.dest],
   348             dests = set(self.destmap.values())
       
   349             if len(dests) != 1:
       
   350                 raise error.Abort(
       
   351                     _('--collapse does not work with multiple destinations'))
       
   352             destrev = next(iter(dests))
       
   353             destancestors = self.repo.changelog.ancestors([destrev],
   339                                                           inclusive=True)
   354                                                           inclusive=True)
   340             self.external = externalparent(self.repo, self.state, destancestors)
   355             self.external = externalparent(self.repo, self.state, destancestors)
   341 
   356 
   342         if dest.closesbranch() and not self.keepbranchesf:
   357         for destrev in sorted(set(destmap.values())):
   343             self.ui.status(_('reopening closed branch head %s\n') % dest)
   358             dest = self.repo[destrev]
       
   359             if dest.closesbranch() and not self.keepbranchesf:
       
   360                 self.ui.status(_('reopening closed branch head %s\n') % dest)
   344 
   361 
   345     def _performrebase(self, tr):
   362     def _performrebase(self, tr):
   346         repo, ui, opts = self.repo, self.ui, self.opts
   363         repo, ui, opts = self.repo, self.ui, self.opts
   347         if self.keepbranchesf:
   364         if self.keepbranchesf:
   348             # insert _savebranch at the start of extrafns so if
   365             # insert _savebranch at the start of extrafns so if
   369         sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
   386         sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
   370         cands = [k for k, v in self.state.iteritems() if v == revtodo]
   387         cands = [k for k, v in self.state.iteritems() if v == revtodo]
   371         total = len(cands)
   388         total = len(cands)
   372         pos = 0
   389         pos = 0
   373         for rev in sortedrevs:
   390         for rev in sortedrevs:
       
   391             dest = self.destmap[rev]
   374             ctx = repo[rev]
   392             ctx = repo[rev]
   375             desc = _ctxdesc(ctx)
   393             desc = _ctxdesc(ctx)
   376             if self.state[rev] == rev:
   394             if self.state[rev] == rev:
   377                 ui.status(_('already rebased %s\n') % desc)
   395                 ui.status(_('already rebased %s\n') % desc)
   378             elif self.state[rev] == revtodo:
   396             elif self.state[rev] == revtodo:
   379                 pos += 1
   397                 pos += 1
   380                 ui.status(_('rebasing %s\n') % desc)
   398                 ui.status(_('rebasing %s\n') % desc)
   381                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
   399                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
   382                             _('changesets'), total)
   400                             _('changesets'), total)
   383                 p1, p2, base = defineparents(repo, rev, self.dest, self.state)
   401                 p1, p2, base = defineparents(repo, rev, self.destmap,
       
   402                                              self.state)
   384                 self.storestatus(tr=tr)
   403                 self.storestatus(tr=tr)
   385                 storecollapsemsg(repo, self.collapsemsg)
   404                 storecollapsemsg(repo, self.collapsemsg)
   386                 if len(repo[None].parents()) == 2:
   405                 if len(repo[None].parents()) == 2:
   387                     repo.ui.debug('resuming interrupted rebase\n')
   406                     repo.ui.debug('resuming interrupted rebase\n')
   388                 else:
   407                 else:
   389                     try:
   408                     try:
   390                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
   409                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
   391                                      'rebase')
   410                                      'rebase')
   392                         stats = rebasenode(repo, rev, p1, base, self.state,
   411                         stats = rebasenode(repo, rev, p1, base, self.state,
   393                                            self.collapsef, self.dest)
   412                                            self.collapsef, dest)
   394                         if stats and stats[3] > 0:
   413                         if stats and stats[3] > 0:
   395                             raise error.InterventionRequired(
   414                             raise error.InterventionRequired(
   396                                 _('unresolved conflicts (see hg '
   415                                 _('unresolved conflicts (see hg '
   397                                   'resolve, then hg rebase --continue)'))
   416                                   'resolve, then hg rebase --continue)'))
   398                     finally:
   417                     finally:
   434         ui.note(_('rebase merging completed\n'))
   453         ui.note(_('rebase merging completed\n'))
   435 
   454 
   436     def _finishrebase(self):
   455     def _finishrebase(self):
   437         repo, ui, opts = self.repo, self.ui, self.opts
   456         repo, ui, opts = self.repo, self.ui, self.opts
   438         if self.collapsef and not self.keepopen:
   457         if self.collapsef and not self.keepopen:
   439             p1, p2, _base = defineparents(repo, min(self.state),
   458             p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
   440                                           self.dest, self.state)
   459                                           self.state)
   441             editopt = opts.get('edit')
   460             editopt = opts.get('edit')
   442             editform = 'rebase.collapse'
   461             editform = 'rebase.collapse'
   443             if self.collapsemsg:
   462             if self.collapsemsg:
   444                 commitmsg = self.collapsemsg
   463                 commitmsg = self.collapsemsg
   445             else:
   464             else:
   481 
   500 
   482         if not self.keepf:
   501         if not self.keepf:
   483             collapsedas = None
   502             collapsedas = None
   484             if self.collapsef:
   503             if self.collapsef:
   485                 collapsedas = newnode
   504                 collapsedas = newnode
   486             clearrebased(ui, repo, self.dest, self.state, self.skipped,
   505             clearrebased(ui, repo, self.destmap, self.state, self.skipped,
   487                          collapsedas)
   506                          collapsedas)
   488 
   507 
   489         clearstatus(repo)
   508         clearstatus(repo)
   490         clearcollapsemsg(repo)
   509         clearcollapsemsg(repo)
   491 
   510 
   673 
   692 
   674             retcode = rbsrt._prepareabortorcontinue(abortf)
   693             retcode = rbsrt._prepareabortorcontinue(abortf)
   675             if retcode is not None:
   694             if retcode is not None:
   676                 return retcode
   695                 return retcode
   677         else:
   696         else:
   678             dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
   697             destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
   679                                           destspace=destspace)
   698                                      destspace=destspace)
   680             retcode = rbsrt._preparenewrebase(dest, rebaseset)
   699             retcode = rbsrt._preparenewrebase(destmap)
   681             if retcode is not None:
   700             if retcode is not None:
   682                 return retcode
   701                 return retcode
   683 
   702 
   684         tr = None
   703         tr = None
   685         dsguard = None
   704         dsguard = None
   693             with util.acceptintervention(dsguard):
   712             with util.acceptintervention(dsguard):
   694                 rbsrt._performrebase(tr)
   713                 rbsrt._performrebase(tr)
   695 
   714 
   696         rbsrt._finishrebase()
   715         rbsrt._finishrebase()
   697 
   716 
   698 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
   717 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
   699                 destspace=None):
   718                    destspace=None):
   700     """use revisions argument to define destination and rebase set
   719     """use revisions argument to define destmap {srcrev: destrev}"""
   701     """
       
   702     if revf is None:
   720     if revf is None:
   703         revf = []
   721         revf = []
   704 
   722 
   705     # destspace is here to work around issues with `hg pull --rebase` see
   723     # destspace is here to work around issues with `hg pull --rebase` see
   706     # issue5214 for details
   724     # issue5214 for details
   723 
   741 
   724     if revf:
   742     if revf:
   725         rebaseset = scmutil.revrange(repo, revf)
   743         rebaseset = scmutil.revrange(repo, revf)
   726         if not rebaseset:
   744         if not rebaseset:
   727             ui.status(_('empty "rev" revision set - nothing to rebase\n'))
   745             ui.status(_('empty "rev" revision set - nothing to rebase\n'))
   728             return None, None
   746             return None
   729     elif srcf:
   747     elif srcf:
   730         src = scmutil.revrange(repo, [srcf])
   748         src = scmutil.revrange(repo, [srcf])
   731         if not src:
   749         if not src:
   732             ui.status(_('empty "source" revision set - nothing to rebase\n'))
   750             ui.status(_('empty "source" revision set - nothing to rebase\n'))
   733             return None, None
   751             return None
   734         rebaseset = repo.revs('(%ld)::', src)
   752         rebaseset = repo.revs('(%ld)::', src)
   735         assert rebaseset
   753         assert rebaseset
   736     else:
   754     else:
   737         base = scmutil.revrange(repo, [basef or '.'])
   755         base = scmutil.revrange(repo, [basef or '.'])
   738         if not base:
   756         if not base:
   739             ui.status(_('empty "base" revision set - '
   757             ui.status(_('empty "base" revision set - '
   740                         "can't compute rebase set\n"))
   758                         "can't compute rebase set\n"))
   741             return None, None
   759             return None
   742         if not destf:
   760         if not destf:
   743             dest = repo[_destrebase(repo, base, destspace=destspace)]
   761             dest = repo[_destrebase(repo, base, destspace=destspace)]
   744             destf = str(dest)
   762             destf = str(dest)
   745 
   763 
   746         roots = [] # selected children of branching points
   764         roots = [] # selected children of branching points
   780                                 'directory parent is already an '
   798                                 'directory parent is already an '
   781                                 'ancestor of destination %s\n') % dest)
   799                                 'ancestor of destination %s\n') % dest)
   782             else: # can it happen?
   800             else: # can it happen?
   783                 ui.status(_('nothing to rebase from %s to %s\n') %
   801                 ui.status(_('nothing to rebase from %s to %s\n') %
   784                           ('+'.join(str(repo[r]) for r in base), dest))
   802                           ('+'.join(str(repo[r]) for r in base), dest))
   785             return None, None
   803             return None
   786 
   804 
   787     if not destf:
   805     if not destf:
   788         dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
   806         dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
   789         destf = str(dest)
   807         destf = str(dest)
   790 
   808 
   791     return dest, rebaseset
   809     # assign dest to each rev in rebaseset
       
   810     destrev = dest.rev()
       
   811     destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
       
   812 
       
   813     return destmap
   792 
   814 
   793 def externalparent(repo, state, destancestors):
   815 def externalparent(repo, state, destancestors):
   794     """Return the revision that should be used as the second parent
   816     """Return the revision that should be used as the second parent
   795     when the revisions in state is collapsed on top of destancestors.
   817     when the revisions in state is collapsed on top of destancestors.
   796     Abort if there is more than one parent.
   818     Abort if there is more than one parent.
   872         # performed in the destination.
   894         # performed in the destination.
   873         p1rev = repo[rev].p1().rev()
   895         p1rev = repo[rev].p1().rev()
   874         copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
   896         copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
   875     return stats
   897     return stats
   876 
   898 
   877 def adjustdest(repo, rev, dest, state):
   899 def adjustdest(repo, rev, destmap, state):
   878     """adjust rebase destination given the current rebase state
   900     """adjust rebase destination given the current rebase state
   879 
   901 
   880     rev is what is being rebased. Return a list of two revs, which are the
   902     rev is what is being rebased. Return a list of two revs, which are the
   881     adjusted destinations for rev's p1 and p2, respectively. If a parent is
   903     adjusted destinations for rev's p1 and p2, respectively. If a parent is
   882     nullrev, return dest without adjustment for it.
   904     nullrev, return dest without adjustment for it.
   912         | |/        |
   934         | |/        |
   913         | B         | ...
   935         | B         | ...
   914         |/          |/
   936         |/          |/
   915         A           A
   937         A           A
   916     """
   938     """
   917     # pick already rebased revs from state
   939     # pick already rebased revs with same dest from state as interesting source
   918     source = [s for s, d in state.items() if d > 0]
   940     dest = destmap[rev]
       
   941     source = [s for s, d in state.items() if d > 0 and destmap[s] == dest]
   919 
   942 
   920     result = []
   943     result = []
   921     for prev in repo.changelog.parentrevs(rev):
   944     for prev in repo.changelog.parentrevs(rev):
   922         adjusted = dest
   945         adjusted = dest
   923         if prev != nullrev:
   946         if prev != nullrev:
   955     nodemap = unfi.changelog.nodemap
   978     nodemap = unfi.changelog.nodemap
   956     for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
   979     for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
   957         if s in nodemap:
   980         if s in nodemap:
   958             yield nodemap[s]
   981             yield nodemap[s]
   959 
   982 
   960 def defineparents(repo, rev, dest, state):
   983 def defineparents(repo, rev, destmap, state):
   961     """Return new parents and optionally a merge base for rev being rebased
   984     """Return new parents and optionally a merge base for rev being rebased
   962 
   985 
   963     The destination specified by "dest" cannot always be used directly because
   986     The destination specified by "dest" cannot always be used directly because
   964     previously rebase result could affect destination. For example,
   987     previously rebase result could affect destination. For example,
   965 
   988 
   979             return True
  1002             return True
   980         elif a > b:
  1003         elif a > b:
   981             return False
  1004             return False
   982         return cl.isancestor(cl.node(a), cl.node(b))
  1005         return cl.isancestor(cl.node(a), cl.node(b))
   983 
  1006 
       
  1007     dest = destmap[rev]
   984     oldps = repo.changelog.parentrevs(rev) # old parents
  1008     oldps = repo.changelog.parentrevs(rev) # old parents
   985     newps = [nullrev, nullrev] # new parents
  1009     newps = [nullrev, nullrev] # new parents
   986     dests = adjustdest(repo, rev, dest, state) # adjusted destinations
  1010     dests = adjustdest(repo, rev, destmap, state) # adjusted destinations
   987     bases = list(oldps) # merge base candidates, initially just old parents
  1011     bases = list(oldps) # merge base candidates, initially just old parents
   988 
  1012 
   989     if all(r == nullrev for r in oldps[1:]):
  1013     if all(r == nullrev for r in oldps[1:]):
   990         # For non-merge changeset, just move p to adjusted dest as requested.
  1014         # For non-merge changeset, just move p to adjusted dest as requested.
   991         newps[0] = dests[0]
  1015         newps[0] = dests[0]
  1236     if firstunrebased in parents:
  1260     if firstunrebased in parents:
  1237         return True
  1261         return True
  1238 
  1262 
  1239     return False
  1263     return False
  1240 
  1264 
  1241 def abort(repo, originalwd, dest, state, activebookmark=None):
  1265 def abort(repo, originalwd, destmap, state, activebookmark=None):
  1242     '''Restore the repository to its original state.  Additional args:
  1266     '''Restore the repository to its original state.  Additional args:
  1243 
  1267 
  1244     activebookmark: the name of the bookmark that should be active after the
  1268     activebookmark: the name of the bookmark that should be active after the
  1245         restore'''
  1269         restore'''
  1246 
  1270 
  1247     try:
  1271     try:
  1248         # If the first commits in the rebased set get skipped during the rebase,
  1272         # If the first commits in the rebased set get skipped during the rebase,
  1249         # their values within the state mapping will be the dest rev id. The
  1273         # their values within the state mapping will be the dest rev id. The
  1250         # dstates list must must not contain the dest rev (issue4896)
  1274         # dstates list must must not contain the dest rev (issue4896)
  1251         dstates = [s for s in state.values() if s >= 0 and s != dest]
  1275         dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
  1252         immutable = [d for d in dstates if not repo[d].mutable()]
  1276         immutable = [d for d in dstates if not repo[d].mutable()]
  1253         cleanup = True
  1277         cleanup = True
  1254         if immutable:
  1278         if immutable:
  1255             repo.ui.warn(_("warning: can't clean up public changesets %s\n")
  1279             repo.ui.warn(_("warning: can't clean up public changesets %s\n")
  1256                         % ', '.join(str(repo[r]) for r in immutable),
  1280                         % ', '.join(str(repo[r]) for r in immutable),
  1265                            "branch, can't strip\n"))
  1289                            "branch, can't strip\n"))
  1266             cleanup = False
  1290             cleanup = False
  1267 
  1291 
  1268         if cleanup:
  1292         if cleanup:
  1269             shouldupdate = False
  1293             shouldupdate = False
  1270             rebased = filter(lambda x: x >= 0 and x != dest, state.values())
  1294             rebased = [s for r, s in state.items()
       
  1295                        if s >= 0 and s != destmap[r]]
  1271             if rebased:
  1296             if rebased:
  1272                 strippoints = [
  1297                 strippoints = [
  1273                         c.node() for c in repo.set('roots(%ld)', rebased)]
  1298                         c.node() for c in repo.set('roots(%ld)', rebased)]
  1274 
  1299 
  1275             updateifonnodes = set(rebased)
  1300             updateifonnodes = set(rebased)
  1276             updateifonnodes.add(dest)
  1301             updateifonnodes.update(destmap.values())
  1277             updateifonnodes.add(originalwd)
  1302             updateifonnodes.add(originalwd)
  1278             shouldupdate = repo['.'].rev() in updateifonnodes
  1303             shouldupdate = repo['.'].rev() in updateifonnodes
  1279 
  1304 
  1280             # Update away from the rebase if necessary
  1305             # Update away from the rebase if necessary
  1281             if shouldupdate or needupdate(repo, state):
  1306             if shouldupdate or needupdate(repo, state):
  1293         clearstatus(repo)
  1318         clearstatus(repo)
  1294         clearcollapsemsg(repo)
  1319         clearcollapsemsg(repo)
  1295         repo.ui.warn(_('rebase aborted\n'))
  1320         repo.ui.warn(_('rebase aborted\n'))
  1296     return 0
  1321     return 0
  1297 
  1322 
  1298 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
  1323 def buildstate(repo, destmap, collapse, obsoletenotrebased):
  1299     '''Define which revisions are going to be rebased and where
  1324     '''Define which revisions are going to be rebased and where
  1300 
  1325 
  1301     repo: repo
  1326     repo: repo
  1302     dest: context
  1327     destmap: {srcrev: destrev}
  1303     rebaseset: set of rev
       
  1304     '''
  1328     '''
       
  1329     rebaseset = destmap.keys()
  1305     originalwd = repo['.'].rev()
  1330     originalwd = repo['.'].rev()
  1306     _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
  1331     _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
  1307 
  1332 
  1308     # This check isn't strictly necessary, since mq detects commits over an
  1333     # This check isn't strictly necessary, since mq detects commits over an
  1309     # applied patch. But it prevents messing up the working directory when
  1334     # applied patch. But it prevents messing up the working directory when
  1310     # a partially completed rebase is blocked by mq.
  1335     # a partially completed rebase is blocked by mq.
  1311     if 'qtip' in repo.tags() and (dest.node() in
  1336     if 'qtip' in repo.tags():
  1312                             [s.node for s in repo.mq.applied]):
  1337         mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
  1313         raise error.Abort(_('cannot rebase onto an applied mq patch'))
  1338         if set(destmap.values()) & mqapplied:
       
  1339             raise error.Abort(_('cannot rebase onto an applied mq patch'))
  1314 
  1340 
  1315     roots = list(repo.set('roots(%ld)', rebaseset))
  1341     roots = list(repo.set('roots(%ld)', rebaseset))
  1316     if not roots:
  1342     if not roots:
  1317         raise error.Abort(_('no matching revisions'))
  1343         raise error.Abort(_('no matching revisions'))
  1318     roots.sort()
  1344     roots.sort()
  1319     state = dict.fromkeys(rebaseset, revtodo)
  1345     state = dict.fromkeys(rebaseset, revtodo)
  1320     emptyrebase = True
  1346     emptyrebase = True
  1321     for root in roots:
  1347     for root in roots:
       
  1348         dest = repo[destmap[root.rev()]]
  1322         commonbase = root.ancestor(dest)
  1349         commonbase = root.ancestor(dest)
  1323         if commonbase == root:
  1350         if commonbase == root:
  1324             raise error.Abort(_('source is ancestor of destination'))
  1351             raise error.Abort(_('source is ancestor of destination'))
  1325         if commonbase == dest:
  1352         if commonbase == dest:
  1326             wctx = repo[None]
  1353             wctx = repo[None]
  1350         desc = _ctxdesc(unfi[r])
  1377         desc = _ctxdesc(unfi[r])
  1351         succ = obsoletenotrebased[r]
  1378         succ = obsoletenotrebased[r]
  1352         if succ is None:
  1379         if succ is None:
  1353             msg = _('note: not rebasing %s, it has no successor\n') % desc
  1380             msg = _('note: not rebasing %s, it has no successor\n') % desc
  1354             del state[r]
  1381             del state[r]
       
  1382             del destmap[r]
  1355         else:
  1383         else:
  1356             destctx = unfi[succ]
  1384             destctx = unfi[succ]
  1357             destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
  1385             destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
  1358                                        destctx.description().split('\n', 1)[0])
  1386                                        destctx.description().split('\n', 1)[0])
  1359             msg = (_('note: not rebasing %s, already in destination as %s\n')
  1387             msg = (_('note: not rebasing %s, already in destination as %s\n')
  1360                    % (desc, destdesc))
  1388                    % (desc, destdesc))
  1361             del state[r]
  1389             del state[r]
       
  1390             del destmap[r]
  1362         repo.ui.status(msg)
  1391         repo.ui.status(msg)
  1363     return originalwd, dest.rev(), state
  1392     return originalwd, destmap, state
  1364 
  1393 
  1365 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
  1394 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
  1366     """dispose of rebased revision at the end of the rebase
  1395     """dispose of rebased revision at the end of the rebase
  1367 
  1396 
  1368     If `collapsedas` is not None, the rebase was a collapse whose result if the
  1397     If `collapsedas` is not None, the rebase was a collapse whose result if the
  1369     `collapsedas` node."""
  1398     `collapsedas` node."""
  1370     tonode = repo.changelog.node
  1399     tonode = repo.changelog.node
  1371     # Move bookmark of skipped nodes to destination. This cannot be handled
  1400     # Move bookmark of skipped nodes to destination. This cannot be handled
  1372     # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
  1401     # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
  1373     # and move bookmark backwards.
  1402     # and move bookmark backwards.
  1374     bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
  1403     bmchanges = [(name, tonode(max(adjustdest(repo, rev, destmap, state))))
  1375                  for rev in skipped
  1404                  for rev in skipped
  1376                  for name in repo.nodebookmarks(tonode(rev))]
  1405                  for name in repo.nodebookmarks(tonode(rev))]
  1377     if bmchanges:
  1406     if bmchanges:
  1378         with repo.transaction('rebase') as tr:
  1407         with repo.transaction('rebase') as tr:
  1379             repo._bookmarks.applychanges(repo, tr, bmchanges)
  1408             repo._bookmarks.applychanges(repo, tr, bmchanges)
  1475 
  1504 
  1476 def _filterobsoleterevs(repo, revs):
  1505 def _filterobsoleterevs(repo, revs):
  1477     """returns a set of the obsolete revisions in revs"""
  1506     """returns a set of the obsolete revisions in revs"""
  1478     return set(r for r in revs if repo[r].obsolete())
  1507     return set(r for r in revs if repo[r].obsolete())
  1479 
  1508 
  1480 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
  1509 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
  1481     """return a mapping obsolete => successor for all obsolete nodes to be
  1510     """return a mapping obsolete => successor for all obsolete nodes to be
  1482     rebased that have a successors in the destination
  1511     rebased that have a successors in the destination
  1483 
  1512 
  1484     obsolete => None entries in the mapping indicate nodes with no successor"""
  1513     obsolete => None entries in the mapping indicate nodes with no successor"""
  1485     obsoletenotrebased = {}
  1514     obsoletenotrebased = {}
  1486 
  1515 
  1487     cl = repo.unfiltered().changelog
  1516     cl = repo.unfiltered().changelog
  1488     nodemap = cl.nodemap
  1517     nodemap = cl.nodemap
  1489     destnode = cl.node(dest)
       
  1490     for srcrev in rebaseobsrevs:
  1518     for srcrev in rebaseobsrevs:
  1491         srcnode = cl.node(srcrev)
  1519         srcnode = cl.node(srcrev)
       
  1520         destnode = cl.node(destmap[srcrev])
  1492         # XXX: more advanced APIs are required to handle split correctly
  1521         # XXX: more advanced APIs are required to handle split correctly
  1493         successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
  1522         successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
  1494         if len(successors) == 1:
  1523         if len(successors) == 1:
  1495             # obsutil.allsuccessors includes node itself. When the list only
  1524             # obsutil.allsuccessors includes node itself. When the list only
  1496             # contains one element, it means there are no successors.
  1525             # contains one element, it means there are no successors.