hgext/rebase.py
changeset 10352 66d954e76ffb
parent 10351 38fe86fb16e3
child 10413 e433002acb05
equal deleted inserted replaced
10351:38fe86fb16e3 10352:66d954e76ffb
    19 from mercurial.commands import templateopts
    19 from mercurial.commands import templateopts
    20 from mercurial.node import nullrev
    20 from mercurial.node import nullrev
    21 from mercurial.lock import release
    21 from mercurial.lock import release
    22 from mercurial.i18n import _
    22 from mercurial.i18n import _
    23 import os, errno
    23 import os, errno
       
    24 
       
    25 nullmerge = -2
    24 
    26 
    25 def rebase(ui, repo, **opts):
    27 def rebase(ui, repo, **opts):
    26     """move changeset (and descendants) to a different branch
    28     """move changeset (and descendants) to a different branch
    27 
    29 
    28     Rebase uses repeated merging to graft changesets from one part of
    30     Rebase uses repeated merging to graft changesets from one part of
    51         abortf = opts.get('abort')
    53         abortf = opts.get('abort')
    52         collapsef = opts.get('collapse', False)
    54         collapsef = opts.get('collapse', False)
    53         extrafn = opts.get('extrafn')
    55         extrafn = opts.get('extrafn')
    54         keepf = opts.get('keep', False)
    56         keepf = opts.get('keep', False)
    55         keepbranchesf = opts.get('keepbranches', False)
    57         keepbranchesf = opts.get('keepbranches', False)
       
    58         detachf = opts.get('detach', False)
    56 
    59 
    57         if contf or abortf:
    60         if contf or abortf:
    58             if contf and abortf:
    61             if contf and abortf:
    59                 raise error.ParseError('rebase',
    62                 raise error.ParseError('rebase',
    60                                        _('cannot use both abort and continue'))
    63                                        _('cannot use both abort and continue'))
    61             if collapsef:
    64             if collapsef:
    62                 raise error.ParseError(
    65                 raise error.ParseError(
    63                     'rebase', _('cannot use collapse with continue or abort'))
    66                     'rebase', _('cannot use collapse with continue or abort'))
       
    67 
       
    68             if detachf:
       
    69                 raise error.ParseError(
       
    70                     'rebase', _('cannot use detach with continue or abort'))
    64 
    71 
    65             if srcf or basef or destf:
    72             if srcf or basef or destf:
    66                 raise error.ParseError('rebase',
    73                 raise error.ParseError('rebase',
    67                     _('abort and continue do not allow specifying revisions'))
    74                     _('abort and continue do not allow specifying revisions'))
    68 
    75 
    73                 return
    80                 return
    74         else:
    81         else:
    75             if srcf and basef:
    82             if srcf and basef:
    76                 raise error.ParseError('rebase', _('cannot specify both a '
    83                 raise error.ParseError('rebase', _('cannot specify both a '
    77                                                    'revision and a base'))
    84                                                    'revision and a base'))
       
    85             if detachf:
       
    86                 if not srcf:
       
    87                     raise error.ParseError(
       
    88                       'rebase', _('detach requires a revision to be specified'))
       
    89                 if basef:
       
    90                     raise error.ParseError(
       
    91                         'rebase', _('cannot specify a base with detach'))
       
    92 
    78             cmdutil.bail_if_changed(repo)
    93             cmdutil.bail_if_changed(repo)
    79             result = buildstate(repo, destf, srcf, basef)
    94             result = buildstate(repo, destf, srcf, basef, detachf)
    80             if not result:
    95             if not result:
    81                 # Empty state built, nothing to rebase
    96                 # Empty state built, nothing to rebase
    82                 ui.status(_('nothing to rebase\n'))
    97                 ui.status(_('nothing to rebase\n'))
    83                 return
    98                 return
    84             else:
    99             else:
   138         if collapsef:
   153         if collapsef:
   139             p1, p2 = defineparents(repo, min(state), target,
   154             p1, p2 = defineparents(repo, min(state), target,
   140                                                         state, targetancestors)
   155                                                         state, targetancestors)
   141             commitmsg = 'Collapsed revision'
   156             commitmsg = 'Collapsed revision'
   142             for rebased in state:
   157             for rebased in state:
   143                 if rebased not in skipped:
   158                 if rebased not in skipped and state[rebased] != nullmerge:
   144                     commitmsg += '\n* %s' % repo[rebased].description()
   159                     commitmsg += '\n* %s' % repo[rebased].description()
   145             commitmsg = ui.edit(commitmsg, repo.ui.username())
   160             commitmsg = ui.edit(commitmsg, repo.ui.username())
   146             concludenode(repo, rev, p1, external, commitmsg=commitmsg,
   161             newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
   147                                                     extra=extrafn)
   162                                                     extra=extrafn)
   148 
   163 
   149         if 'qtip' in repo.tags():
   164         if 'qtip' in repo.tags():
   150             updatemq(repo, state, skipped, **opts)
   165             updatemq(repo, state, skipped, **opts)
   151 
   166 
   152         if not keepf:
   167         if not keepf:
   153             # Remove no more useful revisions
   168             # Remove no more useful revisions
   154             if set(repo.changelog.descendants(min(state))) - set(state):
   169             rebased = [rev for rev in state if state[rev] != nullmerge]
   155                 ui.warn(_("warning: new changesets detected on source branch, "
   170             if rebased:
   156                                                         "not stripping\n"))
   171                 if set(repo.changelog.descendants(min(rebased))) - set(state):
   157             else:
   172                     ui.warn(_("warning: new changesets detected on source branch, "
   158                 repair.strip(ui, repo, repo[min(state)].node(), "strip")
   173                                                             "not stripping\n"))
       
   174                 else:
       
   175                     repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
   159 
   176 
   160         clearstatus(repo)
   177         clearstatus(repo)
   161         ui.status(_("rebase completed\n"))
   178         ui.status(_("rebase completed\n"))
   162         if os.path.exists(repo.sjoin('undo')):
   179         if os.path.exists(repo.sjoin('undo')):
   163             util.unlink(repo.sjoin('undo'))
   180             util.unlink(repo.sjoin('undo'))
   258 
   275 
   259     P1n = parents[0].rev()
   276     P1n = parents[0].rev()
   260     if P1n in targetancestors:
   277     if P1n in targetancestors:
   261         p1 = target
   278         p1 = target
   262     elif P1n in state:
   279     elif P1n in state:
   263         p1 = state[P1n]
   280         if state[P1n] == nullmerge:
       
   281             p1 = target
       
   282         else:
       
   283             p1 = state[P1n]
   264     else: # P1n external
   284     else: # P1n external
   265         p1 = target
   285         p1 = target
   266         p2 = P1n
   286         p2 = P1n
   267 
   287 
   268     if len(parents) == 2 and parents[1].rev() not in targetancestors:
   288     if len(parents) == 2 and parents[1].rev() not in targetancestors:
   377             strippoint = min(rebased)
   397             strippoint = min(rebased)
   378             repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
   398             repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
   379         clearstatus(repo)
   399         clearstatus(repo)
   380         repo.ui.status(_('rebase aborted\n'))
   400         repo.ui.status(_('rebase aborted\n'))
   381 
   401 
   382 def buildstate(repo, dest, src, base):
   402 def buildstate(repo, dest, src, base, detach):
   383     'Define which revisions are going to be rebased and where'
   403     'Define which revisions are going to be rebased and where'
   384     targetancestors = set()
   404     targetancestors = set()
       
   405     detachset = set()
   385 
   406 
   386     if not dest:
   407     if not dest:
   387         # Destination defaults to the latest revision in the current branch
   408         # Destination defaults to the latest revision in the current branch
   388         branch = repo[None].branch()
   409         branch = repo[None].branch()
   389         dest = repo[branch].rev()
   410         dest = repo[branch].rev()
   398         if commonbase == repo[src]:
   419         if commonbase == repo[src]:
   399             raise util.Abort(_('source is ancestor of destination'))
   420             raise util.Abort(_('source is ancestor of destination'))
   400         if commonbase == repo[dest]:
   421         if commonbase == repo[dest]:
   401             raise util.Abort(_('source is descendant of destination'))
   422             raise util.Abort(_('source is descendant of destination'))
   402         source = repo[src].rev()
   423         source = repo[src].rev()
       
   424         if detach:
       
   425             # We need to keep track of source's ancestors up to the common base
       
   426             srcancestors = set(repo.changelog.ancestors(source))
       
   427             baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
       
   428             detachset = srcancestors - baseancestors
       
   429             detachset.remove(commonbase.rev())
   403     else:
   430     else:
   404         if base:
   431         if base:
   405             cwd = repo[base].rev()
   432             cwd = repo[base].rev()
   406         else:
   433         else:
   407             cwd = repo['.'].rev()
   434             cwd = repo['.'].rev()
   424         rebasingbranch = cwdancestors - targetancestors
   451         rebasingbranch = cwdancestors - targetancestors
   425         source = min(rebasingbranch)
   452         source = min(rebasingbranch)
   426 
   453 
   427     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
   454     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
   428     state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
   455     state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
       
   456     state.update(dict.fromkeys(detachset, nullmerge))
   429     state[source] = nullrev
   457     state[source] = nullrev
   430     return repo['.'].rev(), repo[dest].rev(), state
   458     return repo['.'].rev(), repo[dest].rev(), state
   431 
   459 
   432 def pullrebase(orig, ui, repo, *args, **opts):
   460 def pullrebase(orig, ui, repo, *args, **opts):
   433     'Call rebase after pull if the latter has been invoked with --rebase'
   461     'Call rebase after pull if the latter has been invoked with --rebase'
   466         ('b', 'base', '', _('rebase from the base of a given revision')),
   494         ('b', 'base', '', _('rebase from the base of a given revision')),
   467         ('d', 'dest', '', _('rebase onto a given revision')),
   495         ('d', 'dest', '', _('rebase onto a given revision')),
   468         ('', 'collapse', False, _('collapse the rebased changesets')),
   496         ('', 'collapse', False, _('collapse the rebased changesets')),
   469         ('', 'keep', False, _('keep original changesets')),
   497         ('', 'keep', False, _('keep original changesets')),
   470         ('', 'keepbranches', False, _('keep original branch names')),
   498         ('', 'keepbranches', False, _('keep original branch names')),
       
   499         ('', 'detach', False, _('force detaching of source from its original '
       
   500                                 'branch')),
   471         ('c', 'continue', False, _('continue an interrupted rebase')),
   501         ('c', 'continue', False, _('continue an interrupted rebase')),
   472         ('a', 'abort', False, _('abort an interrupted rebase')),] +
   502         ('a', 'abort', False, _('abort an interrupted rebase')),] +
   473          templateopts,
   503          templateopts,
   474         _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
   504         _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] '
   475                             '[--keepbranches] | [-c] | [-a]')),
   505                         '[--keep] [--keepbranches] | [-c] | [-a]')),
   476 }
   506 }