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