overlay: add --noncontiguous argument to override contiguous check (Bug 1375901). r?gps draft
authorSteven MacLeod <smacleod@mozilla.com>
Fri, 23 Jun 2017 12:52:25 -0400
changeset 11308 770a616a582fa7e408b558001ce3be718455c0cb
parent 11304 a0cc955321acc9a716d582587755262779fad091
child 11309 df4b92f316dac2d21de8090e45dc87770c93385a
push id1717
push usersmacleod@mozilla.com
push dateFri, 23 Jun 2017 17:43:52 +0000
reviewersgps
bugs1375901
overlay: add --noncontiguous argument to override contiguous check (Bug 1375901). r?gps In production for servo overlay we're using a non-contiguous head revset so that we may filter out gecko backouts relanding from github. This change adds a hacky --noncontiguous argument to override this check so we can get things moving again quickly. In the long term a better solution should be devised. MozReview-Commit-ID: 2KooWyjohxm
hgext/overlay/__init__.py
hgext/overlay/tests/test-overlay-filtered-dag.t
--- a/hgext/overlay/__init__.py
+++ b/hgext/overlay/__init__.py
@@ -133,25 +133,29 @@ def _overlayrev(sourcerepo, sourceurl, s
 
     memctx = context.memctx(destrepo, parents, sourcectx.description(),
                             files, filectxfn, user=sourcectx.user(),
                             date=sourcectx.date(), extra=extra)
 
     return memctx.commit()
 
 
-def _dooverlay(sourcerepo, sourceurl, sourcerevs, destrepo, destctx, prefix):
+def _dooverlay(sourcerepo, sourceurl, sourcerevs, destrepo, destctx, prefix,
+               noncontiguous):
     """Overlay changesets from one repository into another.
 
     ``sourcerevs`` (iterable of revs) from ``sourcerepo`` will effectively
     be replayed into ``destrepo`` on top of ``destctx``. File paths will be
     added to the directory ``prefix``.
 
     ``sourcerevs`` may include revisions that have already been overlayed.
     If so, overlay will resume at the first revision not yet processed.
+
+    ``noncontigous`` removes the restriction that sourcerevs must be a
+    contiguous DAG.
     """
     assert prefix
     prefix = prefix.rstrip('/') + '/'
 
     ui = destrepo.ui
 
     sourcerevs.sort()
 
@@ -160,18 +164,19 @@ def _dooverlay(sourcerepo, sourceurl, so
     left.remove(sourcerevs.last())
     for ctx in sourcerepo[sourcerevs.last()].ancestors():
         if not left:
             break
 
         try:
             left.remove(ctx.rev())
         except KeyError:
-            raise error.Abort(_('source revisions must be part of contiguous '
-                                'DAG range'))
+            if not noncontiguous:
+                raise error.Abort(
+                    _('source revisions must be part of contiguous DAG range'))
 
     if left:
         raise error.Abort(_('source revisions must be part of same DAG head'))
 
     sourcerevs = list(sourcerevs)
 
     sourcecl = sourcerepo.changelog
     allsourcehexes = set(hex(sourcecl.node(rev)) for rev in
@@ -204,16 +209,17 @@ def _dooverlay(sourcerepo, sourceurl, so
                        (short(lastsourcectx.node()), short(ctx.node()),
                         idx, len(sourcerevs))))
             sourcerevs = sourcerevs[idx:]
             break
         except ValueError:
             # Else the changeset in the destination isn't in the incoming set.
             # This is OK iff the destination changeset is a conversion of
             # the parent of the first incoming changeset.
+            # TODO: This assumption doesn't hold with noncontiguous=True
             firstsourcectx = sourcerepo[sourcerevs[0]]
             if firstsourcectx.p1().hex() == overlayed:
                 break
 
             raise error.Abort(_('first source changeset (%s) is not a child '
                                 'of last overlayed changeset (%s)') % (
                 short(firstsourcectx.node()), short(bin(overlayed))))
 
@@ -282,34 +288,36 @@ def _mirrorrepo(ui, repo, url):
 
     return mirrorrepo
 
 
 @command('overlay', [
     ('d', 'dest', '', _('destination changeset on top of which to overlay '
                         'changesets')),
     ('', 'into', '', _('directory in destination in which to add files')),
+    ('', 'noncontiguous', False, _('allow non continuous dag heads')),
 ], _('[-d REV] SOURCEURL [REVS]'))
-def overlay(ui, repo, sourceurl, revs=None, dest=None, into=None):
+def overlay(ui, repo, sourceurl, revs=None, dest=None, into=None,
+            noncontiguous=False):
     """Integrate contents of another repository.
 
     This command essentially replays changesets from another repository into
     this one. Unlike a simple pull + rebase, the files from the remote
     repository are "overlayed" or unioned with the contents of the destination
     repository.
 
     The functionality of this command is nearly identical to what ``hg
     transplant`` provides. However, the internal mechanism varies
     substantially.
 
     There are currently several restrictions to what can be imported:
 
     * The imported changesets must be in a single DAG head
     * The imported changesets (as evaluated by ``REVS``) must be a contiguous
-      DAG range.
+      DAG range (Unless --noncontiguous is passed).
     * Importing merges is not supported.
     * The state of the files in the destination directory/changeset must
       exactly match the last imported changeset.
 
     That last point is important: it means that this command can effectively
     only be used for unidirectional syncing. In other words, the source
     repository must be the single source of all changes to the destination
     directory.
@@ -336,9 +344,10 @@ def overlay(ui, repo, sourceurl, revs=No
     if dest:
         destctx = repo[dest]
     else:
         destctx = repo['tip']
 
     # Backdoor for testing to force static URL.
     sourceurl = ui.config('overlay', 'sourceurl', sourceurl)
 
-    _dooverlay(sourcerepo, sourceurl, sourcerevs, repo, destctx, into)
+    _dooverlay(sourcerepo, sourceurl, sourcerevs, repo, destctx, into,
+               noncontiguous)
new file mode 100644
--- /dev/null
+++ b/hgext/overlay/tests/test-overlay-filtered-dag.t
@@ -0,0 +1,55 @@
+  $ . $TESTDIR/hgext/overlay/tests/helpers.sh
+
+  $ hg init empty
+  $ hg -R empty serve -d --pid-file hg.pid -p $HGPORT
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg init repo0
+  $ cd repo0
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  $ echo 1 > foo
+  $ hg commit -m 'head 1 commit 1'
+  $ echo 2 > foo
+  $ hg commit -m 'head 1 commit 2'
+  $ echo 3 > foo
+  $ hg commit -m 'head 1 commit 3 FILTERED'
+  $ echo 4 > foo
+  $ hg commit -m 'head 1 commit 4'
+  $ hg log -G -T '{node|short} {desc}'
+  @  eebf284459b0 head 1 commit 4
+  |
+  o  6dfe620a17bb head 1 commit 3 FILTERED
+  |
+  o  38627e51950d head 1 commit 2
+  |
+  o  eb87a779cc67 head 1 commit 1
+  |
+  o  af1e0a150cd4 initial
+  
+
+  $ hg serve -d --pid-file hg.pid -p $HGPORT1
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ cd ..
+
+  $ hg init dest
+  $ cd dest
+
+A non-contiguous dag range will fail to overlay.
+  $ hg overlay http://localhost:$HGPORT1 'not keyword("FILTERED")' --into prefix
+  pulling http://localhost:$HGPORT1 into $TESTTMP/dest/.hg/localhost~3a* (glob)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 1 files
+  abort: source revisions must be part of contiguous DAG range
+  [255]
+
+Passing --noncontiguous should allow a non-contiguous dag range where some of
+the commits have been filtered.
+  $ hg overlay http://localhost:$HGPORT1 'not keyword("FILTERED")' --into prefix --noncontiguous
+  af1e0a150cd4 -> 8e52bf8e668a: initial
+  eb87a779cc67 -> 452dcbcc9fb9: head 1 commit 1
+  38627e51950d -> ccc09fef5c59: head 1 commit 2
+  eebf284459b0 -> ed781cf9ab85: head 1 commit 4