<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>