# HG changeset patch # User Stefano Tortarolo # Date 1265449910 -3600 # Node ID 66d954e76ffb621a4a54aad3705bb28528b71da9 # Parent 38fe86fb16e3d8f67e94f58e682df35edec53105 rebase: add --detach option to detach intermediate revisions (issue1950) When rebasing an intermediate revision, rebase keeps a parent relationship with the original parent. This option forces the removal of this relationship. In more depth, it 'fakes' null merges between the target revision and the ancestors of source, dropping every change from the ancestors. The result is that every change in source and its descendants will be rebased, ignoring the changes in its ancestors. diff -r 38fe86fb16e3 -r 66d954e76ffb hgext/rebase.py --- a/hgext/rebase.py Sun Jan 31 13:30:17 2010 +0100 +++ b/hgext/rebase.py Sat Feb 06 10:51:50 2010 +0100 @@ -22,6 +22,8 @@ from mercurial.i18n import _ import os, errno +nullmerge = -2 + def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch @@ -53,6 +55,7 @@ extrafn = opts.get('extrafn') keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) + detachf = opts.get('detach', False) if contf or abortf: if contf and abortf: @@ -62,6 +65,10 @@ raise error.ParseError( 'rebase', _('cannot use collapse with continue or abort')) + if detachf: + raise error.ParseError( + 'rebase', _('cannot use detach with continue or abort')) + if srcf or basef or destf: raise error.ParseError('rebase', _('abort and continue do not allow specifying revisions')) @@ -75,8 +82,16 @@ if srcf and basef: raise error.ParseError('rebase', _('cannot specify both a ' 'revision and a base')) + if detachf: + if not srcf: + raise error.ParseError( + 'rebase', _('detach requires a revision to be specified')) + if basef: + raise error.ParseError( + 'rebase', _('cannot specify a base with detach')) + cmdutil.bail_if_changed(repo) - result = buildstate(repo, destf, srcf, basef) + result = buildstate(repo, destf, srcf, basef, detachf) if not result: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) @@ -140,10 +155,10 @@ state, targetancestors) commitmsg = 'Collapsed revision' for rebased in state: - if rebased not in skipped: + if rebased not in skipped and state[rebased] != nullmerge: commitmsg += '\n* %s' % repo[rebased].description() commitmsg = ui.edit(commitmsg, repo.ui.username()) - concludenode(repo, rev, p1, external, commitmsg=commitmsg, + newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extra=extrafn) if 'qtip' in repo.tags(): @@ -151,11 +166,13 @@ if not keepf: # Remove no more useful revisions - if set(repo.changelog.descendants(min(state))) - set(state): - ui.warn(_("warning: new changesets detected on source branch, " - "not stripping\n")) - else: - repair.strip(ui, repo, repo[min(state)].node(), "strip") + rebased = [rev for rev in state if state[rev] != nullmerge] + if rebased: + if set(repo.changelog.descendants(min(rebased))) - set(state): + ui.warn(_("warning: new changesets detected on source branch, " + "not stripping\n")) + else: + repair.strip(ui, repo, repo[min(rebased)].node(), "strip") clearstatus(repo) ui.status(_("rebase completed\n")) @@ -260,7 +277,10 @@ if P1n in targetancestors: p1 = target elif P1n in state: - p1 = state[P1n] + if state[P1n] == nullmerge: + p1 = target + else: + p1 = state[P1n] else: # P1n external p1 = target p2 = P1n @@ -379,9 +399,10 @@ clearstatus(repo) repo.ui.status(_('rebase aborted\n')) -def buildstate(repo, dest, src, base): +def buildstate(repo, dest, src, base, detach): 'Define which revisions are going to be rebased and where' targetancestors = set() + detachset = set() if not dest: # Destination defaults to the latest revision in the current branch @@ -400,6 +421,12 @@ if commonbase == repo[dest]: raise util.Abort(_('source is descendant of destination')) source = repo[src].rev() + if detach: + # We need to keep track of source's ancestors up to the common base + srcancestors = set(repo.changelog.ancestors(source)) + baseancestors = set(repo.changelog.ancestors(commonbase.rev())) + detachset = srcancestors - baseancestors + detachset.remove(commonbase.rev()) else: if base: cwd = repo[base].rev() @@ -426,6 +453,7 @@ repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source)) state = dict.fromkeys(repo.changelog.descendants(source), nullrev) + state.update(dict.fromkeys(detachset, nullmerge)) state[source] = nullrev return repo['.'].rev(), repo[dest].rev(), state @@ -468,9 +496,11 @@ ('', 'collapse', False, _('collapse the rebased changesets')), ('', 'keep', False, _('keep original changesets')), ('', 'keepbranches', False, _('keep original branch names')), + ('', 'detach', False, _('force detaching of source from its original ' + 'branch')), ('c', 'continue', False, _('continue an interrupted rebase')), ('a', 'abort', False, _('abort an interrupted rebase')),] + templateopts, - _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] ' - '[--keepbranches] | [-c] | [-a]')), + _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] ' + '[--keep] [--keepbranches] | [-c] | [-a]')), } diff -r 38fe86fb16e3 -r 66d954e76ffb tests/test-rebase-detach --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-detach Sat Feb 06 10:51:50 2010 +0100 @@ -0,0 +1,68 @@ +#!/bin/sh + +echo "[extensions]" >> $HGRCPATH +echo "graphlog=" >> $HGRCPATH +echo "rebase=" >> $HGRCPATH + +BASE=`pwd` + +addcommit () { + echo $1 > $1 + hg add $1 + hg commit -d "${2} 0" -m $1 +} + +commit () { + hg commit -d "${2} 0" -m $1 +} + +createrepo () { + cd $BASE + rm -rf a + hg init a + cd a + addcommit "A" 0 + addcommit "B" 1 + addcommit "C" 2 + addcommit "D" 3 + + hg update -C 0 + addcommit "E" 4 +} + +createrepo > /dev/null 2>&1 +hg glog --template '{rev}: {desc}\n' +echo '% Rebasing D onto E detaching from C' +hg rebase --detach -s 3 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/' +hg glog --template '{rev}: {desc}\n' +echo "Expected A, D, E" +hg manifest + +echo +createrepo > /dev/null 2>&1 +hg glog --template '{rev}: {desc}\n' +echo '% Rebasing C onto E detaching from B' +hg rebase --detach -s 2 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/' +hg glog --template '{rev}: {desc}\n' +echo "Expected A, C, D, E" +hg manifest + +echo +createrepo > /dev/null 2>&1 +hg glog --template '{rev}: {desc}\n' +echo '% Rebasing B onto E using detach (same as not using it)' +hg rebase --detach -s 1 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/' +hg glog --template '{rev}: {desc}\n' +echo "Expected A, B, C, D, E" +hg manifest + +echo +createrepo > /dev/null 2>&1 +hg glog --template '{rev}: {desc}\n' +echo '% Rebasing C onto E detaching from B and collapsing' +hg rebase --detach --collapse -s 2 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/' +hg glog --template '{rev}: {desc}\n' +echo "Expected A, C, D, E" +hg manifest + +exit 0 diff -r 38fe86fb16e3 -r 66d954e76ffb tests/test-rebase-detach.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-detach.out Sat Feb 06 10:51:50 2010 +0100 @@ -0,0 +1,134 @@ +@ 4: E +| +| o 3: D +| | +| o 2: C +| | +| o 1: B +|/ +o 0: A + +% Rebasing D onto E detaching from C +saving bundle to +adding branch +adding changesets +adding manifests +adding file changes +added 2 changesets with 2 changes to 2 files (+1 heads) +rebase completed +@ 4: D +| +o 3: E +| +| o 2: C +| | +| o 1: B +|/ +o 0: A + +Expected A, D, E +A +D +E + +@ 4: E +| +| o 3: D +| | +| o 2: C +| | +| o 1: B +|/ +o 0: A + +% Rebasing C onto E detaching from B +saving bundle to +adding branch +adding changesets +adding manifests +adding file changes +added 3 changesets with 3 changes to 3 files (+1 heads) +rebase completed +@ 4: D +| +o 3: C +| +o 2: E +| +| o 1: B +|/ +o 0: A + +Expected A, C, D, E +A +C +D +E + +@ 4: E +| +| o 3: D +| | +| o 2: C +| | +| o 1: B +|/ +o 0: A + +% Rebasing B onto E using detach (same as not using it) +saving bundle to +adding branch +adding changesets +adding manifests +adding file changes +added 4 changesets with 4 changes to 4 files +rebase completed +@ 4: D +| +o 3: C +| +o 2: B +| +o 1: E +| +o 0: A + +Expected A, B, C, D, E +A +B +C +D +E + +@ 4: E +| +| o 3: D +| | +| o 2: C +| | +| o 1: B +|/ +o 0: A + +% Rebasing C onto E detaching from B and collapsing +saving bundle to +adding branch +adding changesets +adding manifests +adding file changes +added 2 changesets with 3 changes to 3 files (+1 heads) +rebase completed +@ 3: Collapsed revision +| * C +| * D +o 2: E +| +| o 1: B +|/ +o 0: A + +Expected A, C, D, E +A +C +D +E diff -r 38fe86fb16e3 -r 66d954e76ffb tests/test-rebase-parameters.out --- a/tests/test-rebase-parameters.out Sun Jan 31 13:30:17 2010 +0100 +++ b/tests/test-rebase-parameters.out Sat Feb 06 10:51:50 2010 +0100 @@ -2,7 +2,7 @@ % Use continue and abort hg rebase: cannot use both abort and continue -hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] [--keepbranches] | [-c] | [-a] +hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] [--keep] [--keepbranches] | [-c] | [-a] move changeset (and descendants) to a different branch @@ -21,6 +21,7 @@ --collapse collapse the rebased changesets --keep keep original changesets --keepbranches keep original branch names + --detach force detaching of source from its original branch -c --continue continue an interrupted rebase -a --abort abort an interrupted rebase --style display using template map file @@ -30,7 +31,7 @@ % Use continue and collapse hg rebase: cannot use collapse with continue or abort -hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] [--keepbranches] | [-c] | [-a] +hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] [--keep] [--keepbranches] | [-c] | [-a] move changeset (and descendants) to a different branch @@ -49,6 +50,7 @@ --collapse collapse the rebased changesets --keep keep original changesets --keepbranches keep original branch names + --detach force detaching of source from its original branch -c --continue continue an interrupted rebase -a --abort abort an interrupted rebase --style display using template map file @@ -58,7 +60,7 @@ % Use continue/abort and dest/source hg rebase: abort and continue do not allow specifying revisions -hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] [--keepbranches] | [-c] | [-a] +hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] [--keep] [--keepbranches] | [-c] | [-a] move changeset (and descendants) to a different branch @@ -77,6 +79,7 @@ --collapse collapse the rebased changesets --keep keep original changesets --keepbranches keep original branch names + --detach force detaching of source from its original branch -c --continue continue an interrupted rebase -a --abort abort an interrupted rebase --style display using template map file @@ -86,7 +89,7 @@ % Use source and base hg rebase: cannot specify both a revision and a base -hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] [--keepbranches] | [-c] | [-a] +hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] [--keep] [--keepbranches] | [-c] | [-a] move changeset (and descendants) to a different branch @@ -105,6 +108,7 @@ --collapse collapse the rebased changesets --keep keep original changesets --keepbranches keep original branch names + --detach force detaching of source from its original branch -c --continue continue an interrupted rebase -a --abort abort an interrupted rebase --style display using template map file