hgext/rebase.py
changeset 29358 6e83f5bbed8d
parent 29205 a0939666b836
child 29359 d2168849539d
equal deleted inserted replaced
29357:66d41c9e9222 29358:6e83f5bbed8d
   117     sourceset = None
   117     sourceset = None
   118     if x is not None:
   118     if x is not None:
   119         sourceset = revset.getset(repo, revset.fullreposet(repo), x)
   119         sourceset = revset.getset(repo, revset.fullreposet(repo), x)
   120     return subset & revset.baseset([_destrebase(repo, sourceset)])
   120     return subset & revset.baseset([_destrebase(repo, sourceset)])
   121 
   121 
       
   122 class rebaseruntime(object):
       
   123     """This class is a container for rebase runtime state"""
       
   124     def __init__(self):
       
   125         self.originalwd = None
       
   126         self.external = nullrev
       
   127         # Mapping between the old revision id and either what is the new rebased
       
   128         # revision or what needs to be done with the old revision. The state
       
   129         # dict will be what contains most of the rebase progress state.
       
   130         self.state = {}
       
   131         self.activebookmark = None
       
   132 
   122 @command('rebase',
   133 @command('rebase',
   123     [('s', 'source', '',
   134     [('s', 'source', '',
   124      _('rebase the specified changeset and descendants'), _('REV')),
   135      _('rebase the specified changeset and descendants'), _('REV')),
   125     ('b', 'base', '',
   136     ('b', 'base', '',
   126      _('rebase everything from branching point of specified changeset'),
   137      _('rebase everything from branching point of specified changeset'),
   227 
   238 
   228     Returns 0 on success, 1 if nothing to rebase or there are
   239     Returns 0 on success, 1 if nothing to rebase or there are
   229     unresolved conflicts.
   240     unresolved conflicts.
   230 
   241 
   231     """
   242     """
   232     originalwd = target = None
   243     rbsrt = rebaseruntime()
   233     activebookmark = None
   244     target = None
   234     external = nullrev
       
   235     # Mapping between the old revision id and either what is the new rebased
       
   236     # revision or what needs to be done with the old revision. The state dict
       
   237     # will be what contains most of the rebase progress state.
       
   238     state = {}
       
   239     skipped = set()
   245     skipped = set()
   240     targetancestors = set()
   246     targetancestors = set()
   241 
   247 
   242 
   248 
   243     lock = wlock = None
   249     lock = wlock = None
   294                     _('abort and continue do not allow specifying revisions'))
   300                     _('abort and continue do not allow specifying revisions'))
   295             if abortf and opts.get('tool', False):
   301             if abortf and opts.get('tool', False):
   296                 ui.warn(_('tool option will be ignored\n'))
   302                 ui.warn(_('tool option will be ignored\n'))
   297 
   303 
   298             try:
   304             try:
   299                 (originalwd, target, state, skipped, collapsef, keepf,
   305                 (rbsrt.originalwd, target, rbsrt.state, skipped,
   300                  keepbranchesf, external, activebookmark) = restorestatus(repo)
   306                  collapsef, keepf, keepbranchesf, rbsrt.external,
       
   307                  rbsrt.activebookmark) = restorestatus(repo)
   301                 collapsemsg = restorecollapsemsg(repo)
   308                 collapsemsg = restorecollapsemsg(repo)
   302             except error.RepoLookupError:
   309             except error.RepoLookupError:
   303                 if abortf:
   310                 if abortf:
   304                     clearstatus(repo)
   311                     clearstatus(repo)
   305                     clearcollapsemsg(repo)
   312                     clearcollapsemsg(repo)
   309                 else:
   316                 else:
   310                     msg = _('cannot continue inconsistent rebase')
   317                     msg = _('cannot continue inconsistent rebase')
   311                     hint = _('use "hg rebase --abort" to clear broken state')
   318                     hint = _('use "hg rebase --abort" to clear broken state')
   312                     raise error.Abort(msg, hint=hint)
   319                     raise error.Abort(msg, hint=hint)
   313             if abortf:
   320             if abortf:
   314                 return abort(repo, originalwd, target, state,
   321                 return abort(repo, rbsrt.originalwd, target, rbsrt.state,
   315                              activebookmark=activebookmark)
   322                              activebookmark=rbsrt.activebookmark)
   316 
   323 
   317             obsoletenotrebased = {}
   324             obsoletenotrebased = {}
   318             if ui.configbool('experimental', 'rebaseskipobsolete',
   325             if ui.configbool('experimental', 'rebaseskipobsolete',
   319                              default=True):
   326                              default=True):
   320                 rebaseobsrevs = set([r for r, status in state.items()
   327                 rebaseobsrevs = set([r for r, st in rbsrt.state.items()
   321                                      if status == revprecursor])
   328                                      if st == revprecursor])
   322                 rebasesetrevs = set(state.keys())
   329                 rebasesetrevs = set(rbsrt.state.keys())
   323                 obsoletenotrebased = _computeobsoletenotrebased(repo,
   330                 obsoletenotrebased = _computeobsoletenotrebased(repo,
   324                                                                 rebaseobsrevs,
   331                                         rebaseobsrevs, target)
   325                                                                 target)
       
   326                 rebaseobsskipped = set(obsoletenotrebased)
   332                 rebaseobsskipped = set(obsoletenotrebased)
   327                 _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs,
   333                 _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs,
   328                                 rebaseobsskipped)
   334                                 rebaseobsskipped)
   329         else:
   335         else:
   330             dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
   336             dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
   366             if not keepf and not repo[root].mutable():
   372             if not keepf and not repo[root].mutable():
   367                 raise error.Abort(_("can't rebase public changeset %s")
   373                 raise error.Abort(_("can't rebase public changeset %s")
   368                                  % repo[root],
   374                                  % repo[root],
   369                                  hint=_('see "hg help phases" for details'))
   375                                  hint=_('see "hg help phases" for details'))
   370 
   376 
   371             originalwd, target, state = result
   377             (rbsrt.originalwd, target, rbsrt.state) = result
   372             if collapsef:
   378             if collapsef:
   373                 targetancestors = repo.changelog.ancestors([target],
   379                 targetancestors = repo.changelog.ancestors([target],
   374                                                            inclusive=True)
   380                                                            inclusive=True)
   375                 external = externalparent(repo, state, targetancestors)
   381                 rbsrt.external = externalparent(repo, rbsrt.state,
       
   382                                                        targetancestors)
   376 
   383 
   377             if dest.closesbranch() and not keepbranchesf:
   384             if dest.closesbranch() and not keepbranchesf:
   378                 ui.status(_('reopening closed branch head %s\n') % dest)
   385                 ui.status(_('reopening closed branch head %s\n') % dest)
   379 
   386 
   380         if keepbranchesf:
   387         if keepbranchesf:
   382             # there's a user-provided extrafn it can clobber branch if
   389             # there's a user-provided extrafn it can clobber branch if
   383             # desired
   390             # desired
   384             extrafns.insert(0, _savebranch)
   391             extrafns.insert(0, _savebranch)
   385             if collapsef:
   392             if collapsef:
   386                 branches = set()
   393                 branches = set()
   387                 for rev in state:
   394                 for rev in rbsrt.state:
   388                     branches.add(repo[rev].branch())
   395                     branches.add(repo[rev].branch())
   389                     if len(branches) > 1:
   396                     if len(branches) > 1:
   390                         raise error.Abort(_('cannot collapse multiple named '
   397                         raise error.Abort(_('cannot collapse multiple named '
   391                             'branches'))
   398                             'branches'))
   392 
   399 
   394         if not targetancestors:
   401         if not targetancestors:
   395             targetancestors = repo.changelog.ancestors([target], inclusive=True)
   402             targetancestors = repo.changelog.ancestors([target], inclusive=True)
   396 
   403 
   397         # Keep track of the current bookmarks in order to reset them later
   404         # Keep track of the current bookmarks in order to reset them later
   398         currentbookmarks = repo._bookmarks.copy()
   405         currentbookmarks = repo._bookmarks.copy()
   399         activebookmark = activebookmark or repo._activebookmark
   406         rbsrt.activebookmark = rbsrt.activebookmark or repo._activebookmark
   400         if activebookmark:
   407         if rbsrt.activebookmark:
   401             bookmarks.deactivate(repo)
   408             bookmarks.deactivate(repo)
   402 
   409 
   403         extrafn = _makeextrafn(extrafns)
   410         extrafn = _makeextrafn(extrafns)
   404 
   411 
   405         sortedstate = sorted(state)
   412         sortedstate = sorted(rbsrt.state)
   406         total = len(sortedstate)
   413         total = len(sortedstate)
   407         pos = 0
   414         pos = 0
   408         for rev in sortedstate:
   415         for rev in sortedstate:
   409             ctx = repo[rev]
   416             ctx = repo[rev]
   410             desc = '%d:%s "%s"' % (ctx.rev(), ctx,
   417             desc = '%d:%s "%s"' % (ctx.rev(), ctx,
   411                                    ctx.description().split('\n', 1)[0])
   418                                    ctx.description().split('\n', 1)[0])
   412             names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
   419             names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
   413             if names:
   420             if names:
   414                 desc += ' (%s)' % ' '.join(names)
   421                 desc += ' (%s)' % ' '.join(names)
   415             pos += 1
   422             pos += 1
   416             if state[rev] == revtodo:
   423             if rbsrt.state[rev] == revtodo:
   417                 ui.status(_('rebasing %s\n') % desc)
   424                 ui.status(_('rebasing %s\n') % desc)
   418                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
   425                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
   419                             _('changesets'), total)
   426                             _('changesets'), total)
   420                 p1, p2, base = defineparents(repo, rev, target, state,
   427                 p1, p2, base = defineparents(repo, rev, target, rbsrt.state,
   421                                              targetancestors,
   428                                              targetancestors,
   422                                              obsoletenotrebased)
   429                                              obsoletenotrebased)
   423                 storestatus(repo, originalwd, target, state, collapsef, keepf,
   430                 storestatus(repo, rbsrt.originalwd, target, rbsrt.state,
   424                             keepbranchesf, external, activebookmark)
   431                             collapsef, keepf, keepbranchesf,
       
   432                             rbsrt.external, rbsrt.activebookmark)
   425                 storecollapsemsg(repo, collapsemsg)
   433                 storecollapsemsg(repo, collapsemsg)
   426                 if len(repo[None].parents()) == 2:
   434                 if len(repo[None].parents()) == 2:
   427                     repo.ui.debug('resuming interrupted rebase\n')
   435                     repo.ui.debug('resuming interrupted rebase\n')
   428                 else:
   436                 else:
   429                     try:
   437                     try:
   430                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
   438                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
   431                                      'rebase')
   439                                      'rebase')
   432                         stats = rebasenode(repo, rev, p1, base, state,
   440                         stats = rebasenode(repo, rev, p1, base, rbsrt.state,
   433                                            collapsef, target)
   441                                            collapsef, target)
   434                         if stats and stats[3] > 0:
   442                         if stats and stats[3] > 0:
   435                             raise error.InterventionRequired(
   443                             raise error.InterventionRequired(
   436                                 _('unresolved conflicts (see hg '
   444                                 _('unresolved conflicts (see hg '
   437                                   'resolve, then hg rebase --continue)'))
   445                                   'resolve, then hg rebase --continue)'))
   451                     repo.setparents(repo[p1].node())
   459                     repo.setparents(repo[p1].node())
   452                     repo.dirstate.endparentchange()
   460                     repo.dirstate.endparentchange()
   453                     newnode = None
   461                     newnode = None
   454                 # Update the state
   462                 # Update the state
   455                 if newnode is not None:
   463                 if newnode is not None:
   456                     state[rev] = repo[newnode].rev()
   464                     rbsrt.state[rev] = repo[newnode].rev()
   457                     ui.debug('rebased as %s\n' % short(newnode))
   465                     ui.debug('rebased as %s\n' % short(newnode))
   458                 else:
   466                 else:
   459                     if not collapsef:
   467                     if not collapsef:
   460                         ui.warn(_('note: rebase of %d:%s created no changes '
   468                         ui.warn(_('note: rebase of %d:%s created no changes '
   461                                   'to commit\n') % (rev, ctx))
   469                                   'to commit\n') % (rev, ctx))
   462                         skipped.add(rev)
   470                         skipped.add(rev)
   463                     state[rev] = p1
   471                     rbsrt.state[rev] = p1
   464                     ui.debug('next revision set to %s\n' % p1)
   472                     ui.debug('next revision set to %s\n' % p1)
   465             elif state[rev] == nullmerge:
   473             elif rbsrt.state[rev] == nullmerge:
   466                 ui.debug('ignoring null merge rebase of %s\n' % rev)
   474                 ui.debug('ignoring null merge rebase of %s\n' % rev)
   467             elif state[rev] == revignored:
   475             elif rbsrt.state[rev] == revignored:
   468                 ui.status(_('not rebasing ignored %s\n') % desc)
   476                 ui.status(_('not rebasing ignored %s\n') % desc)
   469             elif state[rev] == revprecursor:
   477             elif rbsrt.state[rev] == revprecursor:
   470                 targetctx = repo[obsoletenotrebased[rev]]
   478                 targetctx = repo[obsoletenotrebased[rev]]
   471                 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
   479                 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
   472                              targetctx.description().split('\n', 1)[0])
   480                              targetctx.description().split('\n', 1)[0])
   473                 msg = _('note: not rebasing %s, already in destination as %s\n')
   481                 msg = _('note: not rebasing %s, already in destination as %s\n')
   474                 ui.status(msg % (desc, desctarget))
   482                 ui.status(msg % (desc, desctarget))
   475             elif state[rev] == revpruned:
   483             elif rbsrt.state[rev] == revpruned:
   476                 msg = _('note: not rebasing %s, it has no successor\n')
   484                 msg = _('note: not rebasing %s, it has no successor\n')
   477                 ui.status(msg % desc)
   485                 ui.status(msg % desc)
   478             else:
   486             else:
   479                 ui.status(_('already rebased %s as %s\n') %
   487                 ui.status(_('already rebased %s as %s\n') %
   480                           (desc, repo[state[rev]]))
   488                           (desc, repo[rbsrt.state[rev]]))
   481 
   489 
   482         ui.progress(_('rebasing'), None)
   490         ui.progress(_('rebasing'), None)
   483         ui.note(_('rebase merging completed\n'))
   491         ui.note(_('rebase merging completed\n'))
   484 
   492 
   485         if collapsef and not keepopen:
   493         if collapsef and not keepopen:
   486             p1, p2, _base = defineparents(repo, min(state), target,
   494             p1, p2, _base = defineparents(repo, min(rbsrt.state), target,
   487                                           state, targetancestors,
   495                                           rbsrt.state, targetancestors,
   488                                           obsoletenotrebased)
   496                                           obsoletenotrebased)
   489             editopt = opts.get('edit')
   497             editopt = opts.get('edit')
   490             editform = 'rebase.collapse'
   498             editform = 'rebase.collapse'
   491             if collapsemsg:
   499             if collapsemsg:
   492                 commitmsg = collapsemsg
   500                 commitmsg = collapsemsg
   493             else:
   501             else:
   494                 commitmsg = 'Collapsed revision'
   502                 commitmsg = 'Collapsed revision'
   495                 for rebased in state:
   503                 for rebased in rbsrt.state:
   496                     if rebased not in skipped and state[rebased] > nullmerge:
   504                     if rebased not in skipped and\
       
   505                        rbsrt.state[rebased] > nullmerge:
   497                         commitmsg += '\n* %s' % repo[rebased].description()
   506                         commitmsg += '\n* %s' % repo[rebased].description()
   498                 editopt = True
   507                 editopt = True
   499             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
   508             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
   500             newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
   509             newnode = concludenode(repo, rev, p1, rbsrt.external,
       
   510                                    commitmsg=commitmsg,
   501                                    extrafn=extrafn, editor=editor,
   511                                    extrafn=extrafn, editor=editor,
   502                                    keepbranches=keepbranchesf,
   512                                    keepbranches=keepbranchesf,
   503                                    date=date)
   513                                    date=date)
   504             if newnode is None:
   514             if newnode is None:
   505                 newrev = target
   515                 newrev = target
   506             else:
   516             else:
   507                 newrev = repo[newnode].rev()
   517                 newrev = repo[newnode].rev()
   508             for oldrev in state.iterkeys():
   518             for oldrev in rbsrt.state.iterkeys():
   509                 if state[oldrev] > nullmerge:
   519                 if rbsrt.state[oldrev] > nullmerge:
   510                     state[oldrev] = newrev
   520                     rbsrt.state[oldrev] = newrev
   511 
   521 
   512         if 'qtip' in repo.tags():
   522         if 'qtip' in repo.tags():
   513             updatemq(repo, state, skipped, **opts)
   523             updatemq(repo, rbsrt.state, skipped, **opts)
   514 
   524 
   515         if currentbookmarks:
   525         if currentbookmarks:
   516             # Nodeids are needed to reset bookmarks
   526             # Nodeids are needed to reset bookmarks
   517             nstate = {}
   527             nstate = {}
   518             for k, v in state.iteritems():
   528             for k, v in rbsrt.state.iteritems():
   519                 if v > nullmerge:
   529                 if v > nullmerge:
   520                     nstate[repo[k].node()] = repo[v].node()
   530                     nstate[repo[k].node()] = repo[v].node()
   521             # XXX this is the same as dest.node() for the non-continue path --
   531             # XXX this is the same as dest.node() for the non-continue path --
   522             # this should probably be cleaned up
   532             # this should probably be cleaned up
   523             targetnode = repo[target].node()
   533             targetnode = repo[target].node()
   524 
   534 
   525         # restore original working directory
   535         # restore original working directory
   526         # (we do this before stripping)
   536         # (we do this before stripping)
   527         newwd = state.get(originalwd, originalwd)
   537         newwd = rbsrt.state.get(rbsrt.originalwd, rbsrt.originalwd)
   528         if newwd < 0:
   538         if newwd < 0:
   529             # original directory is a parent of rebase set root or ignored
   539             # original directory is a parent of rebase set root or ignored
   530             newwd = originalwd
   540             newwd = rbsrt.originalwd
   531         if newwd not in [c.rev() for c in repo[None].parents()]:
   541         if newwd not in [c.rev() for c in repo[None].parents()]:
   532             ui.note(_("update back to initial working directory parent\n"))
   542             ui.note(_("update back to initial working directory parent\n"))
   533             hg.updaterepo(repo, newwd, False)
   543             hg.updaterepo(repo, newwd, False)
   534 
   544 
   535         if not keepf:
   545         if not keepf:
   536             collapsedas = None
   546             collapsedas = None
   537             if collapsef:
   547             if collapsef:
   538                 collapsedas = newnode
   548                 collapsedas = newnode
   539             clearrebased(ui, repo, state, skipped, collapsedas)
   549             clearrebased(ui, repo, rbsrt.state, skipped, collapsedas)
   540 
   550 
   541         with repo.transaction('bookmark') as tr:
   551         with repo.transaction('bookmark') as tr:
   542             if currentbookmarks:
   552             if currentbookmarks:
   543                 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
   553                 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
   544                 if activebookmark not in repo._bookmarks:
   554                 if rbsrt.activebookmark not in repo._bookmarks:
   545                     # active bookmark was divergent one and has been deleted
   555                     # active bookmark was divergent one and has been deleted
   546                     activebookmark = None
   556                     rbsrt.activebookmark = None
   547         clearstatus(repo)
   557         clearstatus(repo)
   548         clearcollapsemsg(repo)
   558         clearcollapsemsg(repo)
   549 
   559 
   550         ui.note(_("rebase completed\n"))
   560         ui.note(_("rebase completed\n"))
   551         util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
   561         util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
   552         if skipped:
   562         if skipped:
   553             ui.note(_("%d revisions have been skipped\n") % len(skipped))
   563             ui.note(_("%d revisions have been skipped\n") % len(skipped))
   554 
   564 
   555         if (activebookmark and
   565         if (rbsrt.activebookmark and
   556             repo['.'].node() == repo._bookmarks[activebookmark]):
   566             repo['.'].node() == repo._bookmarks[rbsrt.activebookmark]):
   557                 bookmarks.activate(repo, activebookmark)
   567                 bookmarks.activate(repo, rbsrt.activebookmark)
   558 
   568 
   559     finally:
   569     finally:
   560         release(lock, wlock)
   570         release(lock, wlock)
   561 
   571 
   562 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
   572 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],