hgext/rebase.py
changeset 17005 50f434510da6
parent 16867 1093ad1e8903
child 17028 efd2e14f7235
equal deleted inserted replaced
17004:a1d86396d8c1 17005:50f434510da6
    46     ('e', 'edit', False, _('invoke editor on commit messages')),
    46     ('e', 'edit', False, _('invoke editor on commit messages')),
    47     ('l', 'logfile', '',
    47     ('l', 'logfile', '',
    48      _('read collapse commit message from file'), _('FILE')),
    48      _('read collapse commit message from file'), _('FILE')),
    49     ('', 'keep', False, _('keep original changesets')),
    49     ('', 'keep', False, _('keep original changesets')),
    50     ('', 'keepbranches', False, _('keep original branch names')),
    50     ('', 'keepbranches', False, _('keep original branch names')),
    51     ('D', 'detach', False, _('force detaching of source from its original '
    51     ('D', 'detach', False, _('(DEPRECATED)')),
    52                             'branch')),
       
    53     ('t', 'tool', '', _('specify merge tool')),
    52     ('t', 'tool', '', _('specify merge tool')),
    54     ('c', 'continue', False, _('continue an interrupted rebase')),
    53     ('c', 'continue', False, _('continue an interrupted rebase')),
    55     ('a', 'abort', False, _('abort an interrupted rebase'))] +
    54     ('a', 'abort', False, _('abort an interrupted rebase'))] +
    56      templateopts,
    55      templateopts,
    57     _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
    56     _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
   129         collapsef = opts.get('collapse', False)
   128         collapsef = opts.get('collapse', False)
   130         collapsemsg = cmdutil.logmessage(ui, opts)
   129         collapsemsg = cmdutil.logmessage(ui, opts)
   131         extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
   130         extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
   132         keepf = opts.get('keep', False)
   131         keepf = opts.get('keep', False)
   133         keepbranchesf = opts.get('keepbranches', False)
   132         keepbranchesf = opts.get('keepbranches', False)
   134         detachf = opts.get('detach', False)
       
   135         # keepopen is not meant for use on the command line, but by
   133         # keepopen is not meant for use on the command line, but by
   136         # other extensions
   134         # other extensions
   137         keepopen = opts.get('keepopen', False)
   135         keepopen = opts.get('keepopen', False)
   138 
   136 
   139         if collapsemsg and not collapsef:
   137         if collapsemsg and not collapsef:
   144             if contf and abortf:
   142             if contf and abortf:
   145                 raise util.Abort(_('cannot use both abort and continue'))
   143                 raise util.Abort(_('cannot use both abort and continue'))
   146             if collapsef:
   144             if collapsef:
   147                 raise util.Abort(
   145                 raise util.Abort(
   148                     _('cannot use collapse with continue or abort'))
   146                     _('cannot use collapse with continue or abort'))
   149             if detachf:
       
   150                 raise util.Abort(_('cannot use detach with continue or abort'))
       
   151             if srcf or basef or destf:
   147             if srcf or basef or destf:
   152                 raise util.Abort(
   148                 raise util.Abort(
   153                     _('abort and continue do not allow specifying revisions'))
   149                     _('abort and continue do not allow specifying revisions'))
   154             if opts.get('tool', False):
   150             if opts.get('tool', False):
   155                 ui.warn(_('tool option will be ignored\n'))
   151                 ui.warn(_('tool option will be ignored\n'))
   166                 raise util.Abort(_('cannot specify both a '
   162                 raise util.Abort(_('cannot specify both a '
   167                                    'revision and a base'))
   163                                    'revision and a base'))
   168             if revf and srcf:
   164             if revf and srcf:
   169                 raise util.Abort(_('cannot specify both a '
   165                 raise util.Abort(_('cannot specify both a '
   170                                    'revision and a source'))
   166                                    'revision and a source'))
   171             if detachf:
       
   172                 if not (srcf or revf):
       
   173                     raise util.Abort(
       
   174                         _('detach requires a revision to be specified'))
       
   175                 if basef:
       
   176                     raise util.Abort(_('cannot specify a base with detach'))
       
   177 
   167 
   178             cmdutil.bailifchanged(repo)
   168             cmdutil.bailifchanged(repo)
   179 
   169 
   180             if not destf:
   170             if not destf:
   181                 # Destination defaults to the latest revision in the
   171                 # Destination defaults to the latest revision in the
   213             elif not keepf and not repo[root].mutable():
   203             elif not keepf and not repo[root].mutable():
   214                 raise util.Abort(_("can't rebase immutable changeset %s")
   204                 raise util.Abort(_("can't rebase immutable changeset %s")
   215                                  % repo[root],
   205                                  % repo[root],
   216                                  hint=_('see hg help phases for details'))
   206                                  hint=_('see hg help phases for details'))
   217             else:
   207             else:
   218                 result = buildstate(repo, dest, rebaseset, detachf, collapsef)
   208                 result = buildstate(repo, dest, rebaseset, collapsef)
   219 
   209 
   220             if not result:
   210             if not result:
   221                 # Empty state built, nothing to rebase
   211                 # Empty state built, nothing to rebase
   222                 ui.status(_('nothing to rebase\n'))
   212                 ui.status(_('nothing to rebase\n'))
   223                 return 1
   213                 return 1
   590             repair.strip(repo.ui, repo, repo[strippoint].node())
   580             repair.strip(repo.ui, repo, repo[strippoint].node())
   591         clearstatus(repo)
   581         clearstatus(repo)
   592         repo.ui.warn(_('rebase aborted\n'))
   582         repo.ui.warn(_('rebase aborted\n'))
   593         return 0
   583         return 0
   594 
   584 
   595 def buildstate(repo, dest, rebaseset, detach, collapse):
   585 def buildstate(repo, dest, rebaseset, collapse):
   596     '''Define which revisions are going to be rebased and where
   586     '''Define which revisions are going to be rebased and where
   597 
   587 
   598     repo: repo
   588     repo: repo
   599     dest: context
   589     dest: context
   600     rebaseset: set of rev
   590     rebaseset: set of rev
   601     detach: boolean'''
   591     '''
   602 
   592 
   603     # This check isn't strictly necessary, since mq detects commits over an
   593     # This check isn't strictly necessary, since mq detects commits over an
   604     # applied patch. But it prevents messing up the working directory when
   594     # applied patch. But it prevents messing up the working directory when
   605     # a partially completed rebase is blocked by mq.
   595     # a partially completed rebase is blocked by mq.
   606     if 'qtip' in repo.tags() and (dest.node() in
   596     if 'qtip' in repo.tags() and (dest.node() in
   607                             [s.node for s in repo.mq.applied]):
   597                             [s.node for s in repo.mq.applied]):
   608         raise util.Abort(_('cannot rebase onto an applied mq patch'))
   598         raise util.Abort(_('cannot rebase onto an applied mq patch'))
   609 
   599 
   610     detachset = set()
       
   611     roots = list(repo.set('roots(%ld)', rebaseset))
   600     roots = list(repo.set('roots(%ld)', rebaseset))
   612     if not roots:
   601     if not roots:
   613         raise util.Abort(_('no matching revisions'))
   602         raise util.Abort(_('no matching revisions'))
   614     if len(roots) > 1:
   603     if len(roots) > 1:
   615         raise util.Abort(_("can't rebase multiple roots"))
   604         raise util.Abort(_("can't rebase multiple roots"))
   621     if commonbase == dest:
   610     if commonbase == dest:
   622         samebranch = root.branch() == dest.branch()
   611         samebranch = root.branch() == dest.branch()
   623         if not collapse and samebranch and root in dest.children():
   612         if not collapse and samebranch and root in dest.children():
   624             repo.ui.debug('source is a child of destination\n')
   613             repo.ui.debug('source is a child of destination\n')
   625             return None
   614             return None
   626         # rebase on ancestor, force detach
       
   627         detach = True
       
   628     if detach:
       
   629         detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
       
   630 
   615 
   631     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
   616     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
   632     state = dict.fromkeys(rebaseset, nullrev)
   617     state = dict.fromkeys(rebaseset, nullrev)
   633     state.update(dict.fromkeys(detachset, nullmerge))
   618     # Rebase tries to turn <dest> into a parent of <root> while
       
   619     # preserving the number of parents of rebased changesets:
       
   620     #
       
   621     # - A changeset with a single parent will always be rebased as a
       
   622     #   changeset with a single parent.
       
   623     #
       
   624     # - A merge will be rebased as merge unless its parents are both
       
   625     #   ancestors of <dest> or are themselves in the rebased set and
       
   626     #   pruned while rebased.
       
   627     #
       
   628     # If one parent of <root> is an ancestor of <dest>, the rebased
       
   629     # version of this parent will be <dest>. This is always true with
       
   630     # --base option.
       
   631     #
       
   632     # Otherwise, we need to *replace* the original parents with
       
   633     # <dest>. This "detaches" the rebased set from its former location
       
   634     # and rebases it onto <dest>. Changes introduced by ancestors of
       
   635     # <root> not common with <dest> (the detachset, marked as
       
   636     # nullmerge) are "removed" from the rebased changesets.
       
   637     #
       
   638     # - If <root> has a single parent, set it to <dest>.
       
   639     #
       
   640     # - If <root> is a merge, we cannot decide which parent to
       
   641     #   replace, the rebase operation is not clearly defined.
       
   642     #
       
   643     # The table below sums up this behavior:
       
   644     #
       
   645     # +--------------------+----------------------+-------------------------+
       
   646     # |                    |     one parent       |  merge                  |
       
   647     # +--------------------+----------------------+-------------------------+
       
   648     # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
       
   649     # |                    |                      | remapped to <dest>      |
       
   650     # +--------------------+----------------------+-------------------------+
       
   651     # | unrelated source   | new parent is <dest> | ambiguous, abort        |
       
   652     # +--------------------+----------------------+-------------------------+
       
   653     #
       
   654     # The actual abort is handled by `defineparents`
       
   655     if len(root.parents()) <= 1:
       
   656         # (strict) ancestors of <root> not ancestors of <dest>
       
   657         detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
       
   658         state.update(dict.fromkeys(detachset, nullmerge))
   634     return repo['.'].rev(), dest.rev(), state
   659     return repo['.'].rev(), dest.rev(), state
   635 
   660 
   636 def pullrebase(orig, ui, repo, *args, **opts):
   661 def pullrebase(orig, ui, repo, *args, **opts):
   637     'Call rebase after pull if the latter has been invoked with --rebase'
   662     'Call rebase after pull if the latter has been invoked with --rebase'
   638     if opts.get('rebase'):
   663     if opts.get('rebase'):