vcssync: command for performing hg repo overlaying (bug 1331697); r?glob draft
authorGregory Szorc <gps@mozilla.com>
Wed, 08 Feb 2017 14:25:10 -0800
changeset 10354 81a4944e6295ba1a168ffe8ddf95cc4d8bade084
parent 10353 a1f5b0167ae86324fc64152c78b6743dcd39b246
push id1519
push userbmo:gps@mozilla.com
push dateThu, 09 Feb 2017 02:56:29 +0000
reviewersglob
bugs1331697
vcssync: command for performing hg repo overlaying (bug 1331697); r?glob Now that we have a low-level `hg overlay` command, we need to put it to use. This commit introduces a Python API and CLI program for invoking `hg overlay`. It lives at a layer above `hg overlay` and provides functionality useful for the VCS syncing service. Although, the added value seems somewhat minimal: I'm halfway tempted to roll the functionality into `hg overlay`. The reason I didn't is because I originally envisioned `hg overlay` being a low-level "plumbing" command and not a core part of a version control "syncing" tool per se. Anyway, the added routine is basically a specialized version of Autoland. You give it URLs to repos, the revision and sub-directory in the destination for the overlay, and it does the rest. It can even push the result when it is done. `hg overlay` has no persistent state in the destination repository. Instead, it uses changeset metadata to track what was processed. This means that the overlay is mostly deterministic (only thing that should change is parents the overlay is operated on). The functionality in this commit relies on that mostly-deterministic behavior. One of the first things done after invocation is to `hg strip` mutable changesets. So if there were changesets from a previous `hg overlay` sitting around, they are wiped away and the operation is performed again. This conveniently means that intermittent failures due to tree closures, push races, etc just need to be retried and things should recover. Some of this retry logic is built into the CLI interface. It isn't as robust as Autoland. But I think that's OK for a first iteration. MozReview-Commit-ID: KJi6b0AXVSp
vcssync/mozvcssync/cli.py
vcssync/mozvcssync/overlay.py
vcssync/mozvcssync/util.py
vcssync/setup.py
vcssync/tests/helpers.sh
vcssync/tests/test-overlay-hg-basic.t
vcssync/tests/test-overlay-hg-clone-missing.t
vcssync/tests/test-overlay-hg-dest-mismatch.t
vcssync/tests/test-overlay-hg-new-both.t
vcssync/tests/test-overlay-hg-new-dest.t
vcssync/tests/test-overlay-hg-new-source.t
vcssync/tests/test-overlay-hg-push-fail.t
vcssync/tests/test-overlay-hg-push-lingering.t
vcssync/tests/test-overlay-hg-push-race.t
vcssync/tests/test-overlay-hg-push-result.t
vcssync/tests/test-overlay-hg-strip.t
--- a/vcssync/mozvcssync/cli.py
+++ b/vcssync/mozvcssync/cli.py
@@ -16,16 +16,21 @@ from .git2hg import (
     linearize_git_repo_to_hg,
 )
 from .gitrewrite import (
     RewriteError,
 )
 from .gitrewrite.linearize import (
     linearize_git_repo,
 )
+from .overlay import (
+    overlay_hg_repos,
+    PushRaceError,
+    PushRemoteFail,
+)
 
 
 logger = logging.getLogger(__name__)
 
 
 LINEARIZE_GIT_ARGS = [
     (('--exclude-dir',), dict(action='append', dest='exclude_dirs',
                               help='Directory to exclude from rewritten '
@@ -178,8 +183,66 @@ def linearize_git_to_hg():
             args.git_repo_path,
             args.hg_repo_path,
             **kwargs)
     except RewriteError as e:
         logger.error('abort: %s' % str(e))
         sys.exit(1)
     except subprocess.CalledProcessError:
         sys.exit(1)
+
+
+def overlay_hg_repos_cli():
+    # Unbuffer stdout.
+    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--into', required=True,
+                        help='Subdirectory into which changesets will be '
+                              'applied')
+    parser.add_argument('source_repo_url',
+                        help='URL of repository whose changesets will be '
+                             'overlayed')
+    parser.add_argument('dest_repo_url',
+                        help='URL of repository where changesets will be '
+                             'overlayed')
+    parser.add_argument('dest_repo_path',
+                        help='Local path to clone of <dest_repo_url>')
+    parser.add_argument('--result-push-url',
+                        help='URL where to push the overlayed result')
+
+    args = parser.parse_args()
+
+    configure_logging()
+
+    MAX_ATTEMPTS = 3
+    attempt = 0
+
+    while attempt < MAX_ATTEMPTS:
+        attempt += 1
+        try:
+            overlay_hg_repos(
+                args.source_repo_url,
+                args.dest_repo_url,
+                args.dest_repo_path,
+                dest_prefix=args.into,
+                result_push_url=args.result_push_url)
+            sys.exit(0)
+        except PushRaceError:
+            logger.warn('likely push race on attempt %d/%d' % (
+                attempt, MAX_ATTEMPTS))
+            if attempt < MAX_ATTEMPTS:
+                logger.warn('retrying immediately...')
+        except PushRemoteFail:
+            logger.warn('push failed by remote on attempt %d/%d' % (
+                attempt, MAX_ATTEMPTS))
+            logger.warn('giving up since retry is likely futile')
+            break
+        except hglib.error.CommandError:
+            logger.error('abort: hg command failed')
+            sys.exit(1)
+        except Exception as e:
+            logger.exception('abort: %s' % str(e))
+            sys.exit(1)
+
+    logger.error('overlay not successful after %d attempts; try again '
+                 'later' % attempt)
+    sys.exit(1)
new file mode 100644
--- /dev/null
+++ b/vcssync/mozvcssync/overlay.py
@@ -0,0 +1,140 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, unicode_literals
+
+import logging
+import os
+import subprocess
+
+import hglib
+
+from .util import (
+    run_hg,
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+class PushRaceError(Exception):
+    """Raised when a push fails due to new heads on a remote."""
+
+
+class PushRemoteFail(Exception):
+    """Raised when a push fails due to an error on the remote."""
+
+
+def overlay_hg_repos(source_repo_url, dest_repo_url, dest_repo_path,
+                     dest_prefix, source_rev=None, dest_rev='tip',
+                     result_push_url=None):
+    """Overlay changesets from an hg repo into a sub-directory of another.
+
+    This function will take changesets from the Mercurial repo
+    at ``source_url`` and apply them to the Mercurial repo at
+    ``dest_repo_url``, rewriting file paths so they are stored in
+    the directory ``dest_prefix`` of the destination repository.
+
+    A copy of ``dest_repo_url`` is managed at ``dest_repo_path``,
+    which is where all the activity occurs.
+
+    If ``source_rev`` is specified, it is evaluated as a revset
+    against ``source_repo_url`` and only the returned changesets
+    will be converted. If not defined, the default used by
+    ``hg overlay`` is used.
+
+    ``dest_rev`` is the revision in ``dest_repo_url`` on top of which
+    changesets should be overlayed. By default, the ``tip`` of the
+    repository is used. This will have undefined behavior if the repo
+    has multiple heads. You have been warned.
+    """
+    if not os.path.exists(dest_repo_path):
+        logger.warn('%s does not exist; cloning %s' % (
+                    dest_repo_path, dest_repo_url))
+        subprocess.check_call([hglib.HGPATH, b'clone', b'--noupdate',
+                               b'--pull', dest_repo_url, dest_repo_path])
+
+    configs = (
+        b'extensions.strip=',
+    )
+
+    with hglib.open(dest_repo_path, 'utf-8', configs) as hrepo:
+        # Purge local repo of unwanted changesets.
+        try:
+            run_hg(logger, hrepo,
+                   [b'strip', b'--no-backup', b'-r', b'not public()'])
+        except hglib.error.CommandError as e:
+            if b'empty revision set' not in e.out:
+                raise
+            logger.warn('(ignoring strip failure)')
+
+        # Resolve the destination revision.
+        logger.warn('resolving destination revision: %s' % dest_rev)
+        out = run_hg(logger, hrepo,
+                     [b'identify', dest_repo_url, b'-r', dest_rev])
+
+        out = out.split()[0]
+        if len(out) != 12:
+            raise Exception('%s did not resolve to 12 character node' %
+                            dest_rev)
+
+        dest_rev = out
+
+        if dest_rev not in hrepo:
+            logger.warn('pulling %s to obtain %s' % (dest_repo_url, dest_rev))
+            run_hg(logger, hrepo, [b'pull', b'-r', dest_rev, dest_repo_url])
+
+        dest_node = hrepo[dest_rev].node()
+        old_tip = hrepo[b'tip']
+
+        # The destination revision is now present locally. Commence the overlay!
+        args = [
+            b'overlay',
+            source_repo_url,
+            b'--into', dest_prefix,
+            b'-d', dest_node,
+        ]
+        if source_rev:
+            args.append(source_rev)
+
+        logger.warn('commencing overlay of %s' % source_repo_url)
+        run_hg(logger, hrepo, args)
+
+        new_tip = hrepo[b'tip']
+        if new_tip.rev() == old_tip.rev():
+            logger.warn('no changesets overlayed')
+            return
+
+        new_count = new_tip.rev() - old_tip.rev()
+        logger.warn('%d new changesets; new tip is %s' % (
+                    new_count, new_tip.node()))
+
+        # As a sanity check, verify the new changesets are only in a single
+        # head.
+        new_heads = hrepo.log(revrange=b'heads(%d:)' % (old_tip.rev() + 1))
+        if len(new_heads) != 1:
+            raise Exception('multiple new heads after overlay; you likely '
+                            'found a bug!')
+
+        if not result_push_url:
+            return
+
+        logger.warn('pushing %d new changesets on head %s to %s' % (
+                    new_count, new_tip.node(), result_push_url))
+        for rev in hrepo.log(revrange=b'%d::%s' % (old_tip.rev() + 1,
+                                                   new_tip.node())):
+            logger.warn('%s:%s: %s' % (rev[0], rev[1][0:12],
+                                       rev[5].splitlines()[0]))
+
+        try:
+            run_hg(logger, hrepo,
+                   [b'push', b'-r', new_tip.node(), result_push_url])
+        except hglib.error.CommandError as e:
+            # Detect likely push race and convert exception so caller
+            # can retry.
+            if b'push creates new remote head' in e.out:
+                raise PushRaceError(e.out)
+            elif b'push failed on remote' in e.out:
+                raise PushRemoteFail(e.out)
+            raise
new file mode 100644
--- /dev/null
+++ b/vcssync/mozvcssync/util.py
@@ -0,0 +1,27 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, unicode_literals
+
+import pipes
+
+import hglib
+
+
+def run_hg(logger, client, args):
+    """Run a Mercurial command through hgclient and log output."""
+    logger.warn('executing: hg %s' % ' '.join(map(pipes.quote, args)))
+    out = hglib.util.BytesIO()
+
+    def write(data):
+        logger.warn(b'hg> %s' % data.rstrip())
+        out.write(data)
+
+    out_channels = {b'o': write, b'e': write}
+    ret = client.runcommand(args, {}, out_channels)
+
+    if ret:
+        raise hglib.error.CommandError(args, ret, out.getvalue(), b'')
+
+    return out.getvalue()
--- a/vcssync/setup.py
+++ b/vcssync/setup.py
@@ -13,16 +13,17 @@ setup(
         'Intended Audience :: Developers',
         'Programming Language :: Python :: 2.7',
     ],
     packages=find_packages(),
     entry_points={
         'console_scripts': [
             'linearize-git=mozvcssync.cli:linearize_git',
             'linearize-git-to-hg=mozvcssync.cli:linearize_git_to_hg',
+            'overlay-hg-repos=mozvcssync.cli:overlay_hg_repos_cli',
             'servo-pulse-listen=mozvcssync.servo:pulse_daemon',
         ],
     },
     install_requires=[
         'dulwich>=0.16',
         'github3.py>=0.9.6',
         'kombu>=3.0.37',
         'Mercurial>=4.0',
--- a/vcssync/tests/helpers.sh
+++ b/vcssync/tests/helpers.sh
@@ -108,8 +108,49 @@ EOF
     git checkout master
     git merge head2
     echo 5 > foo
     git add foo
     git commit -m 'dummy commit 1 after merge'
 
     cd $here
 }
+
+standardoverlayenv() {
+    cat >> $HGRCPATH <<EOF
+[extensions]
+overlay=$TESTDIR/hgext/overlay
+
+[overlay]
+sourceurl = http://example.com/dummy-overlay-source
+EOF
+
+    mkdir server
+    cd server
+    hg init overlay-source
+    cd overlay-source
+    echo source-file0 > source-file0
+    echo source-file1 > source-file1
+    hg commit -A -m 'initial - add source-file0 and source-file1'
+    mkdir dir0
+    echo 1 > dir0/file0
+    hg commit -A -m 'add dir0/file0'
+    cd ..
+
+    hg init overlay-dest
+    cd overlay-dest
+    touch dest-file0 dest-file1
+    hg commit -A -m 'initial in dest'
+    cd ..
+
+    cat > hgweb.conf <<EOF
+[paths]
+/ = $TESTTMP/server/*
+[web]
+refreshinterval = -1
+allow_push = *
+push_ssl = False
+EOF
+
+    hg serve -d -p $HGPORT --pid-file hg.pid --web-conf hgweb.conf
+    cat hg.pid >> $DAEMON_PIDS
+    cd ..
+}
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-basic.t
@@ -0,0 +1,118 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir
+  repo does not exist; cloning http://localhost:$HGPORT/overlay-dest
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 2 changes to 2 files
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/repo/.hg/localhost~3a*__overlay-source (glob)
+  hg> requesting all changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 3 changes to 3 files
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  $ cd repo
+
+Overlayed changesets exist in proper location, have proper paths
+
+  $ hg log -G --debug -p
+  o  changeset:   2:eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  |  tag:         tip
+  |  phase:       draft
+  |  parent:      1:67c9543981c6d2001ab6f30dd7fbe83c3d55d33b
+  |  parent:      -1:0000000000000000000000000000000000000000
+  |  manifest:    2:822b75cff23425e6d024bd2da11312cc68579a0c
+  |  user:        Test User <someone@example.com>
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  files+:      subdir/dir0/file0
+  |  extra:       branch=default
+  |  extra:       subtree_revision=d92cc0ff6f1a1afa1d57e8c11c75874bbd991058
+  |  extra:       subtree_source=http://example.com/dummy-overlay-source
+  |  description:
+  |  add dir0/file0
+  |
+  |
+  |  diff -r 67c9543981c6d2001ab6f30dd7fbe83c3d55d33b -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 subdir/dir0/file0
+  |  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/subdir/dir0/file0	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -0,0 +1,1 @@
+  |  +1
+  |
+  o  changeset:   1:67c9543981c6d2001ab6f30dd7fbe83c3d55d33b
+  |  phase:       draft
+  |  parent:      0:88dd2a5005e6e795674d8253cec4dde9f9f77457
+  |  parent:      -1:0000000000000000000000000000000000000000
+  |  manifest:    1:4620d3269b1fa921c9e29f83c76ba5432642e86b
+  |  user:        Test User <someone@example.com>
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  files+:      subdir/source-file0 subdir/source-file1
+  |  extra:       branch=default
+  |  extra:       subtree_revision=76f0fc85e215d86d04307b17c13356ad452d2297
+  |  extra:       subtree_source=http://example.com/dummy-overlay-source
+  |  description:
+  |  initial - add source-file0 and source-file1
+  |
+  |
+  |  diff -r 88dd2a5005e6e795674d8253cec4dde9f9f77457 -r 67c9543981c6d2001ab6f30dd7fbe83c3d55d33b subdir/source-file0
+  |  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/subdir/source-file0	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -0,0 +1,1 @@
+  |  +source-file0
+  |  diff -r 88dd2a5005e6e795674d8253cec4dde9f9f77457 -r 67c9543981c6d2001ab6f30dd7fbe83c3d55d33b subdir/source-file1
+  |  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/subdir/source-file1	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -0,0 +1,1 @@
+  |  +source-file1
+  |
+  o  changeset:   0:88dd2a5005e6e795674d8253cec4dde9f9f77457
+     phase:       public
+     parent:      -1:0000000000000000000000000000000000000000
+     parent:      -1:0000000000000000000000000000000000000000
+     manifest:    0:03ae3abe8fc90de8aa92cb4fa79854b491a13045
+     user:        Test User <someone@example.com>
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     files+:      dest-file0 dest-file1
+     extra:       branch=default
+     description:
+     initial in dest
+  
+  
+  
+
+Running again will strip overlayed changesets (they aren't public)
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   . --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+
+  $ hg log -G -T '{node|short} {desc}'
+  o  eaf64eb11964 add dir0/file0
+  |
+  o  67c9543981c6 initial - add source-file0 and source-file1
+  |
+  o  88dd2a5005e6 initial in dest
+  
+
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-clone-missing.t
@@ -0,0 +1,44 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+Local clone will be created if necessary
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   local_path --into subdir
+  local_path does not exist; cloning http://localhost:$HGPORT/overlay-dest
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 2 changes to 2 files
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/local_path/.hg/localhost~3a*__overlay-source (glob)
+  hg> requesting all changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 3 changes to 3 files
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+
+Again for good measure
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   local_path --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-dest-mismatch.t
@@ -0,0 +1,88 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into destdir > /dev/null 2>&1
+
+  $ hg -R repo push
+  pushing to http://localhost:$HGPORT/overlay-dest
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 2 changesets with 3 changes to 3 files
+
+Create a changeset in destination directory with unexpected changes
+
+  $ cd server/overlay-dest
+  $ hg -q up tip
+  $ echo unwanted > destdir/unwanted-file
+  $ hg -q commit -A -m 'add unwanted file to destdir'
+  $ cd ../overlay-source
+  $ echo new > new-file
+  $ hg -q commit -A -m 'new source changeset'
+  $ cd ../..
+
+Attempting an incremental overlay will fail due to state mismatch in
+destination directory
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into destdir
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 868ababf5511
+  pulling http://localhost:$HGPORT/overlay-dest to obtain 868ababf5511
+  executing: hg pull -r 868ababf5511 http://localhost:$HGPORT/overlay-dest
+  hg> pulling from http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 1 changesets with 1 changes to 1 files
+  hg> (run 'hg update' to get a working copy)
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into destdir -d 868ababf5511149027ca40e5de059e3a88c32a3c
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/repo/.hg/localhost~3a*__overlay-source (glob)
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 1 changesets with 1 changes to 1 files
+  hg> d92cc0ff6f1a already processed as 1467125e7dd1; skipping 2/3 revisions
+  hg> abort: files mismatch between source and destiation: unwanted-file
+  hg> (destination must match previously imported changeset (d92cc0ff6f1a) exactly)
+  abort: hg command failed
+  [1]
+
+We can correct the issue by reconciling the state in dest
+
+  $ cd server/overlay-dest
+  $ hg rm destdir/unwanted-file
+  $ hg commit -m 'remove unwanted-file from dest'
+  $ cd ../..
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into destdir
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 0a081fabba0d
+  pulling http://localhost:$HGPORT/overlay-dest to obtain 0a081fabba0d
+  executing: hg pull -r 0a081fabba0d http://localhost:$HGPORT/overlay-dest
+  hg> pulling from http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 1 changesets with 0 changes to 0 files
+  hg> (run 'hg update' to get a working copy)
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into destdir -d 0a081fabba0d02fd0ebead1c5ce1256da71866ea
+  hg> d92cc0ff6f1a already processed as 1467125e7dd1; skipping 2/3 revisions
+  hg> 74beb83990f0 -> 5065bae0b434: new source changeset
+  1 new changesets; new tip is 5065bae0b434149f4937727d9715b9e1490bb51a
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-new-both.t
@@ -0,0 +1,60 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir > /dev/null 2>&1
+
+  $ hg -R repo push
+  pushing to http://localhost:$HGPORT/overlay-dest
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 2 changesets with 3 changes to 3 files
+
+Add new changesets in both source and dest
+
+  $ cd server/overlay-source
+  $ echo new-source1 > new-source
+  $ hg -q commit -A -m 'new source 1'
+  $ echo new-source > new-source
+  $ hg commit -m 'new source 2'
+  $ cd ../overlay-dest
+  $ hg -q up tip
+  $ echo new-dest1 > new-dest
+  $ hg -q commit -A -m 'new dest 1'
+  $ echo new-dest2 > new-dest
+  $ hg commit -m 'new dest 2'
+  $ cd ../..
+
+Overlay with both new dest and source will pull dest and apply new sources
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 05e6f02d8e8c
+  pulling http://localhost:$HGPORT/overlay-dest to obtain 05e6f02d8e8c
+  executing: hg pull -r 05e6f02d8e8c http://localhost:$HGPORT/overlay-dest
+  hg> pulling from http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 2 changes to 1 files
+  hg> (run 'hg update' to get a working copy)
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 05e6f02d8e8c47eea023572bb08ad29f878936df
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/repo/.hg/localhost~3a*__overlay-source (glob)
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 2 changes to 1 files
+  hg> d92cc0ff6f1a already processed as eaf64eb11964; skipping 2/4 revisions
+  hg> 03f307e60484 -> 1f5ce5f190a2: new source 1
+  hg> fabffa48ea9f -> fc9f4bdac504: new source 2
+  2 new changesets; new tip is fc9f4bdac504bf7da1920a4449b012837e99c152
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-new-dest.t
@@ -0,0 +1,44 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir > /dev/null 2>&1
+
+New changeset in dest will be pulled and overlay will reperformed on it
+
+  $ cd server/overlay-dest
+  $ echo new-dest > new-dest
+  $ hg -q commit -A -m 'new changeset in dest'
+  $ cd ../..
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 7d4c615194ec
+  pulling http://localhost:$HGPORT/overlay-dest to obtain 7d4c615194ec
+  executing: hg pull -r 7d4c615194ec http://localhost:$HGPORT/overlay-dest
+  hg> pulling from http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 1 changesets with 1 changes to 1 files
+  hg> (run 'hg update' to get a working copy)
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 7d4c615194ec642cb4f0ff9be89a536db8075e02
+  hg> 76f0fc85e215 -> adc6459339d2: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> d35c1c7442f0: add dir0/file0
+  2 new changesets; new tip is d35c1c7442f0f2ed4478ca1e1bafebb4ac98c9e3
+
+  $ hg -R repo log -G -T '{node|short} {desc}'
+  o  d35c1c7442f0 add dir0/file0
+  |
+  o  adc6459339d2 initial - add source-file0 and source-file1
+  |
+  o  7d4c615194ec new changeset in dest
+  |
+  o  88dd2a5005e6 initial in dest
+  
+
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-new-source.t
@@ -0,0 +1,74 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir > /dev/null 2>&1
+
+  $ hg -R repo push
+  pushing to http://localhost:$HGPORT/overlay-dest
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 2 changesets with 3 changes to 3 files
+
+New changeset in source will be incrementally applied
+
+  $ cd server/overlay-source
+  $ echo new-source > new-source
+  $ hg -q commit -A -m 'add new-source'
+  $ cd ../..
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> eaf64eb11964
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/repo/.hg/localhost~3a*__overlay-source (glob)
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 1 changesets with 1 changes to 1 files
+  hg> d92cc0ff6f1a already processed as eaf64eb11964; skipping 2/3 revisions
+  hg> becea3ef593b -> 21cdbe8f0971: add new-source
+  1 new changesets; new tip is 21cdbe8f0971d8ec7d64fa34a59ea69e2936ce2e
+
+  $ hg -R repo log -G -T '{node|short} {desc}'
+  o  21cdbe8f0971 add new-source
+  |
+  o  eaf64eb11964 add dir0/file0
+  |
+  o  67c9543981c6 initial - add source-file0 and source-file1
+  |
+  o  88dd2a5005e6 initial in dest
+  
+
+  $ hg -R repo push
+  pushing to http://localhost:$HGPORT/overlay-dest
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+
+No-op after publish
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 21cdbe8f0971
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 21cdbe8f0971d8ec7d64fa34a59ea69e2936ce2e
+  hg> becea3ef593b already processed as 21cdbe8f0971; skipping 3/3 revisions
+  hg> no source revisions left to process
+  no changesets overlayed
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-push-fail.t
@@ -0,0 +1,43 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir >/dev/null 2>&1
+
+Create a server-side hook that fails the push to simulate a failure
+
+  $ cat >> server/overlay-dest/.hg/hgrc << EOF
+  > [hooks]
+  > pretxnchangegroup.fail = sh -c "echo 'can not push'; exit 1"
+  > EOF
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir --result-push-url http://localhost:$HGPORT/overlay-dest
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  pushing 2 new changesets on head eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 to http://localhost:$HGPORT/overlay-dest
+  1:67c9543981c6: initial - add source-file0 and source-file1
+  2:eaf64eb11964: add dir0/file0
+  executing: hg push -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 http://localhost:$HGPORT/overlay-dest
+  hg> pushing to http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> remote: adding changesets
+  hg> remote: adding manifests
+  hg> remote: adding file changes
+  hg> remote: added 2 changesets with 3 changes to 3 files
+  hg> remote: can not push
+  hg> remote: transaction abort!
+  hg> remote: rollback completed
+  hg> remote: pretxnchangegroup.fail hook exited with status 1
+  hg> abort: push failed on remote
+  push failed by remote on attempt 1/3
+  giving up since retry is likely futile
+  overlay not successful after 1 attempts; try again later
+  [1]
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-push-lingering.t
@@ -0,0 +1,30 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+Leave overlayed changesets in local repo then attempt to push in a later
+invocation. This simulates failure to push on a previous run.
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir >/dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir --result-push-url http://localhost:$HGPORT/overlay-dest
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  pushing 2 new changesets on head eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 to http://localhost:$HGPORT/overlay-dest
+  1:67c9543981c6: initial - add source-file0 and source-file1
+  2:eaf64eb11964: add dir0/file0
+  executing: hg push -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 http://localhost:$HGPORT/overlay-dest
+  hg> pushing to http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> remote: adding changesets
+  hg> remote: adding manifests
+  hg> remote: adding file changes
+  hg> remote: added 2 changesets with 3 changes to 3 files
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-push-race.t
@@ -0,0 +1,83 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir >/dev/null 2>&1
+
+Create a new dest-like repo and add a changeset to it.
+This simulates losing a push race to another client.
+
+  $ hg clone server/overlay-dest server/push-race
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd server/push-race
+  $ echo raced > race
+  $ hg -q commit -A -m 'simulate push race'
+  $ cd ../..
+
+Now push to the dest-like repo in a way that would create a new head
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir --result-push-url http://localhost:$HGPORT/push-race
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  pushing 2 new changesets on head eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 to http://localhost:$HGPORT/push-race
+  1:67c9543981c6: initial - add source-file0 and source-file1
+  2:eaf64eb11964: add dir0/file0
+  executing: hg push -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 http://localhost:$HGPORT/push-race
+  hg> pushing to http://localhost:$HGPORT/push-race
+  hg> searching for changes
+  hg> remote has heads on branch 'default' that are not known locally: 9482a15d6fcd
+  hg> abort: push creates new remote head eaf64eb11964!
+  hg> (pull and merge or see 'hg help push' for details about pushing new heads)
+  likely push race on attempt 1/3
+  retrying immediately...
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  pushing 2 new changesets on head eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 to http://localhost:$HGPORT/push-race
+  1:67c9543981c6: initial - add source-file0 and source-file1
+  2:eaf64eb11964: add dir0/file0
+  executing: hg push -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 http://localhost:$HGPORT/push-race
+  hg> pushing to http://localhost:$HGPORT/push-race
+  hg> searching for changes
+  hg> remote has heads on branch 'default' that are not known locally: 9482a15d6fcd
+  hg> abort: push creates new remote head eaf64eb11964!
+  hg> (pull and merge or see 'hg help push' for details about pushing new heads)
+  likely push race on attempt 2/3
+  retrying immediately...
+  executing: hg strip --no-backup -r 'not public()'
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  pushing 2 new changesets on head eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 to http://localhost:$HGPORT/push-race
+  1:67c9543981c6: initial - add source-file0 and source-file1
+  2:eaf64eb11964: add dir0/file0
+  executing: hg push -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 http://localhost:$HGPORT/push-race
+  hg> pushing to http://localhost:$HGPORT/push-race
+  hg> searching for changes
+  hg> remote has heads on branch 'default' that are not known locally: 9482a15d6fcd
+  hg> abort: push creates new remote head eaf64eb11964!
+  hg> (pull and merge or see 'hg help push' for details about pushing new heads)
+  likely push race on attempt 3/3
+  overlay not successful after 3 attempts; try again later
+  [1]
+
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-push-result.t
@@ -0,0 +1,111 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+--result-push-url will push results after overlay
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir --result-push-url http://localhost:$HGPORT/overlay-dest
+  repo does not exist; cloning http://localhost:$HGPORT/overlay-dest
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 2 changes to 2 files
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/repo/.hg/localhost~3a*__overlay-source (glob)
+  hg> requesting all changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 3 changes to 3 files
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  pushing 2 new changesets on head eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 to http://localhost:$HGPORT/overlay-dest
+  1:67c9543981c6: initial - add source-file0 and source-file1
+  2:eaf64eb11964: add dir0/file0
+  executing: hg push -r eaf64eb119642ef85b4d952a49d0f5c815d5bcd1 http://localhost:$HGPORT/overlay-dest
+  hg> pushing to http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> remote: adding changesets
+  hg> remote: adding manifests
+  hg> remote: adding file changes
+  hg> remote: added 2 changesets with 3 changes to 3 files
+
+No-op after pushing results
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir --result-push-url http://localhost:$HGPORT/overlay-dest
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> eaf64eb11964
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+  hg> d92cc0ff6f1a already processed as eaf64eb11964; skipping 2/2 revisions
+  hg> no source revisions left to process
+  no changesets overlayed
+
+Incremental overlay + push works
+
+  $ cd server/overlay-dest
+  $ hg -q up tip
+  $ echo 1 > new-dest
+  $ hg -q commit -A -m 'new in dest 1'
+  $ echo 2 > new-dest
+  $ hg commit -m 'new in dest 2'
+  $ cd ../overlay-source
+  $ echo 1 > new-source
+  $ hg -q commit -A -m 'new in source 1'
+  $ echo 2 > new-source
+  $ hg commit -m 'new in source 2'
+  $ cd ../..
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir --result-push-url http://localhost:$HGPORT/overlay-dest
+  executing: hg strip --no-backup -r 'not public()'
+  hg> abort: empty revision set
+  (ignoring strip failure)
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 9407bbd2ed9a
+  pulling http://localhost:$HGPORT/overlay-dest to obtain 9407bbd2ed9a
+  executing: hg pull -r 9407bbd2ed9a http://localhost:$HGPORT/overlay-dest
+  hg> pulling from http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 2 changes to 1 files
+  hg> (run 'hg update' to get a working copy)
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 9407bbd2ed9ae87d1412db63ccbdc88dfc244d8b
+  hg> pulling http://localhost:$HGPORT/overlay-source into $TESTTMP/repo/.hg/localhost~3a*__overlay-source (glob)
+  hg> searching for changes
+  hg> adding changesets
+  hg> adding manifests
+  hg> adding file changes
+  hg> added 2 changesets with 2 changes to 1 files
+  hg> d92cc0ff6f1a already processed as eaf64eb11964; skipping 2/4 revisions
+  hg> b819368ed1b8 -> 065d6faac6a8: new in source 1
+  hg> 8daaa17f19e3 -> a8fc26f818c2: new in source 2
+  2 new changesets; new tip is a8fc26f818c2ec9874d098d3ba1ccbbde7abfab6
+  pushing 2 new changesets on head a8fc26f818c2ec9874d098d3ba1ccbbde7abfab6 to http://localhost:$HGPORT/overlay-dest
+  5:065d6faac6a8: new in source 1
+  6:a8fc26f818c2: new in source 2
+  executing: hg push -r a8fc26f818c2ec9874d098d3ba1ccbbde7abfab6 http://localhost:$HGPORT/overlay-dest
+  hg> pushing to http://localhost:$HGPORT/overlay-dest
+  hg> searching for changes
+  hg> remote: adding changesets
+  hg> remote: adding manifests
+  hg> remote: adding file changes
+  hg> remote: added 2 changesets with 2 changes to 1 files
new file mode 100644
--- /dev/null
+++ b/vcssync/tests/test-overlay-hg-strip.t
@@ -0,0 +1,46 @@
+  $ . $TESTDIR/vcssync/tests/helpers.sh
+  $ standardoverlayenv > /dev/null 2>&1
+
+Seed local clone
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   repo --into subdir > /dev/null 2>&1
+
+  $ cd repo
+
+Nuke the overlayed changesets b/c they interfere with next test
+
+  $ hg -q --config extensions.strip= strip -r 'not public()'
+  $ hg log -G -T '{node|short} {desc}'
+  o  88dd2a5005e6 initial in dest
+  
+
+Add some draft changesets that should be stripped
+
+  $ touch local0
+  $ hg -q commit -A -m 'add local0'
+  $ touch local1
+  $ hg -q commit -A -m 'add local1'
+
+Verify draft changesets are stripped
+
+  $ overlay-hg-repos http://localhost:$HGPORT/overlay-source http://localhost:$HGPORT/overlay-dest \
+  >   . --into subdir
+  executing: hg strip --no-backup -r 'not public()'
+  hg> 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  resolving destination revision: tip
+  executing: hg identify http://localhost:$HGPORT/overlay-dest -r tip
+  hg> 88dd2a5005e6
+  commencing overlay of http://localhost:$HGPORT/overlay-source
+  executing: hg overlay http://localhost:$HGPORT/overlay-source --into subdir -d 88dd2a5005e6e795674d8253cec4dde9f9f77457
+  hg> 76f0fc85e215 -> 67c9543981c6: initial - add source-file0 and source-file1
+  hg> d92cc0ff6f1a -> eaf64eb11964: add dir0/file0
+  2 new changesets; new tip is eaf64eb119642ef85b4d952a49d0f5c815d5bcd1
+
+  $ hg log -G -T '{node|short} {desc}'
+  o  eaf64eb11964 add dir0/file0
+  |
+  o  67c9543981c6 initial - add source-file0 and source-file1
+  |
+  o  88dd2a5005e6 initial in dest
+