From aa99ac6df508f4fa258e89c9d903440f0799e1fd Mon Sep 17 00:00:00 2001 From: Pierre-Yves David Date: Wed, 20 Jun 2012 20:08:57 +0200 Subject: [PATCH] rebase: do not add second parent to rebased changeset (drop detach option) (BC) Rebase now behaves as if --detach was always passed. Non-merges are rebased as non-merges, regardless of their parent being an ancestor of the destination. Merges will usually be rebased as merges unless both of their parents are ancestors of the destination, or one of their parents is pruned when rebased. This only alters the behavior of rebase when using the --source/--rev options. --detach option is deprecated. All test changes were carefully validated. --- hgext/rebase.py | 65 ++++++++++----- tests/test-bookmarks-rebase.t | 3 +- tests/test-rebase-bookmarks.t | 2 +- tests/test-rebase-cache.t | 6 +- tests/test-rebase-collapse.t | 6 +- tests/test-rebase-detach.t | 14 ++-- tests/test-rebase-parameters.t | 50 +++++------ tests/test-rebase-scenario-global.t | 124 ++++++++++++++-------------- 8 files changed, 146 insertions(+), 124 deletions(-) diff --git a/hgext/rebase.py b/hgext/rebase.py index 69cb01e9b6..c40cef0b18 100644 --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -48,8 +48,7 @@ testedwith = 'internal' _('read collapse commit message from file'), _('FILE')), ('', 'keep', False, _('keep original changesets')), ('', 'keepbranches', False, _('keep original branch names')), - ('D', 'detach', False, _('force detaching of source from its original ' - 'branch')), + ('D', 'detach', False, _('(DEPRECATED)')), ('t', 'tool', '', _('specify merge tool')), ('c', 'continue', False, _('continue an interrupted rebase')), ('a', 'abort', False, _('abort an interrupted rebase'))] + @@ -131,7 +130,6 @@ def rebase(ui, repo, **opts): extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) - detachf = opts.get('detach', False) # keepopen is not meant for use on the command line, but by # other extensions keepopen = opts.get('keepopen', False) @@ -146,8 +144,6 @@ def rebase(ui, repo, **opts): if collapsef: raise util.Abort( _('cannot use collapse with continue or abort')) - if detachf: - raise util.Abort(_('cannot use detach with continue or abort')) if srcf or basef or destf: raise util.Abort( _('abort and continue do not allow specifying revisions')) @@ -168,12 +164,6 @@ def rebase(ui, repo, **opts): if revf and srcf: raise util.Abort(_('cannot specify both a ' 'revision and a source')) - if detachf: - if not (srcf or revf): - raise util.Abort( - _('detach requires a revision to be specified')) - if basef: - raise util.Abort(_('cannot specify a base with detach')) cmdutil.bailifchanged(repo) @@ -215,7 +205,7 @@ def rebase(ui, repo, **opts): % repo[root], hint=_('see hg help phases for details')) else: - result = buildstate(repo, dest, rebaseset, detachf, collapsef) + result = buildstate(repo, dest, rebaseset, collapsef) if not result: # Empty state built, nothing to rebase @@ -592,13 +582,13 @@ def abort(repo, originalwd, target, state): repo.ui.warn(_('rebase aborted\n')) return 0 -def buildstate(repo, dest, rebaseset, detach, collapse): +def buildstate(repo, dest, rebaseset, collapse): '''Define which revisions are going to be rebased and where repo: repo dest: context rebaseset: set of rev - detach: boolean''' + ''' # This check isn't strictly necessary, since mq detects commits over an # applied patch. But it prevents messing up the working directory when @@ -607,7 +597,6 @@ def buildstate(repo, dest, rebaseset, detach, collapse): [s.node for s in repo.mq.applied]): raise util.Abort(_('cannot rebase onto an applied mq patch')) - detachset = set() roots = list(repo.set('roots(%ld)', rebaseset)) if not roots: raise util.Abort(_('no matching revisions')) @@ -623,14 +612,50 @@ def buildstate(repo, dest, rebaseset, detach, collapse): if not collapse and samebranch and root in dest.children(): repo.ui.debug('source is a child of destination\n') return None - # rebase on ancestor, force detach - detach = True - if detach: - detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root) repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root)) state = dict.fromkeys(rebaseset, nullrev) - state.update(dict.fromkeys(detachset, nullmerge)) + # Rebase tries to turn into a parent of while + # preserving the number of parents of rebased changesets: + # + # - A changeset with a single parent will always be rebased as a + # changeset with a single parent. + # + # - A merge will be rebased as merge unless its parents are both + # ancestors of or are themselves in the rebased set and + # pruned while rebased. + # + # If one parent of is an ancestor of , the rebased + # version of this parent will be . This is always true with + # --base option. + # + # Otherwise, we need to *replace* the original parents with + # . This "detaches" the rebased set from its former location + # and rebases it onto . Changes introduced by ancestors of + # not common with (the detachset, marked as + # nullmerge) are "removed" from the rebased changesets. + # + # - If has a single parent, set it to . + # + # - If is a merge, we cannot decide which parent to + # replace, the rebase operation is not clearly defined. + # + # The table below sums up this behavior: + # + # +--------------------+----------------------+-------------------------+ + # | | one parent | merge | + # +--------------------+----------------------+-------------------------+ + # | parent in :: | new parent is | parents in :: are | + # | | | remapped to | + # +--------------------+----------------------+-------------------------+ + # | unrelated source | new parent is | ambiguous, abort | + # +--------------------+----------------------+-------------------------+ + # + # The actual abort is handled by `defineparents` + if len(root.parents()) <= 1: + # (strict) ancestors of not ancestors of + detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root) + state.update(dict.fromkeys(detachset, nullmerge)) return repo['.'].rev(), dest.rev(), state def pullrebase(orig, ui, repo, *args, **opts): diff --git a/tests/test-bookmarks-rebase.t b/tests/test-bookmarks-rebase.t index 49eb5c6ce9..1b89620bf8 100644 --- a/tests/test-bookmarks-rebase.t +++ b/tests/test-bookmarks-rebase.t @@ -39,11 +39,10 @@ rebase saved backup bundle to $TESTTMP/.hg/strip-backup/*-backup.hg (glob) $ hg log - changeset: 3:9163974d1cb5 + changeset: 3:42e5ed2cdcf4 bookmark: two tag: tip parent: 1:925d80f479bb - parent: 2:db815d6d32e6 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: 3 diff --git a/tests/test-rebase-bookmarks.t b/tests/test-rebase-bookmarks.t index dad944fb55..b15ed87442 100644 --- a/tests/test-rebase-bookmarks.t +++ b/tests/test-rebase-bookmarks.t @@ -54,7 +54,7 @@ Move only rebased bookmarks $ cd a1 $ hg up -q Z - $ hg rebase --detach -s Y -d 3 + $ hg rebase -s Y -d 3 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog diff --git a/tests/test-rebase-cache.t b/tests/test-rebase-cache.t index a957e938bd..8281770cad 100644 --- a/tests/test-rebase-cache.t +++ b/tests/test-rebase-cache.t @@ -104,7 +104,7 @@ Rebase part of branch2 (5-6) onto branch3 (8): 2: 'B' branch1 0: 'A' - $ hg rebase --detach -s 5 -d 8 + $ hg rebase -s 5 -d 8 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg branches @@ -165,7 +165,7 @@ Rebase head of branch3 (8) onto branch2 (6): |/ o 0: 'A' - $ hg rebase --detach -s 8 -d 6 + $ hg rebase -s 8 -d 6 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg branches @@ -229,7 +229,7 @@ Rebase entire branch3 (7-8) onto branch2 (6): |/ o 0: 'A' - $ hg rebase --detach -s 7 -d 6 + $ hg rebase -s 7 -d 6 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg branches diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t index d641213f8c..4ec919677e 100644 --- a/tests/test-rebase-collapse.t +++ b/tests/test-rebase-collapse.t @@ -230,7 +230,7 @@ Rebase and collapse - more than one external (fail): Rebase and collapse - E onto H: - $ hg rebase -s 4 --collapse + $ hg rebase -s 4 --collapse # root (4) is not a merge saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -250,7 +250,6 @@ Rebase and collapse - E onto H: $ hg manifest A - B C D E @@ -340,7 +339,7 @@ Rebase and collapse - E onto I: $ hg clone -q -u . c c1 $ cd c1 - $ hg rebase -s 4 --collapse + $ hg rebase -s 4 --collapse # root (4) is not a merge merging E saved backup bundle to $TESTTMP/c1/.hg/strip-backup/*-backup.hg (glob) @@ -362,7 +361,6 @@ Rebase and collapse - E onto I: $ hg manifest A - B C D E diff --git a/tests/test-rebase-detach.t b/tests/test-rebase-detach.t index 97176c2f4a..cacc754966 100644 --- a/tests/test-rebase-detach.t +++ b/tests/test-rebase-detach.t @@ -48,7 +48,7 @@ Rebasing D onto H detaching from C: o 0: 'A' $ hg phase --force --secret 3 - $ hg rebase --detach -s 3 -d 7 + $ hg rebase -s 3 -d 7 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n" @@ -99,7 +99,7 @@ Rebasing C onto H detaching from B: |/ o 0: 'A' - $ hg rebase --detach -s 2 -d 7 + $ hg rebase -s 2 -d 7 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -151,7 +151,7 @@ Rebasing B onto H using detach (same as not using it): |/ o 0: 'A' - $ hg rebase --detach -s 1 -d 7 + $ hg rebase -s 1 -d 7 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -205,7 +205,7 @@ Rebasing C onto H detaching from B and collapsing: |/ o 0: 'A' - $ hg rebase --detach --collapse -s 2 -d 7 + $ hg rebase --collapse -s 2 -d 7 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob) $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n" @@ -264,7 +264,7 @@ Rebasing across null as ancestor |/ o 0: 'A' - $ hg rebase --detach -s 1 -d tip + $ hg rebase -s 1 -d tip saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -325,7 +325,7 @@ Verify that target is not selected as external rev (issue3085) $ echo "J" >> F $ hg ci -m "J" - $ hg rebase -s 8 -d 7 --collapse --detach --config ui.merge=internal:other + $ hg rebase -s 8 -d 7 --collapse --config ui.merge=internal:other remote changed E which local deleted use (c)hanged version or leave (d)eleted? c saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob) @@ -370,7 +370,7 @@ Ensure --continue restores a correct state (issue3046) and phase: $ hg ci -A -m 'H2' adding H $ hg phase --force --secret 8 - $ hg rebase -s 8 -d 7 --detach --config ui.merge=internal:fail + $ hg rebase -s 8 -d 7 --config ui.merge=internal:fail merging H warning: conflicts during merge. merging H incomplete! (edit conflicts, then use 'hg resolve --mark') diff --git a/tests/test-rebase-parameters.t b/tests/test-rebase-parameters.t index 36b0d5fcf4..6597f75694 100644 --- a/tests/test-rebase-parameters.t +++ b/tests/test-rebase-parameters.t @@ -199,19 +199,19 @@ Specify only source (from 2 onto 8): $ hg tglog @ 8: 'D' | - o 7: 'C' - |\ - | o 6: 'I' + o 7: 'C' + | + o 6: 'I' + | + o 5: 'H' + | + | o 4: 'G' + |/| + o | 3: 'F' | | - | o 5: 'H' - | | - | | o 4: 'G' - | |/| - | o | 3: 'F' - | | | - | | o 2: 'E' - | |/ - o | 1: 'B' + | o 2: 'E' + |/ + | o 1: 'B' |/ o 0: 'A' @@ -283,7 +283,7 @@ Specify source and dest (from 2 onto 7): $ hg clone -q -u . a a7 $ cd a7 - $ hg rebase --detach --source 2 --dest 7 + $ hg rebase --source 2 --dest 7 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -349,19 +349,19 @@ Specify only revs (from 2 onto 8) $ hg tglog @ 8: 'D' | - o 7: 'C' - |\ - | o 6: 'I' + o 7: 'C' + | + o 6: 'I' + | + o 5: 'H' + | + | o 4: 'G' + |/| + o | 3: 'F' | | - | o 5: 'H' - | | - | | o 4: 'G' - | |/| - | o | 3: 'F' - | | | - | | o 2: 'E' - | |/ - o | 1: 'B' + | o 2: 'E' + |/ + | o 1: 'B' |/ o 0: 'A' diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t index 5d0b5ffeb0..9c86450357 100644 --- a/tests/test-rebase-scenario-global.t +++ b/tests/test-rebase-scenario-global.t @@ -52,19 +52,19 @@ D onto H - simple rebase: saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'D' - |\ - | o 6: 'H' + @ 7: 'D' + | + o 6: 'H' + | + | o 5: 'G' + |/| + o | 4: 'F' | | - | | o 5: 'G' - | |/| - | o | 4: 'F' - | | | - | | o 3: 'E' - | |/ - o | 2: 'C' + | o 3: 'E' + |/ + | o 2: 'C' | | - o | 1: 'B' + | o 1: 'B' |/ o 0: 'A' @@ -80,19 +80,19 @@ D onto F - intermediate point: saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'D' - |\ - | | o 6: 'H' - | |/ - | | o 5: 'G' - | |/| - | o | 4: 'F' - | | | - | | o 3: 'E' - | |/ - o | 2: 'C' + @ 7: 'D' + | + | o 6: 'H' + |/ + | o 5: 'G' + |/| + o | 4: 'F' | | - o | 1: 'B' + | o 3: 'E' + |/ + | o 2: 'C' + | | + | o 1: 'B' |/ o 0: 'A' @@ -303,9 +303,9 @@ Source phase greater or equal to destination phase: new changeset get the phase $ hg log --template "{phase}\n" -r 9 secret Source phase lower than destination phase: new changeset get the phase of destination: - $ hg rebase -s7 -d9 - saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c9659aac0000-backup.hg (glob) - $ hg log --template "{phase}\n" -r 9 + $ hg rebase -s8 -d9 + saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-backup.hg + $ hg log --template "{phase}\n" -r 'rev(9)' secret $ cd .. @@ -404,20 +404,20 @@ Base on have one descendant heads we ask for but common ancestor have two | o 10: 'G' | - o 9: 'D' - |\ - | | o 8: 'I' + o 9: 'D' + | + | o 8: 'I' + | | + | o 7: 'H' + | | + | o 6: 'G' + | | + | | o 5: 'F' | | | - | | o 7: 'H' - | | | - | | o 6: 'G' - | | | - | | | o 5: 'F' - | | | | - | | | o 4: 'E' - | | |/ - | | o 3: 'D' + | | o 4: 'E' | |/ + | o 3: 'D' + | | | o 2: 'C' | | o | 1: 'B' @@ -441,20 +441,20 @@ rebase subset | o 10: 'G' | - o 9: 'D' - |\ - | | o 8: 'I' + o 9: 'D' + | + | o 8: 'I' + | | + | o 7: 'H' + | | + | o 6: 'G' + | | + | | o 5: 'F' | | | - | | o 7: 'H' - | | | - | | o 6: 'G' - | | | - | | | o 5: 'F' - | | | | - | | | o 4: 'E' - | | |/ - | | o 3: 'D' + | | o 4: 'E' | |/ + | o 3: 'D' + | | | o 2: 'C' | | o | 1: 'B' @@ -482,20 +482,20 @@ rebase subset with multiple head | | | o 10: 'E' |/ - o 9: 'D' - |\ - | | o 8: 'I' + o 9: 'D' + | + | o 8: 'I' + | | + | o 7: 'H' + | | + | o 6: 'G' + | | + | | o 5: 'F' | | | - | | o 7: 'H' - | | | - | | o 6: 'G' - | | | - | | | o 5: 'F' - | | | | - | | | o 4: 'E' - | | |/ - | | o 3: 'D' + | | o 4: 'E' | |/ + | o 3: 'D' + | | | o 2: 'C' | | o | 1: 'B'