[PATCH 4 of 4] rebase: exclude obsoletes without a successor in destination (issue5300)

Augie Fackler raf at durin42.com
Mon Nov 13 18:07:40 EST 2017


On Sun, Nov 12, 2017 at 06:06:37PM +0100, Denis Laxalde wrote:
> # HG changeset patch
> # User Denis Laxalde <denis at laxalde.org>
> # Date 1510489041 -3600
> #      Sun Nov 12 13:17:21 2017 +0100
> # Node ID 25d1891bd300bc527cb7e878532c8512ff5489de
> # Parent  1d16e28225e01d2fac9f31ed058ae1f4c00bb9da
> # EXP-Topic rebase-obsolete
> rebase: exclude obsoletes without a successor in destination (issue5300)

I took patches 1-3. I think this is right. I'm 90% sure this is
right. But I'm a little tired, and so I want Martin's more careful eye
on it if he has time.

>
> In the following example, when trying to rebase 3:: onto 2, the rebase
> will abort with "this rebase will cause divergence from: 4":
>
>     o  7 f
>     |
>     | o  6 e
>     | |
>     | o  5 d'
>     | |
>     x |  4 d (rewritten as 5)
>     |/
>     o  3 c
>     |
>     | o  2 x
>     | |
>     o |  1 b
>     |/
>     o  0 a
>
> By excluding obsolete changesets without a successor in destination (4
> in the example above) and their descendants, we make rebase work in this
> case, thus giving:
>
>     o  11 e
>     |
>     o  10 d'
>     |
>     o  9 c
>     |
>     o  8 b
>     |
>     | o  7 f
>     | |
>     | | x  6 e (rewritten using rebase as 11)
>     | | |
>     | | x  5 d' (rewritten using rebase as 10)
>     | | |
>     | x |  4 d
>     | |/
>     | x  3 c (rewritten using rebase as 9)
>     | |
>     o |  2 x
>     | |
>     | x  1 b (rewritten using rebase as 8)
>     |/
>     o  0 a
>
> where branch 4:: is left behind while branch 5:: is rebased as expected.
>
> The rationale is that users may not be interested in rebasing orphan
> changesets when specifying a rebase set that include them but would
> still want "stable" ones to be rebased. Currently, the user is suggested
> to allow divergence (but probably does not want it) or they must specify
> a rebase set excluding problematic changesets (which might be a bit
> cumbersome). The approach proposed here corresponds to "Option 2" in
> https://www.mercurial-scm.org/wiki/CEDRebase.
>
> .. feature::
>
>    Let 'hg rebase' avoid content-divergence when obsolete changesets are
>    present in the rebase set by skipping possible sources of divergence.
>
>
> We extend _computeobsoletenotrebased() so that it also return a set of
> obsolete changesets in rebaseset without a successor in destination.
> This 'obsoletewithoutsuccessorindestination' is then stored as an
> attribute of rebaseruntime and used in _performrebasesubset() to:
>
> * filter out descendants of these changesets from the revisions to
>   rebase;
> * issue a message about these revisions being skipped.
>
> This only occurs if 'evolution.allowdivergence' option is off and
> 'rebaseskipobsolete' is on.
>
> diff --git a/hgext/rebase.py b/hgext/rebase.py
> --- a/hgext/rebase.py
> +++ b/hgext/rebase.py
> @@ -179,6 +179,7 @@ class rebaseruntime(object):
>          # other extensions
>          self.keepopen = opts.get('keepopen', False)
>          self.obsoletenotrebased = {}
> +        self.obsoletewithoutsuccessorindestination = set()
>
>      @property
>      def repo(self):
> @@ -311,9 +312,10 @@ class rebaseruntime(object):
>          if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
>              return
>          obsoleteset = set(obsoleterevs)
> -        self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
> -                                    obsoleteset, destmap)
> +        self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \
> +            _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
>          skippedset = set(self.obsoletenotrebased)
> +        skippedset.update(self.obsoletewithoutsuccessorindestination)
>          _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
>
>      def _prepareabortorcontinue(self, isabort):
> @@ -419,12 +421,26 @@ class rebaseruntime(object):
>      def _performrebasesubset(self, tr, subset, pos, total):
>          repo, ui, opts = self.repo, self.ui, self.opts
>          sortedrevs = repo.revs('sort(%ld, -topo)', subset)
> +        allowdivergence = self.ui.configbool(
> +            'experimental', 'evolution.allowdivergence')
> +        if not allowdivergence:
> +            sortedrevs -= repo.revs(
> +                'descendants(%ld) and not %ld',
> +                self.obsoletewithoutsuccessorindestination,
> +                self.obsoletewithoutsuccessorindestination,
> +            )
>          for rev in sortedrevs:
>              dest = self.destmap[rev]
>              ctx = repo[rev]
>              desc = _ctxdesc(ctx)
>              if self.state[rev] == rev:
>                  ui.status(_('already rebased %s\n') % desc)
> +            elif (not allowdivergence
> +                  and rev in self.obsoletewithoutsuccessorindestination):
> +                msg = _('note: not rebasing %s and its descendants as '
> +                        'this would cause divergences\n') % desc
> +                repo.ui.status(msg)
> +                self.skipped.add(rev)
>              elif rev in self.obsoletenotrebased:
>                  succ = self.obsoletenotrebased[rev]
>                  if succ is None:
> @@ -1616,11 +1632,16 @@ def _filterobsoleterevs(repo, revs):
>      return set(r for r in revs if repo[r].obsolete())
>
>  def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
> -    """return a mapping obsolete => successor for all obsolete nodes to be
> -    rebased that have a successors in the destination
> +    """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
> +
> +    `obsoletenotrebased` is a mapping mapping obsolete => successor for all
> +    obsolete nodes to be rebased given in `rebaseobsrevs`.
>
> -    obsolete => None entries in the mapping indicate nodes with no successor"""
> +    `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
> +    without a successor in destination.
> +    """
>      obsoletenotrebased = {}
> +    obsoletewithoutsuccessorindestination = set([])
>
>      assert repo.filtername is None
>      cl = repo.changelog
> @@ -1641,8 +1662,17 @@ def _computeobsoletenotrebased(repo, reb
>                  if cl.isancestor(succnode, destnode):
>                      obsoletenotrebased[srcrev] = nodemap[succnode]
>                      break
> +            else:
> +                # Unless 'srcrev' only has one successor and it's not in
> +                # rebaseset, we might skip it and its descendants as it has no
> +                # successor in destination. Otherwise, we'll issue the
> +                # divergence warning and abort.
> +                if (len(successors) > 1
> +                        and any(nodemap[s] in destmap
> +                                for s in successors if s != srcnode)):
> +                    obsoletewithoutsuccessorindestination.add(srcrev)
>
> -    return obsoletenotrebased
> +    return obsoletenotrebased, obsoletewithoutsuccessorindestination
>
>  def summaryhook(ui, repo):
>      if not repo.vfs.exists('rebasestate'):
> diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
> --- a/tests/test-rebase-obsolete.t
> +++ b/tests/test-rebase-obsolete.t
> @@ -987,6 +987,208 @@ Create the changes that we will rebase
>    rebasing 21:7bdc8a87673d "dummy change" (tip)
>    $ cd ..
>
> +Divergence cases due to obsolete changesets
> +-------------------------------------------
> +
> +We should ignore branches with unstable changesets when they are based on an
> +obsolete changeset which successor is in rebase set.
> +
> +  $ hg init divergence
> +  $ cd divergence
> +  $ cat >> .hg/hgrc << EOF
> +  > [extensions]
> +  > strip =
> +  > [alias]
> +  > strip = strip --no-backup --quiet
> +  > [templates]
> +  > instabilities = '{rev}:{node|short} {desc|firstline}{if(instabilities," ({instabilities})")}\n'
> +  > EOF
> +
> +  $ hg debugdrawdag <<EOF
> +  >   e   f
> +  >   |   |
> +  >   d'  d # replace: d -> d'
> +  >    \ /
> +  >     c
> +  >     |
> +  >   x b
> +  >    \|
> +  >     a
> +  > EOF
> +  $ hg log -G -r 'a'::
> +  o  7:1143e9adc121 f
> +  |
> +  | o  6:d60ebfa0f1cb e
> +  | |
> +  | o  5:027ad6c5830d d'
> +  | |
> +  x |  4:76be324c128b d (rewritten using replace as 5:027ad6c5830d)
> +  |/
> +  o  3:a82ac2b38757 c
> +  |
> +  | o  2:630d7c95eff7 x
> +  | |
> +  o |  1:488e1b7e7341 b
> +  |/
> +  o  0:b173517d0057 a
> +
> +
> +Changeset d and its descendants are excluded to avoid divergence of d, which
> +would occur because the successor of d (d') is also in rebaseset. As a
> +consequence f (descendant of d) is left behind.
> +
> +  $ hg rebase -b 'e' -d 'x'
> +  rebasing 1:488e1b7e7341 "b" (b)
> +  rebasing 3:a82ac2b38757 "c" (c)
> +  rebasing 5:027ad6c5830d "d'" (d')
> +  rebasing 6:d60ebfa0f1cb "e" (e)
> +  note: not rebasing 4:76be324c128b "d" (d) and its descendants as this would cause divergences
> +  $ hg log -G -r 'a'::
> +  o  11:eb6d63fc4ed5 e
> +  |
> +  o  10:44d8c724a70c d'
> +  |
> +  o  9:d008e6b4d3fd c
> +  |
> +  o  8:67e8f4a16c49 b
> +  |
> +  | o  7:1143e9adc121 f
> +  | |
> +  | | x  6:d60ebfa0f1cb e (rewritten using rebase as 11:eb6d63fc4ed5)
> +  | | |
> +  | | x  5:027ad6c5830d d' (rewritten using rebase as 10:44d8c724a70c)
> +  | | |
> +  | x |  4:76be324c128b d (rewritten using replace as 5:027ad6c5830d)
> +  | |/
> +  | x  3:a82ac2b38757 c (rewritten using rebase as 9:d008e6b4d3fd)
> +  | |
> +  o |  2:630d7c95eff7 x
> +  | |
> +  | x  1:488e1b7e7341 b (rewritten using rebase as 8:67e8f4a16c49)
> +  |/
> +  o  0:b173517d0057 a
> +
> +  $ hg strip -r 8:
> +
> +If the rebase set has an obsolete (d) with a unique successor (d') outside the
> +rebase set and not in destination, we still get the divergence warning. By
> +allowing divergence, we can perform the rebase.
> +
> +  $ hg rebase -r 'c'::'f' -d 'x'
> +  abort: this rebase will cause divergences from: 76be324c128b
> +  (to force the rebase please set experimental.evolution.allowdivergence=True)
> +  [255]
> +  $ hg rebase --config experimental.evolution.allowdivergence=true -r 'c'::'f' -d 'x'
> +  rebasing 3:a82ac2b38757 "c" (c)
> +  rebasing 4:76be324c128b "d" (d)
> +  rebasing 7:1143e9adc121 "f" (f tip)
> +  $ hg log -G -r 'a':: -T instabilities
> +  o  10:e1744ea07510 f
> +  |
> +  o  9:e2b36ea9a0a0 d (content-divergent)
> +  |
> +  o  8:6a0376de376e c
> +  |
> +  | x  7:1143e9adc121 f
> +  | |
> +  | | o  6:d60ebfa0f1cb e (orphan)
> +  | | |
> +  | | o  5:027ad6c5830d d' (orphan content-divergent)
> +  | | |
> +  | x |  4:76be324c128b d
> +  | |/
> +  | x  3:a82ac2b38757 c
> +  | |
> +  o |  2:630d7c95eff7 x
> +  | |
> +  | o  1:488e1b7e7341 b
> +  |/
> +  o  0:b173517d0057 a
> +
> +  $ hg strip -r 8:
> +
> +(Not skipping obsoletes means that divergence is allowed.)
> +
> +  $ hg rebase --config experimental.rebaseskipobsolete=false -r 'c'::'f' -d 'x'
> +  rebasing 3:a82ac2b38757 "c" (c)
> +  rebasing 4:76be324c128b "d" (d)
> +  rebasing 7:1143e9adc121 "f" (f tip)
> +
> +  $ hg strip -r 0:
> +
> +Similar test on a more complex graph
> +
> +  $ hg debugdrawdag <<EOF
> +  >       g
> +  >       |
> +  >   f   e
> +  >   |   |
> +  >   e'  d # replace: e -> e'
> +  >    \ /
> +  >     c
> +  >     |
> +  >   x b
> +  >    \|
> +  >     a
> +  > EOF
> +  $ hg log -G -r 'a':
> +  o  8:2876ce66c6eb g
> +  |
> +  | o  7:3ffec603ab53 f
> +  | |
> +  x |  6:e36fae928aec e (rewritten using replace as 5:63324dc512ea)
> +  | |
> +  | o  5:63324dc512ea e'
> +  | |
> +  o |  4:76be324c128b d
> +  |/
> +  o  3:a82ac2b38757 c
> +  |
> +  | o  2:630d7c95eff7 x
> +  | |
> +  o |  1:488e1b7e7341 b
> +  |/
> +  o  0:b173517d0057 a
> +
> +  $ hg rebase -b 'f' -d 'x'
> +  rebasing 1:488e1b7e7341 "b" (b)
> +  rebasing 3:a82ac2b38757 "c" (c)
> +  rebasing 5:63324dc512ea "e'" (e')
> +  rebasing 7:3ffec603ab53 "f" (f)
> +  rebasing 4:76be324c128b "d" (d)
> +  note: not rebasing 6:e36fae928aec "e" (e) and its descendants as this would cause divergences
> +  $ hg log -G -r 'a':
> +  o  13:a1707a5b7c2c d
> +  |
> +  | o  12:ef6251596616 f
> +  | |
> +  | o  11:b6f172e64af9 e'
> +  |/
> +  o  10:d008e6b4d3fd c
> +  |
> +  o  9:67e8f4a16c49 b
> +  |
> +  | o  8:2876ce66c6eb g
> +  | |
> +  | | x  7:3ffec603ab53 f (rewritten using rebase as 12:ef6251596616)
> +  | | |
> +  | x |  6:e36fae928aec e (rewritten using replace as 5:63324dc512ea)
> +  | | |
> +  | | x  5:63324dc512ea e' (rewritten using rebase as 11:b6f172e64af9)
> +  | | |
> +  | x |  4:76be324c128b d (rewritten using rebase as 13:a1707a5b7c2c)
> +  | |/
> +  | x  3:a82ac2b38757 c (rewritten using rebase as 10:d008e6b4d3fd)
> +  | |
> +  o |  2:630d7c95eff7 x
> +  | |
> +  | x  1:488e1b7e7341 b (rewritten using rebase as 9:67e8f4a16c49)
> +  |/
> +  o  0:b173517d0057 a
> +
> +
> +  $ cd ..
> +
>  Rebase merge where successor of one parent is equal to destination (issue5198)
>
>    $ hg init p1-succ-is-dest
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


More information about the Mercurial-devel mailing list