##// END OF EJS Templates
add blank line after copyright notices and after header
add blank line after copyright notices and after header

File last commit:

r8225:46293a0c default
r8228:eee2319c default
Show More
rebase.py
469 lines | 17.6 KiB | text/x-python | PythonLexer
Stefano Tortarolo
Add rebase extension
r6906 # rebase.py - rebasing feature for mercurial
#
# Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
Stefano Tortarolo
Add rebase extension
r6906
Dirkjan Ochtman
help: better documentation intro for a few extensions
r7127 '''move sets of revisions to a different ancestor
Stefano Tortarolo
Add rebase extension
r6906
Martin Geisler
rebase: word-wrap help texts at 70 characters
r7999 This extension lets you rebase changesets in an existing Mercurial
repository.
Stefano Tortarolo
Add rebase extension
r6906
For more information:
http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
'''
Matt Mackall
error: move ParseError
r7636 from mercurial import util, repair, merge, cmdutil, commands, error
Stefano Tortarolo
rebase: keep original mq patch format (Issue1574)...
r7955 from mercurial import extensions, ancestor, copies, patch
Stefano Tortarolo
Add rebase extension
r6906 from mercurial.commands import templateopts
from mercurial.node import nullrev
Ronny Pfannschmidt
switch lock releasing in the extensions from gc to explicit
r8112 from mercurial.lock import release
Stefano Tortarolo
Add rebase extension
r6906 from mercurial.i18n import _
import os, errno
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 def rebasemerge(repo, rev, first=False):
'return the correct ancestor'
oldancestor = ancestor.ancestor
Dirkjan Ochtman
kill some trailing spaces
r7298
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 def newancestor(a, b, pfunc):
ancestor.ancestor = oldancestor
if b == rev:
return repo[rev].parents()[0].rev()
return ancestor.ancestor(a, b, pfunc)
if not first:
ancestor.ancestor = newancestor
else:
Martin Geisler
lowercase ui.debug and assert output...
r7599 repo.ui.debug(_("first revision, do not change ancestor\n"))
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 stats = merge.update(repo, rev, True, True, False)
return stats
Stefano Tortarolo
Add rebase extension
r6906 def rebase(ui, repo, **opts):
"""move changeset (and descendants) to a different branch
Martin Geisler
rebase: word-wrap help texts at 70 characters
r7999 Rebase uses repeated merging to graft changesets from one part of
history onto another. This can be useful for linearizing local
changes relative to a master development tree.
Stefano Tortarolo
Add rebase extension
r6906
Martin Geisler
rebase: word-wrap help texts at 70 characters
r7999 If a rebase is interrupted to manually resolve a merge, it can be
Martin Geisler
help texts: write command line switches as -a/--abc
r8076 continued with --continue/-c or aborted with --abort/-a.
Stefano Tortarolo
Add rebase extension
r6906 """
Benoit Boissinot
remove unused variables
r7280 originalwd = target = None
Stefano Tortarolo
Add rebase extension
r6906 external = nullrev
state = skipped = {}
lock = wlock = None
try:
lock = repo.lock()
wlock = repo.wlock()
# Validate input and define rebasing points
destf = opts.get('dest', None)
srcf = opts.get('source', None)
basef = opts.get('base', None)
contf = opts.get('continue')
abortf = opts.get('abort')
collapsef = opts.get('collapse', False)
Augie Fackler
rebase: add support to keep branch names...
r7468 extrafn = opts.get('extrafn')
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 keepf = opts.get('keep', False)
keepbranchesf = opts.get('keepbranches', False)
Augie Fackler
rebase: add support to keep branch names...
r7468
Stefano Tortarolo
Add rebase extension
r6906 if contf or abortf:
if contf and abortf:
Matt Mackall
error: move ParseError
r7636 raise error.ParseError('rebase',
_('cannot use both abort and continue'))
Stefano Tortarolo
Add rebase extension
r6906 if collapsef:
Matt Mackall
error: move ParseError
r7636 raise error.ParseError(
'rebase', _('cannot use collapse with continue or abort'))
Stefano Tortarolo
Add rebase extension
r6906
Martin Geisler
remove unnecessary outer parenthesis in if-statements
r8117 if srcf or basef or destf:
Matt Mackall
error: move ParseError
r7636 raise error.ParseError('rebase',
Stefano Tortarolo
Add rebase extension
r6906 _('abort and continue do not allow specifying revisions'))
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 (originalwd, target, state, collapsef, keepf,
keepbranchesf, external) = restorestatus(repo)
Stefano Tortarolo
Add rebase extension
r6906 if abortf:
abort(repo, originalwd, target, state)
return
else:
if srcf and basef:
Matt Mackall
error: move ParseError
r7636 raise error.ParseError('rebase', _('cannot specify both a '
'revision and a base'))
Stefano Tortarolo
Add rebase extension
r6906 cmdutil.bail_if_changed(repo)
result = buildstate(repo, destf, srcf, basef, collapsef)
if result:
originalwd, target, state, external = result
else: # Empty state built, nothing to rebase
repo.ui.status(_('nothing to rebase\n'))
return
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 if keepbranchesf:
if extrafn:
raise error.ParseError(
'rebase', _('cannot use both keepbranches and extrafn'))
def extrafn(ctx, extra):
extra['branch'] = ctx.branch()
Stefano Tortarolo
Add rebase extension
r6906 # Rebase
targetancestors = list(repo.changelog.ancestors(target))
targetancestors.append(target)
Matt Mackall
replace util.sort with sorted built-in...
r8209 for rev in sorted(state):
Stefano Tortarolo
Add rebase extension
r6906 if state[rev] == -1:
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 storestatus(repo, originalwd, target, state, collapsef, keepf,
keepbranchesf, external)
Stefano Tortarolo
Add rebase extension
r6906 rebasenode(repo, rev, target, state, skipped, targetancestors,
Augie Fackler
rebase: add support to keep branch names...
r7468 collapsef, extrafn)
Stefano Tortarolo
Add rebase extension
r6906 ui.note(_('rebase merging completed\n'))
if collapsef:
Dirkjan Ochtman
strip trailing whitespace, replace tabs by spaces
r6923 p1, p2 = defineparents(repo, min(state), target,
Stefano Tortarolo
Add rebase extension
r6906 state, targetancestors)
concludenode(repo, rev, p1, external, state, collapsef,
Augie Fackler
rebase: add support to keep branch names...
r7468 last=True, skipped=skipped, extrafn=extrafn)
Stefano Tortarolo
Add rebase extension
r6906
if 'qtip' in repo.tags():
updatemq(repo, state, skipped, **opts)
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 if not keepf:
Stefano Tortarolo
Add rebase extension
r6906 # Remove no more useful revisions
Martin Geisler
rebase, revlog: use set(x) instead of set(x.keys())...
r8163 if set(repo.changelog.descendants(min(state))) - set(state):
Stefano Tortarolo
Add rebase extension
r6906 ui.warn(_("warning: new changesets detected on source branch, "
"not stripping\n"))
else:
repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
clearstatus(repo)
ui.status(_("rebase completed\n"))
Stefano Tortarolo
rebase: disable rollback after rebasing
r7130 if os.path.exists(repo.sjoin('undo')):
util.unlink(repo.sjoin('undo'))
Stefano Tortarolo
Add rebase extension
r6906 if skipped:
ui.note(_("%d revisions have been skipped\n") % len(skipped))
finally:
Ronny Pfannschmidt
switch lock releasing in the extensions from gc to explicit
r8112 release(lock, wlock)
Stefano Tortarolo
Add rebase extension
r6906
Augie Fackler
rebase: add support to keep branch names...
r7468 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={},
extrafn=None):
Stefano Tortarolo
Add rebase extension
r6906 """Skip commit if collapsing has been required and rev is not the last
revision, commit otherwise
"""
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 repo.ui.debug(_(" set parents\n"))
if collapse and not last:
repo.dirstate.setparents(repo[p1].node())
return None
Stefano Tortarolo
Add rebase extension
r6906
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
Stefano Tortarolo
Add rebase extension
r6906
# Commit, record the old nodeid
m, a, r = repo.status()[:3]
newrev = nullrev
try:
if last:
commitmsg = 'Collapsed revision'
for rebased in state:
if rebased not in skipped:
commitmsg += '\n* %s' % repo[rebased].description()
commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
else:
commitmsg = repo[rev].description()
# Commit might fail if unresolved files exist
Augie Fackler
rebase: add support to keep branch names...
r7468 extra = {'rebase_source': repo[rev].hex()}
if extrafn:
extrafn(repo[rev], extra)
Stefano Tortarolo
Add rebase extension
r6906 newrev = repo.commit(m+a+r,
text=commitmsg,
user=repo[rev].user(),
date=repo[rev].date(),
Augie Fackler
rebase: add support to keep branch names...
r7468 extra=extra)
Stefano Tortarolo
Add rebase extension
r6906 return newrev
except util.Abort:
# Invalidate the previous setparents
repo.dirstate.invalidate()
raise
Augie Fackler
rebase: add support to keep branch names...
r7468 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
extrafn):
Stefano Tortarolo
Add rebase extension
r6906 'Rebase a single revision'
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
Stefano Tortarolo
Add rebase extension
r6906
p1, p2 = defineparents(repo, rev, target, state, targetancestors)
Dirkjan Ochtman
kill some trailing spaces
r7298 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 repo[p2].rev()))
Dirkjan Ochtman
kill some trailing spaces
r7298
Stefano Tortarolo
Add rebase extension
r6906 # Merge phase
if len(repo.parents()) != 2:
# Update to target and merge it with local
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 if repo['.'].rev() != repo[p1].rev():
repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
merge.update(repo, p1, False, True, False)
else:
repo.ui.debug(_(" already in target\n"))
Stefano Tortarolo
Add rebase extension
r6906 repo.dirstate.write()
Stefano Tortarolo
rebase: avoid redundant merges (issue1301)
r7278 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
first = repo[rev].rev() == repo[min(state)].rev()
stats = rebasemerge(repo, rev, first)
Stefano Tortarolo
Add rebase extension
r6906
if stats[3] > 0:
raise util.Abort(_('fix unresolved conflicts with hg resolve then '
'run hg rebase --continue'))
else: # we have an interrupted rebase
repo.ui.debug(_('resuming interrupted rebase\n'))
Stefano Tortarolo
rebase: don't lose rename/copy data (Issue1423)
r7954 # Keep track of renamed files in the revision that is going to be rebased
# Here we simulate the copies and renames in the source changeset
cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
m1 = repo[rev].manifest()
m2 = repo[target].manifest()
for k, v in cop.iteritems():
if k in m1:
if v in m1 or v in m2:
repo.dirstate.copy(v, k)
if v in m2 and v not in m1:
repo.dirstate.remove(v)
Dirkjan Ochtman
more whitespace cleanup and some other style nits
r8222
Augie Fackler
rebase: add support to keep branch names...
r7468 newrev = concludenode(repo, rev, p1, p2, state, collapse,
extrafn=extrafn)
Stefano Tortarolo
Add rebase extension
r6906
# Update the state
if newrev is not None:
state[rev] = repo[newrev].rev()
else:
if not collapse:
Martin Geisler
i18n: mark strings for translation in rebase extension
r6964 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
repo.ui.debug(_('next revision set to %s\n') % p1)
Stefano Tortarolo
Add rebase extension
r6906 skipped[rev] = True
state[rev] = p1
Dirkjan Ochtman
strip trailing whitespace, replace tabs by spaces
r6923
Stefano Tortarolo
Add rebase extension
r6906 def defineparents(repo, rev, target, state, targetancestors):
'Return the new parent relationship of the revision that will be rebased'
parents = repo[rev].parents()
p1 = p2 = nullrev
P1n = parents[0].rev()
if P1n in targetancestors:
p1 = target
elif P1n in state:
p1 = state[P1n]
else: # P1n external
p1 = target
p2 = P1n
if len(parents) == 2 and parents[1].rev() not in targetancestors:
P2n = parents[1].rev()
# interesting second parent
if P2n in state:
if p1 == target: # P1n in targetancestors or external
p1 = state[P2n]
else:
p2 = state[P2n]
else: # P2n external
if p2 != nullrev: # P1n external too => rev is a merged revision
raise util.Abort(_('cannot use revision %d as base, result '
'would have 3 parents') % rev)
p2 = P2n
return p1, p2
Stefano Tortarolo
rebase: keep original mq patch format (Issue1574)...
r7955 def isagitpatch(repo, patchname):
'Return true if the given patch is in git format'
mqpatch = os.path.join(repo.mq.path, patchname)
for line in patch.linereader(file(mqpatch, 'rb')):
if line.startswith('diff --git'):
return True
return False
Stefano Tortarolo
Add rebase extension
r6906 def updatemq(repo, state, skipped, **opts):
'Update rebased mq patches - finalize and then import them'
mqrebase = {}
for p in repo.mq.applied:
if repo[p.rev].rev() in state:
Martin Geisler
i18n: mark strings for translation in rebase extension
r6964 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
Stefano Tortarolo
Add rebase extension
r6906 (repo[p.rev].rev(), p.name))
Stefano Tortarolo
rebase: keep original mq patch format (Issue1574)...
r7955 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
Stefano Tortarolo
Add rebase extension
r6906
if mqrebase:
repo.mq.finish(repo, mqrebase.keys())
# We must start import from the newest revision
Matt Mackall
replace various uses of list.reverse()
r8210 for rev in sorted(mqrebase, reverse=True):
Stefano Tortarolo
Add rebase extension
r6906 if rev not in skipped:
Martin Geisler
i18n: mark strings for translation in rebase extension
r6964 repo.ui.debug(_('import mq patch %d (%s)\n')
Stefano Tortarolo
rebase: keep original mq patch format (Issue1574)...
r7955 % (state[rev], mqrebase[rev][0]))
repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
git=mqrebase[rev][1],rev=[str(state[rev])])
Stefano Tortarolo
Add rebase extension
r6906 repo.mq.save_dirty()
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
external):
Stefano Tortarolo
Add rebase extension
r6906 'Store the current status to allow recovery'
f = repo.opener("rebasestate", "w")
f.write(repo[originalwd].hex() + '\n')
f.write(repo[target].hex() + '\n')
f.write(repo[external].hex() + '\n')
f.write('%d\n' % int(collapse))
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 f.write('%d\n' % int(keep))
f.write('%d\n' % int(keepbranches))
Dirkjan Ochtman
use dict.iteritems() rather than dict.items()...
r7622 for d, v in state.iteritems():
Stefano Tortarolo
Add rebase extension
r6906 oldrev = repo[d].hex()
newrev = repo[v].hex()
f.write("%s:%s\n" % (oldrev, newrev))
f.close()
repo.ui.debug(_('rebase status stored\n'))
def clearstatus(repo):
'Remove the status files'
if os.path.exists(repo.join("rebasestate")):
util.unlink(repo.join("rebasestate"))
def restorestatus(repo):
'Restore a previously stored status'
try:
target = None
collapse = False
external = nullrev
state = {}
f = repo.opener("rebasestate")
for i, l in enumerate(f.read().splitlines()):
if i == 0:
originalwd = repo[l].rev()
elif i == 1:
target = repo[l].rev()
elif i == 2:
external = repo[l].rev()
elif i == 3:
collapse = bool(int(l))
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 elif i == 4:
keep = bool(int(l))
elif i == 5:
keepbranches = bool(int(l))
Stefano Tortarolo
Add rebase extension
r6906 else:
oldrev, newrev = l.split(':')
state[repo[oldrev].rev()] = repo[newrev].rev()
repo.ui.debug(_('rebase status resumed\n'))
Stefano Tortarolo
rebase: store/restore arguments correctly...
r7952 return originalwd, target, state, collapse, keep, keepbranches, external
Stefano Tortarolo
Add rebase extension
r6906 except IOError, err:
if err.errno != errno.ENOENT:
raise
raise util.Abort(_('no rebase in progress'))
def abort(repo, originalwd, target, state):
'Restore the repository to its original state'
Martin Geisler
util: use built-in set and frozenset...
r8150 if set(repo.changelog.descendants(target)) - set(state.values()):
Stefano Tortarolo
Add rebase extension
r6906 repo.ui.warn(_("warning: new changesets detected on target branch, "
"not stripping\n"))
else:
# Strip from the first rebased revision
merge.update(repo, repo[originalwd].rev(), False, True, False)
rebased = filter(lambda x: x > -1, state.values())
if rebased:
strippoint = min(rebased)
repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
clearstatus(repo)
repo.ui.status(_('rebase aborted\n'))
def buildstate(repo, dest, src, base, collapse):
'Define which revisions are going to be rebased and where'
Martin Geisler
util: use built-in set and frozenset...
r8150 targetancestors = set()
Stefano Tortarolo
Add rebase extension
r6906
if not dest:
Peter Arrenbrecht
cleanup: whitespace cleanup
r7877 # Destination defaults to the latest revision in the current branch
Stefano Tortarolo
Add rebase extension
r6906 branch = repo[None].branch()
dest = repo[branch].rev()
else:
if 'qtip' in repo.tags() and (repo[dest].hex() in
[s.rev for s in repo.mq.applied]):
raise util.Abort(_('cannot rebase onto an applied mq patch'))
dest = repo[dest].rev()
if src:
commonbase = repo[src].ancestor(repo[dest])
if commonbase == repo[src]:
raise util.Abort(_('cannot rebase an ancestor'))
if commonbase == repo[dest]:
raise util.Abort(_('cannot rebase a descendant'))
source = repo[src].rev()
else:
if base:
cwd = repo[base].rev()
else:
cwd = repo['.'].rev()
if cwd == dest:
repo.ui.debug(_('already working on current\n'))
return None
Martin Geisler
util: use built-in set and frozenset...
r8150 targetancestors = set(repo.changelog.ancestors(dest))
Stefano Tortarolo
Add rebase extension
r6906 if cwd in targetancestors:
repo.ui.debug(_('already working on the current branch\n'))
return None
Martin Geisler
util: use built-in set and frozenset...
r8150 cwdancestors = set(repo.changelog.ancestors(cwd))
Stefano Tortarolo
Add rebase extension
r6906 cwdancestors.add(cwd)
rebasingbranch = cwdancestors - targetancestors
source = min(rebasingbranch)
Dirkjan Ochtman
strip trailing whitespace, replace tabs by spaces
r6923
Stefano Tortarolo
Add rebase extension
r6906 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
external = nullrev
if collapse:
if not targetancestors:
Martin Geisler
util: use built-in set and frozenset...
r8150 targetancestors = set(repo.changelog.ancestors(dest))
Stefano Tortarolo
Add rebase extension
r6906 for rev in state:
# Check externals and fail if there are more than one
for p in repo[rev].parents():
if (p.rev() not in state and p.rev() != source
and p.rev() not in targetancestors):
if external != nullrev:
raise util.Abort(_('unable to collapse, there is more '
'than one external parent'))
external = p.rev()
state[source] = nullrev
return repo['.'].rev(), repo[dest].rev(), state, external
Matt Mackall
extensions: use new wrapper functions
r7216 def pullrebase(orig, ui, repo, *args, **opts):
Stefano Tortarolo
Add rebase extension
r6906 'Call rebase after pull if the latter has been invoked with --rebase'
if opts.get('rebase'):
if opts.get('update'):
Stefano Tortarolo
rebase: pull --rebase updates if there is nothing to rebase
r7786 del opts.get['update']
ui.debug(_('--update and --rebase are not compatible, ignoring '
'the update flag\n'))
Stefano Tortarolo
Add rebase extension
r6906
cmdutil.bail_if_changed(repo)
revsprepull = len(repo)
Matt Mackall
extensions: use new wrapper functions
r7216 orig(ui, repo, *args, **opts)
Stefano Tortarolo
Add rebase extension
r6906 revspostpull = len(repo)
if revspostpull > revsprepull:
Matt Mackall
extensions: use new wrapper functions
r7216 rebase(ui, repo, **opts)
Stefano Tortarolo
rebase: pull --rebase updates if there is nothing to rebase
r7786 branch = repo[None].branch()
dest = repo[branch].rev()
if dest != repo['.'].rev():
# there was nothing to rebase we force an update
merge.update(repo, dest, False, False, False)
Stefano Tortarolo
Add rebase extension
r6906 else:
Matt Mackall
extensions: use new wrapper functions
r7216 orig(ui, repo, *args, **opts)
Stefano Tortarolo
Add rebase extension
r6906
def uisetup(ui):
'Replace pull with a decorator to provide --rebase option'
Matt Mackall
extensions: use new wrapper functions
r7216 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
entry[1].append(('', 'rebase', None,
_("rebase working directory to branch head"))
)
Stefano Tortarolo
Add rebase extension
r6906
cmdtable = {
"rebase":
(rebase,
[
('s', 'source', '', _('rebase from a given revision')),
('b', 'base', '', _('rebase from the base of a given revision')),
('d', 'dest', '', _('rebase onto a given revision')),
('', 'collapse', False, _('collapse the rebased revisions')),
Stefano Tortarolo
rebase: correct help text...
r7951 ('', 'keep', False, _('keep original revisions')),
('', 'keepbranches', False, _('keep original branches')),
Stefano Tortarolo
Add rebase extension
r6906 ('c', 'continue', False, _('continue an interrupted rebase')),
('a', 'abort', False, _('abort an interrupted rebase')),] +
templateopts,
Martin Geisler
upper-case command line meta variables
r8031 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
Stefano Tortarolo
rebase: correct help text...
r7951 '[--keepbranches] | [-c] | [-a]')),
Stefano Tortarolo
Add rebase extension
r6906 }