[PATCH] rebase: do not add second parent to rebased changeset (drop detach option) (BC)
Augie Fackler
raf at durin42.com
Thu Jun 7 08:49:04 CDT 2012
On Thu, Jun 7, 2012 at 6:58 AM, <pierre-yves.david at logilab.fr> wrote:
> # HG changeset patch
> # User Pierre-Yves David <pierre-yves.david at ens-lyon.org>
> # Date 1339069890 -7200
> # Node ID 146a5994a0dbe3f6dd5dcb05bacfdff6f92a8c97
> # Parent 2255950e1f7663a9faa6b57040cc5c0debe7d4dd
> rebase: do not add second parent to rebased changeset (drop detach option) (BC)
>
> Rebase now always keeps the number of parents constant. merge are rebased as
> merge and non-merge are rebased with single parent. This mean that hg rebase
> always "detach" the rebase set from it's original branch if the root changeset
> only have a single parent.
>
> This only alter the behavior of rebase when using the --source option. The
> detach option itself is dropped.
>
> See in comment in code for details on the Why and How of the new behavior.
>
> All test changes have been manually validated with care.
>
> diff --git a/hgext/rebase.py b/hgext/rebase.py
> --- a/hgext/rebase.py
> +++ b/hgext/rebase.py
> @@ -46,12 +46,11 @@ testedwith = 'internal'
> ('e', 'edit', False, _('invoke editor on commit messages')),
> ('l', 'logfile', '',
> _('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, _('do nothing (DEPRECATED)')),
This makes it sound like the flag will cause rebase to be a noop. How
about only listing (DEPRECATED) in the doc for --detach?
> ('t', 'tool', '', _('specify merge tool')),
> ('c', 'continue', False, _('continue an interrupted rebase')),
> ('a', 'abort', False, _('abort an interrupted rebase'))] +
> templateopts,
> _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
> @@ -129,11 +128,10 @@ def rebase(ui, repo, **opts):
> collapsef = opts.get('collapse', False)
> collapsemsg = cmdutil.logmessage(ui, 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)
>
> if collapsemsg and not collapsef:
> @@ -144,12 +142,10 @@ def rebase(ui, repo, **opts):
> if contf and abortf:
> raise util.Abort(_('cannot use both abort and continue'))
> 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'))
> if opts.get('tool', False):
> ui.warn(_('tool option will be ignored\n'))
> @@ -166,16 +162,10 @@ def rebase(ui, repo, **opts):
> raise util.Abort(_('cannot specify both a '
> 'revision and a base'))
> 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)
>
> if not destf:
> # Destination defaults to the latest revision in the
> @@ -213,11 +203,11 @@ def rebase(ui, repo, **opts):
> elif not keepf and not repo[root].mutable():
> raise util.Abort(_("can't rebase immutable changeset %s")
> % 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
> ui.status(_('nothing to rebase\n'))
> return 1
> @@ -590,26 +580,25 @@ def abort(repo, originalwd, target, stat
> repair.strip(repo.ui, repo, repo[strippoint].node())
> clearstatus(repo)
> 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
> # a partially completed rebase is blocked by mq.
> if 'qtip' in repo.tags() and (dest.node() in
> [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'))
> if len(roots) > 1:
> raise util.Abort(_("can't rebase multiple roots"))
> @@ -621,18 +610,64 @@ def buildstate(repo, dest, rebaseset, de
> if commonbase == dest:
> samebranch = root.branch() == dest.branch()
> 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))
> + # Detach root from it's former parent
> + # If root is a merge we try to preserve it.
> + # (possible if at least one parent is ancestor of destination)
> + #
> + # Full Explanation
> + # -----------------
> + #
> + # Rebase try to preserve 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 changeset with two parent will likely be rebased as a changeset
> + # with two parents. (It can have fewer parents in some special cases
> + # ignored here)
> + #
> + # The goal of rebase is to make <dest> a parent of <root>. It's is
> + # very simple if at least one parent of <root> is an ancestor of
> + # <dest>. In this case the "rebased" version of this parent is
> + # obviously <dest>. This is always the case when <root> where
> + # specified using the --base option, .
> + #
> + # If no parent of <root> is an ancestor of <dest> this is more
> + # complex. To keep the same number of parents for the rebased
> + # version of <root> we need to *replace* the original parent by
> + # <dest>. This "detach" the rebased set from it's former
> + # localisation and rebase it onto <dest>. When "detaching" the
> + # changeset, changes introduced by ancestors of <root> which are
> + # not common with <dest> are "removed" from the rebased changeset.
> + #
> + # - If <root> has a single parent, we just need to drop it.
> + #
> + # - If <root> is a merge, we can't decide which parent to drop. Dropping
> + # both parents does not make sense.
> + #
> + # The table below sum up this behavior:
> + #
> + # +--------------------+----------------------+-------------------------+
> + # | | one parent | merge |
> + # +--------------------+----------------------+-------------------------+
> + # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
> + # | | | remapped to <dest> |
> + # +--------------------+----------------------+-------------------------+
> + # | unrelated source | new parent is <dest> | Ambiguous rebase abort |
> + # +--------------------+----------------------+-------------------------+
> + #
> + # The actual abort is handled by `defineparents`
> + if len(root.parents()) <= 1:
> + # (strict) ancestors of <root> not ancestors of <dest>
> + 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):
> 'Call rebase after pull if the latter has been invoked with --rebase'
> if opts.get('rebase'):
> diff --git a/tests/test-bookmarks-rebase.t b/tests/test-bookmarks-rebase.t
> --- a/tests/test-bookmarks-rebase.t
> +++ b/tests/test-bookmarks-rebase.t
> @@ -37,15 +37,14 @@ rebase
>
> $ hg rebase -s two -d one
> 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
>
> changeset: 2:db815d6d32e6
> diff --git a/tests/test-rebase-bookmarks.t b/tests/test-rebase-bookmarks.t
> --- a/tests/test-rebase-bookmarks.t
> +++ b/tests/test-rebase-bookmarks.t
> @@ -52,11 +52,11 @@ Move only rebased bookmarks
> $ hg clone -q a a1
>
> $ 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
> @ 3: 'C' bookmarks: Y Z
> |
> diff --git a/tests/test-rebase-cache.t b/tests/test-rebase-cache.t
> --- a/tests/test-rebase-cache.t
> +++ b/tests/test-rebase-cache.t
> @@ -102,11 +102,11 @@ Rebase part of branch2 (5-6) onto branch
> 6: 'E' branch2
> 4: 'C' branch2
> 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
> branch3 8:466cdfb14b62
> branch2 4:e4fdb121d036
> @@ -163,11 +163,11 @@ Rebase head of branch3 (8) onto branch2
> | |
> | o 1: 'branch1' branch1
> |/
> 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
> branch2 8:6b4bdc1b5ac0
> branch3 7:653b9feb4616
> @@ -227,11 +227,11 @@ Rebase entire branch3 (7-8) onto branch2
> | |
> | o 1: 'branch1' branch1
> |/
> 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
> branch2 7:6b4bdc1b5ac0
> branch1 2:0a03079c47fd (inactive)
> diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t
> --- a/tests/test-rebase-collapse.t
> +++ b/tests/test-rebase-collapse.t
> @@ -228,11 +228,11 @@ Rebase and collapse - more than one exte
> abort: unable to collapse, there is more than one external parent
> [255]
>
> 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
> @ 5: 'Collapsed revision
> |\ * E
> @@ -248,11 +248,10 @@ Rebase and collapse - E onto H:
> |/
> o 0: 'A'
>
> $ hg manifest
> A
> - B
> C
> D
> E
> F
> H
> @@ -338,11 +337,11 @@ Create repo c:
> 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)
>
> $ hg tglog
> @ 5: 'Collapsed revision
> @@ -360,11 +359,10 @@ Rebase and collapse - E onto I:
> |/
> o 0: 'A'
>
> $ hg manifest
> A
> - B
> C
> D
> E
> G
> I
> diff --git a/tests/test-rebase-detach.t b/tests/test-rebase-detach.t
> --- a/tests/test-rebase-detach.t
> +++ b/tests/test-rebase-detach.t
> @@ -46,11 +46,11 @@ Rebasing D onto H detaching from C:
> | o 1: 'B'
> |/
> 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"
> @ 7:secret 'D'
> |
> @@ -97,11 +97,11 @@ Rebasing C onto H detaching from B:
> | |
> | o 1: '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
> @ 7: 'D'
> |
> @@ -149,11 +149,11 @@ Rebasing B onto H using detach (same as
> | |
> | o 1: 'B'
> |/
> 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
> @ 7: 'D'
> |
> @@ -203,11 +203,11 @@ Rebasing C onto H detaching from B and c
> | |
> | o 1: 'B'
> |/
> 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"
> @ 6:secret 'Collapsed revision
> | * C
> @@ -262,11 +262,11 @@ Rebasing across null as ancestor
> | |
> | o 1: 'B'
> |/
> 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
> @ 8: 'D'
> |
> @@ -323,11 +323,11 @@ Verify that target is not selected as ex
> (branch merge, don't forget to commit)
> $ hg ci -m "Merge"
> $ 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)
>
> $ hg tglog
> @@ -368,11 +368,11 @@ Ensure --continue restores a correct sta
> $ hg up -q 3
> $ echo 'H2' > H
> $ 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')
> abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
> [255]
> diff --git a/tests/test-rebase-parameters.t b/tests/test-rebase-parameters.t
> --- a/tests/test-rebase-parameters.t
> +++ b/tests/test-rebase-parameters.t
> @@ -197,23 +197,23 @@ Specify only source (from 2 onto 8):
> saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
>
> $ 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'
>
> $ cd ..
>
> @@ -281,11 +281,11 @@ Specify only base (from 1 onto 8):
> 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
> @ 8: 'D'
> |
> @@ -347,23 +347,23 @@ Specify only revs (from 2 onto 8)
> saved backup bundle to $TESTTMP/a9/.hg/strip-backup/*-backup.hg (glob)
>
> $ 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'
>
> $ cd ..
>
> diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
> --- a/tests/test-rebase-scenario-global.t
> +++ b/tests/test-rebase-scenario-global.t
> @@ -50,23 +50,23 @@ D onto H - simple rebase:
>
> $ hg rebase -s 3 -d 7
> 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'
>
> $ cd ..
>
> @@ -78,23 +78,23 @@ D onto F - intermediate point:
>
> $ hg rebase -s 3 -d 5
> 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'
>
> $ cd ..
>
> @@ -304,11 +304,11 @@ Source phase greater or equal to destina
> 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
> - secret
> + draft
>
> $ cd ..
>
> Test for revset
>
> @@ -402,24 +402,24 @@ Base on have one descendant heads we ask
> |
> o 11: 'H'
> |
> 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'
> |/
> o 0: 'A'
> @@ -439,24 +439,24 @@ rebase subset
> $ hg tglog
> @ 11: 'H'
> |
> 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'
> |/
> o 0: 'A'
> @@ -480,24 +480,24 @@ rebase subset with multiple head
> |
> | o 11: 'F'
> | |
> | 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'
> |/
> o 0: 'A'
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
More information about the Mercurial-devel
mailing list