hgext/rebase.py
changeset 15267 3bfdfefea2fc
parent 15219 9d58569a8b92
child 15269 b12362ab13e7
equal deleted inserted replaced
15266:8bea39ca9acb 15267:3bfdfefea2fc
   163                         _('detach requires a revision to be specified'))
   163                         _('detach requires a revision to be specified'))
   164                 if basef:
   164                 if basef:
   165                     raise util.Abort(_('cannot specify a base with detach'))
   165                     raise util.Abort(_('cannot specify a base with detach'))
   166 
   166 
   167             cmdutil.bailifchanged(repo)
   167             cmdutil.bailifchanged(repo)
   168             result = buildstate(repo, destf, srcf, basef, detachf)
   168 
       
   169             if not destf:
       
   170                 # Destination defaults to the latest revision in the current branch
       
   171                 branch = repo[None].branch()
       
   172                 dest = repo[branch]
       
   173             else:
       
   174                 dest = repo[destf]
       
   175 
       
   176             if srcf:
       
   177                 revsetargs = ('(%s)::', srcf)
       
   178             else:
       
   179                 base = basef or '.'
       
   180                 revsetargs = ('(children(ancestor(%s, %d)) and ::(%s))::',
       
   181                              base, dest, base)
       
   182 
       
   183             rebaseset = [c.rev() for c in repo.set(*revsetargs)]
       
   184             if rebaseset:
       
   185                 result = buildstate(repo, dest, rebaseset, detachf)
       
   186             else:
       
   187                 repo.ui.debug(_('base is ancestor of destination'))
       
   188                 result = None
   169             if not result:
   189             if not result:
   170                 # Empty state built, nothing to rebase
   190                 # Empty state built, nothing to rebase
   171                 ui.status(_('nothing to rebase\n'))
   191                 ui.status(_('nothing to rebase\n'))
   172                 return 1
   192                 return 1
   173             else:
   193             else:
   505             repair.strip(repo.ui, repo, repo[strippoint].node())
   525             repair.strip(repo.ui, repo, repo[strippoint].node())
   506         clearstatus(repo)
   526         clearstatus(repo)
   507         repo.ui.warn(_('rebase aborted\n'))
   527         repo.ui.warn(_('rebase aborted\n'))
   508         return 0
   528         return 0
   509 
   529 
   510 def buildstate(repo, dest, src, base, detach):
   530 def buildstate(repo, dest, rebaseset, detach):
   511     'Define which revisions are going to be rebased and where'
   531     '''Define which revisions are going to be rebased and where
   512     targetancestors = set()
   532 
   513     detachset = set()
   533     repo: repo
   514 
   534     dest: context
   515     if not dest:
   535     rebaseset: set of rev
   516         # Destination defaults to the latest revision in the current branch
   536     detach: boolean'''
   517         branch = repo[None].branch()
       
   518         dest = repo[branch].rev()
       
   519     else:
       
   520         dest = repo[dest].rev()
       
   521 
   537 
   522     # This check isn't strictly necessary, since mq detects commits over an
   538     # This check isn't strictly necessary, since mq detects commits over an
   523     # applied patch. But it prevents messing up the working directory when
   539     # applied patch. But it prevents messing up the working directory when
   524     # a partially completed rebase is blocked by mq.
   540     # a partially completed rebase is blocked by mq.
   525     if 'qtip' in repo.tags() and (repo[dest].node() in
   541     if 'qtip' in repo.tags() and (dest.node() in
   526                             [s.node for s in repo.mq.applied]):
   542                             [s.node for s in repo.mq.applied]):
   527         raise util.Abort(_('cannot rebase onto an applied mq patch'))
   543         raise util.Abort(_('cannot rebase onto an applied mq patch'))
   528 
   544 
   529     if src:
   545     detachset = set()
   530         commonbase = repo[src].ancestor(repo[dest])
   546     roots = list(repo.set('roots(%ld)', rebaseset))
   531         if commonbase == repo[src]:
   547     if not roots:
   532             raise util.Abort(_('source is ancestor of destination'))
   548         raise util.Abort( _('no matching revisions'))
   533         if commonbase == repo[dest]:
   549     if len(roots) > 1:
   534             samebranch = repo[src].branch() == repo[dest].branch()
   550         raise util.Abort( _("can't rebase multiple roots"))
   535             if samebranch and repo[src] in repo[dest].children():
   551     root = roots[0]
   536                 raise util.Abort(_('source is a child of destination'))
   552 
   537             # rebase on ancestor, force detach
   553     commonbase = root.ancestor(dest)
   538             detach = True
   554     if commonbase == root:
   539         source = repo[src].rev()
   555         raise util.Abort(_('source is ancestor of destination'))
   540         if detach:
   556     if commonbase == dest:
   541             # We need to keep track of source's ancestors up to the common base
   557         samebranch = root.branch() == dest.branch()
   542             srcancestors = set(repo.changelog.ancestors(source))
   558         if samebranch and root in dest.children():
   543             baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
   559            repo.ui.debug(_('source is a child of destination'))
   544             detachset = srcancestors - baseancestors
   560            return None
   545             detachset.discard(commonbase.rev())
   561         # rebase on ancestor, force detach
   546     else:
   562         detach = True
   547         if base:
   563     if detach:
   548             cwd = repo[base].rev()
   564         detachset = [c.rev() for c in repo.set('::%d - ::%d - %d',
   549         else:
   565                                                 root, commonbase, root)]
   550             cwd = repo['.'].rev()
   566 
   551 
   567     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
   552         if cwd == dest:
   568     state = dict.fromkeys(rebaseset, nullrev)
   553             repo.ui.debug('source and destination are the same\n')
       
   554             return None
       
   555 
       
   556         targetancestors = set(repo.changelog.ancestors(dest))
       
   557         if cwd in targetancestors:
       
   558             repo.ui.debug('source is ancestor of destination\n')
       
   559             return None
       
   560 
       
   561         cwdancestors = set(repo.changelog.ancestors(cwd))
       
   562         if dest in cwdancestors:
       
   563             repo.ui.debug('source is descendant of destination\n')
       
   564             return None
       
   565 
       
   566         cwdancestors.add(cwd)
       
   567         rebasingbranch = cwdancestors - targetancestors
       
   568         source = min(rebasingbranch)
       
   569 
       
   570     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
       
   571     state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
       
   572     state.update(dict.fromkeys(detachset, nullmerge))
   569     state.update(dict.fromkeys(detachset, nullmerge))
   573     state[source] = nullrev
   570     return repo['.'].rev(), dest.rev(), state
   574     return repo['.'].rev(), repo[dest].rev(), state
       
   575 
   571 
   576 def pullrebase(orig, ui, repo, *args, **opts):
   572 def pullrebase(orig, ui, repo, *args, **opts):
   577     'Call rebase after pull if the latter has been invoked with --rebase'
   573     'Call rebase after pull if the latter has been invoked with --rebase'
   578     if opts.get('rebase'):
   574     if opts.get('rebase'):
   579         if opts.get('update'):
   575         if opts.get('update'):