eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/rebase.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # rebase.py - rebasing feature for mercurial
       
     2 #
       
     3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 '''command to move sets of revisions to a different ancestor
       
     9 
       
    10 This extension lets you rebase changesets in an existing Mercurial
       
    11 repository.
       
    12 
       
    13 For more information:
       
    14 http://mercurial.selenic.com/wiki/RebaseExtension
       
    15 '''
       
    16 
       
    17 from mercurial import hg, util, repair, merge, cmdutil, commands
       
    18 from mercurial import extensions, ancestor, copies, patch
       
    19 from mercurial.commands import templateopts
       
    20 from mercurial.node import nullrev
       
    21 from mercurial.lock import release
       
    22 from mercurial.i18n import _
       
    23 import os, errno
       
    24 
       
    25 nullmerge = -2
       
    26 
       
    27 def rebase(ui, repo, **opts):
       
    28     """move changeset (and descendants) to a different branch
       
    29 
       
    30     Rebase uses repeated merging to graft changesets from one part of
       
    31     history (the source) onto another (the destination). This can be
       
    32     useful for linearizing *local* changes relative to a master
       
    33     development tree.
       
    34 
       
    35     You should not rebase changesets that have already been shared
       
    36     with others. Doing so will force everybody else to perform the
       
    37     same rebase or they will end up with duplicated changesets after
       
    38     pulling in your rebased changesets.
       
    39 
       
    40     If you don't specify a destination changeset (``-d/--dest``),
       
    41     rebase uses the tipmost head of the current named branch as the
       
    42     destination. (The destination changeset is not modified by
       
    43     rebasing, but new changesets are added as its descendants.)
       
    44 
       
    45     You can specify which changesets to rebase in two ways: as a
       
    46     "source" changeset or as a "base" changeset. Both are shorthand
       
    47     for a topologically related set of changesets (the "source
       
    48     branch"). If you specify source (``-s/--source``), rebase will
       
    49     rebase that changeset and all of its descendants onto dest. If you
       
    50     specify base (``-b/--base``), rebase will select ancestors of base
       
    51     back to but not including the common ancestor with dest. Thus,
       
    52     ``-b`` is less precise but more convenient than ``-s``: you can
       
    53     specify any changeset in the source branch, and rebase will select
       
    54     the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
       
    55     uses the parent of the working directory as the base.
       
    56 
       
    57     By default, rebase recreates the changesets in the source branch
       
    58     as descendants of dest and then destroys the originals. Use
       
    59     ``--keep`` to preserve the original source changesets. Some
       
    60     changesets in the source branch (e.g. merges from the destination
       
    61     branch) may be dropped if they no longer contribute any change.
       
    62 
       
    63     One result of the rules for selecting the destination changeset
       
    64     and source branch is that, unlike ``merge``, rebase will do
       
    65     nothing if you are at the latest (tipmost) head of a named branch
       
    66     with two heads. You need to explicitly specify source and/or
       
    67     destination (or ``update`` to the other head, if it's the head of
       
    68     the intended source branch).
       
    69 
       
    70     If a rebase is interrupted to manually resolve a merge, it can be
       
    71     continued with --continue/-c or aborted with --abort/-a.
       
    72 
       
    73     Returns 0 on success, 1 if nothing to rebase.
       
    74     """
       
    75     originalwd = target = None
       
    76     external = nullrev
       
    77     state = {}
       
    78     skipped = set()
       
    79     targetancestors = set()
       
    80 
       
    81     lock = wlock = None
       
    82     try:
       
    83         lock = repo.lock()
       
    84         wlock = repo.wlock()
       
    85 
       
    86         # Validate input and define rebasing points
       
    87         destf = opts.get('dest', None)
       
    88         srcf = opts.get('source', None)
       
    89         basef = opts.get('base', None)
       
    90         contf = opts.get('continue')
       
    91         abortf = opts.get('abort')
       
    92         collapsef = opts.get('collapse', False)
       
    93         extrafn = opts.get('extrafn')
       
    94         keepf = opts.get('keep', False)
       
    95         keepbranchesf = opts.get('keepbranches', False)
       
    96         detachf = opts.get('detach', False)
       
    97         # keepopen is not meant for use on the command line, but by
       
    98         # other extensions
       
    99         keepopen = opts.get('keepopen', False)
       
   100 
       
   101         if contf or abortf:
       
   102             if contf and abortf:
       
   103                 raise util.Abort(_('cannot use both abort and continue'))
       
   104             if collapsef:
       
   105                 raise util.Abort(
       
   106                     _('cannot use collapse with continue or abort'))
       
   107             if detachf:
       
   108                 raise util.Abort(_('cannot use detach with continue or abort'))
       
   109             if srcf or basef or destf:
       
   110                 raise util.Abort(
       
   111                     _('abort and continue do not allow specifying revisions'))
       
   112 
       
   113             (originalwd, target, state, skipped, collapsef, keepf,
       
   114                                 keepbranchesf, external) = restorestatus(repo)
       
   115             if abortf:
       
   116                 return abort(repo, originalwd, target, state)
       
   117         else:
       
   118             if srcf and basef:
       
   119                 raise util.Abort(_('cannot specify both a '
       
   120                                    'revision and a base'))
       
   121             if detachf:
       
   122                 if not srcf:
       
   123                     raise util.Abort(
       
   124                         _('detach requires a revision to be specified'))
       
   125                 if basef:
       
   126                     raise util.Abort(_('cannot specify a base with detach'))
       
   127 
       
   128             cmdutil.bail_if_changed(repo)
       
   129             result = buildstate(repo, destf, srcf, basef, detachf)
       
   130             if not result:
       
   131                 # Empty state built, nothing to rebase
       
   132                 ui.status(_('nothing to rebase\n'))
       
   133                 return 1
       
   134             else:
       
   135                 originalwd, target, state = result
       
   136                 if collapsef:
       
   137                     targetancestors = set(repo.changelog.ancestors(target))
       
   138                     external = checkexternal(repo, state, targetancestors)
       
   139 
       
   140         if keepbranchesf:
       
   141             if extrafn:
       
   142                 raise util.Abort(_('cannot use both keepbranches and extrafn'))
       
   143             def extrafn(ctx, extra):
       
   144                 extra['branch'] = ctx.branch()
       
   145 
       
   146         # Rebase
       
   147         if not targetancestors:
       
   148             targetancestors = set(repo.changelog.ancestors(target))
       
   149             targetancestors.add(target)
       
   150 
       
   151         sortedstate = sorted(state)
       
   152         total = len(sortedstate)
       
   153         pos = 0
       
   154         for rev in sortedstate:
       
   155             pos += 1
       
   156             if state[rev] == -1:
       
   157                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
       
   158                             _('changesets'), total)
       
   159                 storestatus(repo, originalwd, target, state, collapsef, keepf,
       
   160                                                     keepbranchesf, external)
       
   161                 p1, p2 = defineparents(repo, rev, target, state,
       
   162                                                         targetancestors)
       
   163                 if len(repo.parents()) == 2:
       
   164                     repo.ui.debug('resuming interrupted rebase\n')
       
   165                 else:
       
   166                     stats = rebasenode(repo, rev, p1, p2, state)
       
   167                     if stats and stats[3] > 0:
       
   168                         raise util.Abort(_('unresolved conflicts (see hg '
       
   169                                     'resolve, then hg rebase --continue)'))
       
   170                 updatedirstate(repo, rev, target, p2)
       
   171                 if not collapsef:
       
   172                     newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
       
   173                 else:
       
   174                     # Skip commit if we are collapsing
       
   175                     repo.dirstate.setparents(repo[p1].node())
       
   176                     newrev = None
       
   177                 # Update the state
       
   178                 if newrev is not None:
       
   179                     state[rev] = repo[newrev].rev()
       
   180                 else:
       
   181                     if not collapsef:
       
   182                         ui.note(_('no changes, revision %d skipped\n') % rev)
       
   183                         ui.debug('next revision set to %s\n' % p1)
       
   184                         skipped.add(rev)
       
   185                     state[rev] = p1
       
   186 
       
   187         ui.progress(_('rebasing'), None)
       
   188         ui.note(_('rebase merging completed\n'))
       
   189 
       
   190         if collapsef and not keepopen:
       
   191             p1, p2 = defineparents(repo, min(state), target,
       
   192                                                         state, targetancestors)
       
   193             commitmsg = 'Collapsed revision'
       
   194             for rebased in state:
       
   195                 if rebased not in skipped and state[rebased] != nullmerge:
       
   196                     commitmsg += '\n* %s' % repo[rebased].description()
       
   197             commitmsg = ui.edit(commitmsg, repo.ui.username())
       
   198             newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
       
   199                                   extrafn=extrafn)
       
   200 
       
   201         if 'qtip' in repo.tags():
       
   202             updatemq(repo, state, skipped, **opts)
       
   203 
       
   204         if not keepf:
       
   205             # Remove no more useful revisions
       
   206             rebased = [rev for rev in state if state[rev] != nullmerge]
       
   207             if rebased:
       
   208                 if set(repo.changelog.descendants(min(rebased))) - set(state):
       
   209                     ui.warn(_("warning: new changesets detected "
       
   210                               "on source branch, not stripping\n"))
       
   211                 else:
       
   212                     # backup the old csets by default
       
   213                     repair.strip(ui, repo, repo[min(rebased)].node(), "all")
       
   214 
       
   215         clearstatus(repo)
       
   216         ui.note(_("rebase completed\n"))
       
   217         if os.path.exists(repo.sjoin('undo')):
       
   218             util.unlink(repo.sjoin('undo'))
       
   219         if skipped:
       
   220             ui.note(_("%d revisions have been skipped\n") % len(skipped))
       
   221     finally:
       
   222         release(lock, wlock)
       
   223 
       
   224 def rebasemerge(repo, rev, first=False):
       
   225     'return the correct ancestor'
       
   226     oldancestor = ancestor.ancestor
       
   227 
       
   228     def newancestor(a, b, pfunc):
       
   229         if b == rev:
       
   230             return repo[rev].parents()[0].rev()
       
   231         return oldancestor(a, b, pfunc)
       
   232 
       
   233     if not first:
       
   234         ancestor.ancestor = newancestor
       
   235     else:
       
   236         repo.ui.debug("first revision, do not change ancestor\n")
       
   237     try:
       
   238         stats = merge.update(repo, rev, True, True, False)
       
   239         return stats
       
   240     finally:
       
   241         ancestor.ancestor = oldancestor
       
   242 
       
   243 def checkexternal(repo, state, targetancestors):
       
   244     """Check whether one or more external revisions need to be taken in
       
   245     consideration. In the latter case, abort.
       
   246     """
       
   247     external = nullrev
       
   248     source = min(state)
       
   249     for rev in state:
       
   250         if rev == source:
       
   251             continue
       
   252         # Check externals and fail if there are more than one
       
   253         for p in repo[rev].parents():
       
   254             if (p.rev() not in state
       
   255                         and p.rev() not in targetancestors):
       
   256                 if external != nullrev:
       
   257                     raise util.Abort(_('unable to collapse, there is more '
       
   258                             'than one external parent'))
       
   259                 external = p.rev()
       
   260     return external
       
   261 
       
   262 def updatedirstate(repo, rev, p1, p2):
       
   263     """Keep track of renamed files in the revision that is going to be rebased
       
   264     """
       
   265     # Here we simulate the copies and renames in the source changeset
       
   266     cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
       
   267     m1 = repo[rev].manifest()
       
   268     m2 = repo[p1].manifest()
       
   269     for k, v in cop.iteritems():
       
   270         if k in m1:
       
   271             if v in m1 or v in m2:
       
   272                 repo.dirstate.copy(v, k)
       
   273                 if v in m2 and v not in m1:
       
   274                     repo.dirstate.remove(v)
       
   275 
       
   276 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
       
   277     'Commit the changes and store useful information in extra'
       
   278     try:
       
   279         repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
       
   280         ctx = repo[rev]
       
   281         if commitmsg is None:
       
   282             commitmsg = ctx.description()
       
   283         extra = {'rebase_source': ctx.hex()}
       
   284         if extrafn:
       
   285             extrafn(ctx, extra)
       
   286         # Commit might fail if unresolved files exist
       
   287         newrev = repo.commit(text=commitmsg, user=ctx.user(),
       
   288                              date=ctx.date(), extra=extra)
       
   289         repo.dirstate.setbranch(repo[newrev].branch())
       
   290         return newrev
       
   291     except util.Abort:
       
   292         # Invalidate the previous setparents
       
   293         repo.dirstate.invalidate()
       
   294         raise
       
   295 
       
   296 def rebasenode(repo, rev, p1, p2, state):
       
   297     'Rebase a single revision'
       
   298     # Merge phase
       
   299     # Update to target and merge it with local
       
   300     if repo['.'].rev() != repo[p1].rev():
       
   301         repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
       
   302         merge.update(repo, p1, False, True, False)
       
   303     else:
       
   304         repo.ui.debug(" already in target\n")
       
   305     repo.dirstate.write()
       
   306     repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
       
   307     first = repo[rev].rev() == repo[min(state)].rev()
       
   308     stats = rebasemerge(repo, rev, first)
       
   309     return stats
       
   310 
       
   311 def defineparents(repo, rev, target, state, targetancestors):
       
   312     'Return the new parent relationship of the revision that will be rebased'
       
   313     parents = repo[rev].parents()
       
   314     p1 = p2 = nullrev
       
   315 
       
   316     P1n = parents[0].rev()
       
   317     if P1n in targetancestors:
       
   318         p1 = target
       
   319     elif P1n in state:
       
   320         if state[P1n] == nullmerge:
       
   321             p1 = target
       
   322         else:
       
   323             p1 = state[P1n]
       
   324     else: # P1n external
       
   325         p1 = target
       
   326         p2 = P1n
       
   327 
       
   328     if len(parents) == 2 and parents[1].rev() not in targetancestors:
       
   329         P2n = parents[1].rev()
       
   330         # interesting second parent
       
   331         if P2n in state:
       
   332             if p1 == target: # P1n in targetancestors or external
       
   333                 p1 = state[P2n]
       
   334             else:
       
   335                 p2 = state[P2n]
       
   336         else: # P2n external
       
   337             if p2 != nullrev: # P1n external too => rev is a merged revision
       
   338                 raise util.Abort(_('cannot use revision %d as base, result '
       
   339                         'would have 3 parents') % rev)
       
   340             p2 = P2n
       
   341     repo.ui.debug(" future parents are %d and %d\n" %
       
   342                             (repo[p1].rev(), repo[p2].rev()))
       
   343     return p1, p2
       
   344 
       
   345 def isagitpatch(repo, patchname):
       
   346     'Return true if the given patch is in git format'
       
   347     mqpatch = os.path.join(repo.mq.path, patchname)
       
   348     for line in patch.linereader(file(mqpatch, 'rb')):
       
   349         if line.startswith('diff --git'):
       
   350             return True
       
   351     return False
       
   352 
       
   353 def updatemq(repo, state, skipped, **opts):
       
   354     'Update rebased mq patches - finalize and then import them'
       
   355     mqrebase = {}
       
   356     mq = repo.mq
       
   357     for p in mq.applied:
       
   358         rev = repo[p.node].rev()
       
   359         if rev in state:
       
   360             repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
       
   361                                         (rev, p.name))
       
   362             mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
       
   363 
       
   364     if mqrebase:
       
   365         mq.finish(repo, mqrebase.keys())
       
   366 
       
   367         # We must start import from the newest revision
       
   368         for rev in sorted(mqrebase, reverse=True):
       
   369             if rev not in skipped:
       
   370                 name, isgit = mqrebase[rev]
       
   371                 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
       
   372                 mq.qimport(repo, (), patchname=name, git=isgit,
       
   373                                 rev=[str(state[rev])])
       
   374         mq.save_dirty()
       
   375 
       
   376 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
       
   377                                                                 external):
       
   378     'Store the current status to allow recovery'
       
   379     f = repo.opener("rebasestate", "w")
       
   380     f.write(repo[originalwd].hex() + '\n')
       
   381     f.write(repo[target].hex() + '\n')
       
   382     f.write(repo[external].hex() + '\n')
       
   383     f.write('%d\n' % int(collapse))
       
   384     f.write('%d\n' % int(keep))
       
   385     f.write('%d\n' % int(keepbranches))
       
   386     for d, v in state.iteritems():
       
   387         oldrev = repo[d].hex()
       
   388         newrev = repo[v].hex()
       
   389         f.write("%s:%s\n" % (oldrev, newrev))
       
   390     f.close()
       
   391     repo.ui.debug('rebase status stored\n')
       
   392 
       
   393 def clearstatus(repo):
       
   394     'Remove the status files'
       
   395     if os.path.exists(repo.join("rebasestate")):
       
   396         util.unlink(repo.join("rebasestate"))
       
   397 
       
   398 def restorestatus(repo):
       
   399     'Restore a previously stored status'
       
   400     try:
       
   401         target = None
       
   402         collapse = False
       
   403         external = nullrev
       
   404         state = {}
       
   405         f = repo.opener("rebasestate")
       
   406         for i, l in enumerate(f.read().splitlines()):
       
   407             if i == 0:
       
   408                 originalwd = repo[l].rev()
       
   409             elif i == 1:
       
   410                 target = repo[l].rev()
       
   411             elif i == 2:
       
   412                 external = repo[l].rev()
       
   413             elif i == 3:
       
   414                 collapse = bool(int(l))
       
   415             elif i == 4:
       
   416                 keep = bool(int(l))
       
   417             elif i == 5:
       
   418                 keepbranches = bool(int(l))
       
   419             else:
       
   420                 oldrev, newrev = l.split(':')
       
   421                 state[repo[oldrev].rev()] = repo[newrev].rev()
       
   422         skipped = set()
       
   423         # recompute the set of skipped revs
       
   424         if not collapse:
       
   425             seen = set([target])
       
   426             for old, new in sorted(state.items()):
       
   427                 if new != nullrev and new in seen:
       
   428                     skipped.add(old)
       
   429                 seen.add(new)
       
   430         repo.ui.debug('computed skipped revs: %s\n' % skipped)
       
   431         repo.ui.debug('rebase status resumed\n')
       
   432         return (originalwd, target, state, skipped,
       
   433                 collapse, keep, keepbranches, external)
       
   434     except IOError, err:
       
   435         if err.errno != errno.ENOENT:
       
   436             raise
       
   437         raise util.Abort(_('no rebase in progress'))
       
   438 
       
   439 def abort(repo, originalwd, target, state):
       
   440     'Restore the repository to its original state'
       
   441     if set(repo.changelog.descendants(target)) - set(state.values()):
       
   442         repo.ui.warn(_("warning: new changesets detected on target branch, "
       
   443                                                     "can't abort\n"))
       
   444         return -1
       
   445     else:
       
   446         # Strip from the first rebased revision
       
   447         merge.update(repo, repo[originalwd].rev(), False, True, False)
       
   448         rebased = filter(lambda x: x > -1 and x != target, state.values())
       
   449         if rebased:
       
   450             strippoint = min(rebased)
       
   451             # no backup of rebased cset versions needed
       
   452             repair.strip(repo.ui, repo, repo[strippoint].node())
       
   453         clearstatus(repo)
       
   454         repo.ui.warn(_('rebase aborted\n'))
       
   455         return 0
       
   456 
       
   457 def buildstate(repo, dest, src, base, detach):
       
   458     'Define which revisions are going to be rebased and where'
       
   459     targetancestors = set()
       
   460     detachset = set()
       
   461 
       
   462     if not dest:
       
   463         # Destination defaults to the latest revision in the current branch
       
   464         branch = repo[None].branch()
       
   465         dest = repo[branch].rev()
       
   466     else:
       
   467         dest = repo[dest].rev()
       
   468 
       
   469     # This check isn't strictly necessary, since mq detects commits over an
       
   470     # applied patch. But it prevents messing up the working directory when
       
   471     # a partially completed rebase is blocked by mq.
       
   472     if 'qtip' in repo.tags() and (repo[dest].node() in
       
   473                             [s.node for s in repo.mq.applied]):
       
   474         raise util.Abort(_('cannot rebase onto an applied mq patch'))
       
   475 
       
   476     if src:
       
   477         commonbase = repo[src].ancestor(repo[dest])
       
   478         if commonbase == repo[src]:
       
   479             raise util.Abort(_('source is ancestor of destination'))
       
   480         if commonbase == repo[dest]:
       
   481             raise util.Abort(_('source is descendant of destination'))
       
   482         source = repo[src].rev()
       
   483         if detach:
       
   484             # We need to keep track of source's ancestors up to the common base
       
   485             srcancestors = set(repo.changelog.ancestors(source))
       
   486             baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
       
   487             detachset = srcancestors - baseancestors
       
   488             detachset.discard(commonbase.rev())
       
   489     else:
       
   490         if base:
       
   491             cwd = repo[base].rev()
       
   492         else:
       
   493             cwd = repo['.'].rev()
       
   494 
       
   495         if cwd == dest:
       
   496             repo.ui.debug('source and destination are the same\n')
       
   497             return None
       
   498 
       
   499         targetancestors = set(repo.changelog.ancestors(dest))
       
   500         if cwd in targetancestors:
       
   501             repo.ui.debug('source is ancestor of destination\n')
       
   502             return None
       
   503 
       
   504         cwdancestors = set(repo.changelog.ancestors(cwd))
       
   505         if dest in cwdancestors:
       
   506             repo.ui.debug('source is descendant of destination\n')
       
   507             return None
       
   508 
       
   509         cwdancestors.add(cwd)
       
   510         rebasingbranch = cwdancestors - targetancestors
       
   511         source = min(rebasingbranch)
       
   512 
       
   513     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
       
   514     state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
       
   515     state.update(dict.fromkeys(detachset, nullmerge))
       
   516     state[source] = nullrev
       
   517     return repo['.'].rev(), repo[dest].rev(), state
       
   518 
       
   519 def pullrebase(orig, ui, repo, *args, **opts):
       
   520     'Call rebase after pull if the latter has been invoked with --rebase'
       
   521     if opts.get('rebase'):
       
   522         if opts.get('update'):
       
   523             del opts['update']
       
   524             ui.debug('--update and --rebase are not compatible, ignoring '
       
   525                      'the update flag\n')
       
   526 
       
   527         cmdutil.bail_if_changed(repo)
       
   528         revsprepull = len(repo)
       
   529         origpostincoming = commands.postincoming
       
   530         def _dummy(*args, **kwargs):
       
   531             pass
       
   532         commands.postincoming = _dummy
       
   533         try:
       
   534             orig(ui, repo, *args, **opts)
       
   535         finally:
       
   536             commands.postincoming = origpostincoming
       
   537         revspostpull = len(repo)
       
   538         if revspostpull > revsprepull:
       
   539             rebase(ui, repo, **opts)
       
   540             branch = repo[None].branch()
       
   541             dest = repo[branch].rev()
       
   542             if dest != repo['.'].rev():
       
   543                 # there was nothing to rebase we force an update
       
   544                 hg.update(repo, dest)
       
   545     else:
       
   546         orig(ui, repo, *args, **opts)
       
   547 
       
   548 def uisetup(ui):
       
   549     'Replace pull with a decorator to provide --rebase option'
       
   550     entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
       
   551     entry[1].append(('', 'rebase', None,
       
   552                      _("rebase working directory to branch head"))
       
   553 )
       
   554 
       
   555 cmdtable = {
       
   556 "rebase":
       
   557         (rebase,
       
   558         [
       
   559         ('s', 'source', '',
       
   560          _('rebase from the specified changeset'), _('REV')),
       
   561         ('b', 'base', '',
       
   562          _('rebase from the base of the specified changeset '
       
   563            '(up to greatest common ancestor of base and dest)'),
       
   564          _('REV')),
       
   565         ('d', 'dest', '',
       
   566          _('rebase onto the specified changeset'), _('REV')),
       
   567         ('', 'collapse', False, _('collapse the rebased changesets')),
       
   568         ('', 'keep', False, _('keep original changesets')),
       
   569         ('', 'keepbranches', False, _('keep original branch names')),
       
   570         ('', 'detach', False, _('force detaching of source from its original '
       
   571                                 'branch')),
       
   572         ('c', 'continue', False, _('continue an interrupted rebase')),
       
   573         ('a', 'abort', False, _('abort an interrupted rebase'))] +
       
   574          templateopts,
       
   575         _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
       
   576           'hg rebase {-a|-c}'))
       
   577 }