hgext/rebase.py
changeset 33845 3ddbab49efcf
parent 33844 437e317d7913
child 33846 3b04a6ff625c
equal deleted inserted replaced
33844:437e317d7913 33845:3ddbab49efcf
    58 # The following constants are used throughout the rebase module. The ordering of
    58 # The following constants are used throughout the rebase module. The ordering of
    59 # their values must be maintained.
    59 # their values must be maintained.
    60 
    60 
    61 # Indicates that a revision needs to be rebased
    61 # Indicates that a revision needs to be rebased
    62 revtodo = -1
    62 revtodo = -1
    63 nullmerge = -2
       
    64 revignored = -3
       
    65 
    63 
    66 # legacy revstates no longer needed in current code
    64 # legacy revstates no longer needed in current code
    67 # -4: revprecursor, -5: revpruned
    65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
    68 legacystates = {'-4', '-5'}
    66 legacystates = {'-2', '-3', '-4', '-5'}
    69 
    67 
    70 cmdtable = {}
    68 cmdtable = {}
    71 command = registrar.command(cmdtable)
    69 command = registrar.command(cmdtable)
    72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
    70 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
    73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
    71 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   231                     activebookmark = l
   229                     activebookmark = l
   232                 else:
   230                 else:
   233                     oldrev, newrev = l.split(':')
   231                     oldrev, newrev = l.split(':')
   234                     if newrev in legacystates:
   232                     if newrev in legacystates:
   235                         continue
   233                         continue
   236                     if newrev in (str(nullmerge), str(revignored)):
       
   237                         state[repo[oldrev].rev()] = int(newrev)
       
   238                     elif newrev == nullid:
   234                     elif newrev == nullid:
   239                         state[repo[oldrev].rev()] = revtodo
   235                         state[repo[oldrev].rev()] = revtodo
   240                         # Legacy compat special case
   236                         # Legacy compat special case
   241                     else:
   237                     else:
   242                         state[repo[oldrev].rev()] = repo[newrev].rev()
   238                         state[repo[oldrev].rev()] = repo[newrev].rev()
   437                         ui.warn(_('note: rebase of %d:%s created no changes '
   433                         ui.warn(_('note: rebase of %d:%s created no changes '
   438                                   'to commit\n') % (rev, ctx))
   434                                   'to commit\n') % (rev, ctx))
   439                         self.skipped.add(rev)
   435                         self.skipped.add(rev)
   440                     self.state[rev] = p1
   436                     self.state[rev] = p1
   441                     ui.debug('next revision set to %s\n' % p1)
   437                     ui.debug('next revision set to %s\n' % p1)
   442             elif self.state[rev] == nullmerge:
       
   443                 pass
       
   444             elif self.state[rev] == revignored:
       
   445                 pass
       
   446             else:
   438             else:
   447                 ui.status(_('already rebased %s as %s\n') %
   439                 ui.status(_('already rebased %s as %s\n') %
   448                           (desc, repo[self.state[rev]]))
   440                           (desc, repo[self.state[rev]]))
   449 
   441 
   450         ui.progress(_('rebasing'), None)
   442         ui.progress(_('rebasing'), None)
   461                 commitmsg = self.collapsemsg
   453                 commitmsg = self.collapsemsg
   462             else:
   454             else:
   463                 commitmsg = 'Collapsed revision'
   455                 commitmsg = 'Collapsed revision'
   464                 for rebased in sorted(self.state):
   456                 for rebased in sorted(self.state):
   465                     if rebased not in self.skipped and\
   457                     if rebased not in self.skipped and\
   466                        self.state[rebased] > nullmerge:
   458                        self.state[rebased] >= revtodo:
   467                         commitmsg += '\n* %s' % repo[rebased].description()
   459                         commitmsg += '\n* %s' % repo[rebased].description()
   468                 editopt = True
   460                 editopt = True
   469             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
   461             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
   470             revtoreuse = max(self.state)
   462             revtoreuse = max(self.state)
   471 
   463 
   482             if newnode is None:
   474             if newnode is None:
   483                 newrev = self.dest
   475                 newrev = self.dest
   484             else:
   476             else:
   485                 newrev = repo[newnode].rev()
   477                 newrev = repo[newnode].rev()
   486             for oldrev in self.state.iterkeys():
   478             for oldrev in self.state.iterkeys():
   487                 if self.state[oldrev] > nullmerge:
   479                 if self.state[oldrev] >= revtodo:
   488                     self.state[oldrev] = newrev
   480                     self.state[oldrev] = newrev
   489 
   481 
   490         if 'qtip' in repo.tags():
   482         if 'qtip' in repo.tags():
   491             updatemq(repo, self.state, self.skipped, **opts)
   483             updatemq(repo, self.state, self.skipped, **opts)
   492 
   484 
  1316     roots = list(repo.set('roots(%ld)', rebaseset))
  1308     roots = list(repo.set('roots(%ld)', rebaseset))
  1317     if not roots:
  1309     if not roots:
  1318         raise error.Abort(_('no matching revisions'))
  1310         raise error.Abort(_('no matching revisions'))
  1319     roots.sort()
  1311     roots.sort()
  1320     state = dict.fromkeys(rebaseset, revtodo)
  1312     state = dict.fromkeys(rebaseset, revtodo)
  1321     detachset = set()
       
  1322     emptyrebase = True
  1313     emptyrebase = True
  1323     for root in roots:
  1314     for root in roots:
  1324         commonbase = root.ancestor(dest)
  1315         commonbase = root.ancestor(dest)
  1325         if commonbase == root:
  1316         if commonbase == root:
  1326             raise error.Abort(_('source is ancestor of destination'))
  1317             raise error.Abort(_('source is ancestor of destination'))
  1338                 repo.ui.debug('source is a child of destination\n')
  1329                 repo.ui.debug('source is a child of destination\n')
  1339                 continue
  1330                 continue
  1340 
  1331 
  1341         emptyrebase = False
  1332         emptyrebase = False
  1342         repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
  1333         repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
  1343         # Rebase tries to turn <dest> into a parent of <root> while
       
  1344         # preserving the number of parents of rebased changesets:
       
  1345         #
       
  1346         # - A changeset with a single parent will always be rebased as a
       
  1347         #   changeset with a single parent.
       
  1348         #
       
  1349         # - A merge will be rebased as merge unless its parents are both
       
  1350         #   ancestors of <dest> or are themselves in the rebased set and
       
  1351         #   pruned while rebased.
       
  1352         #
       
  1353         # If one parent of <root> is an ancestor of <dest>, the rebased
       
  1354         # version of this parent will be <dest>. This is always true with
       
  1355         # --base option.
       
  1356         #
       
  1357         # Otherwise, we need to *replace* the original parents with
       
  1358         # <dest>. This "detaches" the rebased set from its former location
       
  1359         # and rebases it onto <dest>. Changes introduced by ancestors of
       
  1360         # <root> not common with <dest> (the detachset, marked as
       
  1361         # nullmerge) are "removed" from the rebased changesets.
       
  1362         #
       
  1363         # - If <root> has a single parent, set it to <dest>.
       
  1364         #
       
  1365         # - If <root> is a merge, we cannot decide which parent to
       
  1366         #   replace, the rebase operation is not clearly defined.
       
  1367         #
       
  1368         # The table below sums up this behavior:
       
  1369         #
       
  1370         # +------------------+----------------------+-------------------------+
       
  1371         # |                  |     one parent       |  merge                  |
       
  1372         # +------------------+----------------------+-------------------------+
       
  1373         # | parent in        | new parent is <dest> | parents in ::<dest> are |
       
  1374         # | ::<dest>         |                      | remapped to <dest>      |
       
  1375         # +------------------+----------------------+-------------------------+
       
  1376         # | unrelated source | new parent is <dest> | ambiguous, abort        |
       
  1377         # +------------------+----------------------+-------------------------+
       
  1378         #
       
  1379         # The actual abort is handled by `defineparents`
       
  1380         if len(root.parents()) <= 1:
       
  1381             # ancestors of <root> not ancestors of <dest>
       
  1382             detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
       
  1383                                                             [root.rev()]))
       
  1384     if emptyrebase:
  1334     if emptyrebase:
  1385         return None
  1335         return None
  1386     for rev in sorted(state):
  1336     for rev in sorted(state):
  1387         parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
  1337         parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
  1388         # if all parents of this revision are done, then so is this revision
  1338         # if all parents of this revision are done, then so is this revision
  1389         if parents and all((state.get(p) == p for p in parents)):
  1339         if parents and all((state.get(p) == p for p in parents)):
  1390             state[rev] = rev
  1340             state[rev] = rev
  1391     for r in detachset:
       
  1392         if r not in state:
       
  1393             state[r] = nullmerge
       
  1394     if len(roots) > 1:
       
  1395         # If we have multiple roots, we may have "hole" in the rebase set.
       
  1396         # Rebase roots that descend from those "hole" should not be detached as
       
  1397         # other root are. We use the special `revignored` to inform rebase that
       
  1398         # the revision should be ignored but that `defineparents` should search
       
  1399         # a rebase destination that make sense regarding rebased topology.
       
  1400         rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
       
  1401         for ignored in set(rebasedomain) - set(rebaseset):
       
  1402             state[ignored] = revignored
       
  1403     unfi = repo.unfiltered()
  1341     unfi = repo.unfiltered()
  1404     for r in obsoletenotrebased:
  1342     for r in obsoletenotrebased:
  1405         desc = _ctxdesc(unfi[r])
  1343         desc = _ctxdesc(unfi[r])
  1406         succ = obsoletenotrebased[r]
  1344         succ = obsoletenotrebased[r]
  1407         if succ is None:
  1345         if succ is None: