diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -22,6 +22,8 @@ from mercurial.lock import release 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 @@ def rebase(ui, repo, **opts): 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 @@ def rebase(ui, repo, **opts): 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 @@ def rebase(ui, repo, **opts): 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 @@ def rebase(ui, repo, **opts): 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 @@ def rebase(ui, repo, **opts): 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 @@ def defineparents(repo, rev, target, sta 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 @@ def abort(repo, originalwd, target, stat 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 @@ def buildstate(repo, dest, src, base): 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 @@ def buildstate(repo, dest, src, base): 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 @@ cmdtable = { ('', '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 --git a/tests/test-rebase-detach b/tests/test-rebase-detach new file mode 100755 --- /dev/null +++ b/tests/test-rebase-detach @@ -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 --git a/tests/test-rebase-detach.out b/tests/test-rebase-detach.out new file mode 100644 --- /dev/null +++ b/tests/test-rebase-detach.out @@ -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 --git a/tests/test-rebase-parameters.out b/tests/test-rebase-parameters.out --- a/tests/test-rebase-parameters.out +++ b/tests/test-rebase-parameters.out @@ -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 @@ options: --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 "hg -v help rebase" to show global o % 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 @@ options: --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 "hg -v help rebase" to show global o % 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 @@ options: --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 "hg -v help rebase" to show global o % 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 @@ options: --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