--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/__init__.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,111 @@
+# __init__.py - narrowhg extension
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+'''create clones which fetch history data for subset of files (EXPERIMENTAL)'''
+
+from __future__ import absolute_import
+
+from mercurial import __version__
+if __version__.version < '3.7':
+ raise ImportError(
+ 'narrowhg requires mercurial 3.7 or newer')
+
+try:
+ from .__versionnum__ import version
+ __version__ = version
+except ImportError:
+ pass
+
+from mercurial import (
+ extensions,
+ hg,
+ localrepo,
+ registrar,
+ util,
+ verify as verifymod,
+)
+
+from . import (
+ narrowbundle2,
+ narrowchangegroup,
+ narrowcommands,
+ narrowcopies,
+ narrowdirstate,
+ narrowmerge,
+ narrowpatch,
+ narrowrepo,
+ narrowrevlog,
+ narrowtemplates,
+ narrowwirepeer,
+)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+# Narrowhg *has* support for serving ellipsis nodes (which are used at
+# least by Google's internal server), but that support is pretty
+# fragile and has a lot of problems on real-world repositories that
+# have complex graph topologies. This could probably be corrected, but
+# absent someone needing the full support for ellipsis nodes in
+# repositories with merges, it's unlikely this work will get done. As
+# of this writining in late 2017, all repositories large enough for
+# ellipsis nodes to be a hard requirement also enforce strictly linear
+# history for other scaling reasons.
+configitem('experimental', 'narrowservebrokenellipses',
+ default=False,
+ alias=[('narrow', 'serveellipses')],
+)
+
+# Export the commands table for Mercurial to see.
+cmdtable = narrowcommands.table
+
+localrepo.localrepository._basesupported.add(narrowrepo.requirement)
+
+def uisetup(ui):
+ """Wraps user-facing mercurial commands with narrow-aware versions."""
+ narrowrevlog.setup()
+ narrowbundle2.setup()
+ narrowmerge.setup()
+ narrowtemplates.setup()
+ narrowcommands.setup()
+ narrowchangegroup.setup()
+ narrowwirepeer.uisetup()
+
+def reposetup(ui, repo):
+ """Wraps local repositories with narrow repo support."""
+ if not isinstance(repo, localrepo.localrepository):
+ return
+
+ if narrowrepo.requirement in repo.requirements:
+ narrowrepo.wraprepo(repo, True)
+ narrowcopies.setup(repo)
+ narrowdirstate.setup(repo)
+ narrowpatch.setup(repo)
+ narrowwirepeer.reposetup(repo)
+
+def _narrowvalidpath(orig, repo, path):
+ matcher = getattr(repo, 'narrowmatch', None)
+ if matcher is None:
+ return orig(repo, path)
+ matcher = matcher()
+ if matcher.visitdir(path) or matcher(path):
+ return orig(repo, path)
+ return False
+
+def _verifierinit(orig, self, repo, matcher=None):
+ # The verifier's matcher argument was desgined for narrowhg, so it should
+ # be None from core. If another extension passes a matcher (unlikely),
+ # we'll have to fail until matchers can be composed more easily.
+ assert matcher is None
+ matcher = getattr(repo, 'narrowmatch', lambda: None)()
+ orig(self, repo, matcher)
+
+def extsetup(ui):
+ if util.safehasattr(verifymod, '_validpath'):
+ extensions.wrapfunction(verifymod, '_validpath', _narrowvalidpath)
+ else:
+ extensions.wrapfunction(verifymod.verifier, '__init__', _verifierinit)
+ extensions.wrapfunction(hg, 'postshare', narrowrepo.wrappostshare)
+ extensions.wrapfunction(hg, 'copystore', narrowrepo.unsharenarrowspec)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowbundle2.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,503 @@
+# narrowbundle2.py - bundle2 extensions for narrow repository support
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import collections
+import errno
+import struct
+
+from mercurial.i18n import _
+from mercurial.node import (
+ bin,
+ nullid,
+ nullrev,
+)
+from mercurial import (
+ bundle2,
+ changegroup,
+ dagutil,
+ error,
+ exchange,
+ extensions,
+ repair,
+ util,
+ wireproto,
+)
+
+from . import (
+ narrowrepo,
+ narrowspec,
+)
+
+narrowcap = 'narrow'
+narrowacl_section = 'narrowhgacl'
+changespecpart = narrowcap + ':changespec'
+specpart = narrowcap + ':spec'
+specpart_include = 'include'
+specpart_exclude = 'exclude'
+killnodesignal = 'KILL'
+donesignal = 'DONE'
+elidedcsheader = '>20s20s20sl' # cset id, p1, p2, len(text)
+elidedmfheader = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
+csheadersize = struct.calcsize(elidedcsheader)
+mfheadersize = struct.calcsize(elidedmfheader)
+
+# When advertising capabilities, always include narrow clone support.
+def getrepocaps_narrow(orig, repo, **kwargs):
+ caps = orig(repo, **kwargs)
+ caps[narrowcap] = ['v0']
+ return caps
+
+def _computeellipsis(repo, common, heads, known, match, depth=None):
+ """Compute the shape of a narrowed DAG.
+
+ Args:
+ repo: The repository we're transferring.
+ common: The roots of the DAG range we're transferring.
+ May be just [nullid], which means all ancestors of heads.
+ heads: The heads of the DAG range we're transferring.
+ match: The narrowmatcher that allows us to identify relevant changes.
+ depth: If not None, only consider nodes to be full nodes if they are at
+ most depth changesets away from one of heads.
+
+ Returns:
+ A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
+
+ visitnodes: The list of nodes (either full or ellipsis) which
+ need to be sent to the client.
+ relevant_nodes: The set of changelog nodes which change a file inside
+ the narrowspec. The client needs these as non-ellipsis nodes.
+ ellipsisroots: A dict of {rev: parents} that is used in
+ narrowchangegroup to produce ellipsis nodes with the
+ correct parents.
+ """
+ cl = repo.changelog
+ mfl = repo.manifestlog
+
+ cldag = dagutil.revlogdag(cl)
+ # dagutil does not like nullid/nullrev
+ commonrevs = cldag.internalizeall(common - set([nullid])) | set([nullrev])
+ headsrevs = cldag.internalizeall(heads)
+ if depth:
+ revdepth = {h: 0 for h in headsrevs}
+
+ ellipsisheads = collections.defaultdict(set)
+ ellipsisroots = collections.defaultdict(set)
+
+ def addroot(head, curchange):
+ """Add a root to an ellipsis head, splitting heads with 3 roots."""
+ ellipsisroots[head].add(curchange)
+ # Recursively split ellipsis heads with 3 roots by finding the
+ # roots' youngest common descendant which is an elided merge commit.
+ # That descendant takes 2 of the 3 roots as its own, and becomes a
+ # root of the head.
+ while len(ellipsisroots[head]) > 2:
+ child, roots = splithead(head)
+ splitroots(head, child, roots)
+ head = child # Recurse in case we just added a 3rd root
+
+ def splitroots(head, child, roots):
+ ellipsisroots[head].difference_update(roots)
+ ellipsisroots[head].add(child)
+ ellipsisroots[child].update(roots)
+ ellipsisroots[child].discard(child)
+
+ def splithead(head):
+ r1, r2, r3 = sorted(ellipsisroots[head])
+ for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
+ mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
+ nr1, head, nr2, head)
+ for j in mid:
+ if j == nr2:
+ return nr2, (nr1, nr2)
+ if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
+ return j, (nr1, nr2)
+ raise error.Abort('Failed to split up ellipsis node! head: %d, '
+ 'roots: %d %d %d' % (head, r1, r2, r3))
+
+ missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
+ visit = reversed(missing)
+ relevant_nodes = set()
+ visitnodes = map(cl.node, missing)
+ required = set(headsrevs) | known
+ for rev in visit:
+ clrev = cl.changelogrevision(rev)
+ ps = cldag.parents(rev)
+ if depth is not None:
+ curdepth = revdepth[rev]
+ for p in ps:
+ revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
+ needed = False
+ shallow_enough = depth is None or revdepth[rev] <= depth
+ if shallow_enough:
+ curmf = mfl[clrev.manifest].read()
+ if ps:
+ # We choose to not trust the changed files list in
+ # changesets because it's not always correct. TODO: could
+ # we trust it for the non-merge case?
+ p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
+ needed = any(match(f) for f in curmf.diff(p1mf).iterkeys())
+ if not needed and len(ps) > 1:
+ # For merge changes, the list of changed files is not
+ # helpful, since we need to emit the merge if a file
+ # in the narrow spec has changed on either side of the
+ # merge. As a result, we do a manifest diff to check.
+ p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
+ needed = any(match(f) for f in curmf.diff(p2mf).iterkeys())
+ else:
+ # For a root node, we need to include the node if any
+ # files in the node match the narrowspec.
+ needed = any(match(f) for f in curmf)
+
+ if needed:
+ for head in ellipsisheads[rev]:
+ addroot(head, rev)
+ for p in ps:
+ required.add(p)
+ relevant_nodes.add(cl.node(rev))
+ else:
+ if not ps:
+ ps = [nullrev]
+ if rev in required:
+ for head in ellipsisheads[rev]:
+ addroot(head, rev)
+ for p in ps:
+ ellipsisheads[p].add(rev)
+ else:
+ for p in ps:
+ ellipsisheads[p] |= ellipsisheads[rev]
+
+ # add common changesets as roots of their reachable ellipsis heads
+ for c in commonrevs:
+ for head in ellipsisheads[c]:
+ addroot(head, c)
+ return visitnodes, relevant_nodes, ellipsisroots
+
+def _packellipsischangegroup(repo, common, match, relevant_nodes,
+ ellipsisroots, visitnodes, depth, source, version):
+ if version in ('01', '02'):
+ raise error.Abort(
+ 'ellipsis nodes require at least cg3 on client and server, '
+ 'but negotiated version %s' % version)
+ # We wrap cg1packer.revchunk, using a side channel to pass
+ # relevant_nodes into that area. Then if linknode isn't in the
+ # set, we know we have an ellipsis node and we should defer
+ # sending that node's data. We override close() to detect
+ # pending ellipsis nodes and flush them.
+ packer = changegroup.getbundler(version, repo)
+ # Let the packer have access to the narrow matcher so it can
+ # omit filelogs and dirlogs as needed
+ packer._narrow_matcher = lambda : match
+ # Give the packer the list of nodes which should not be
+ # ellipsis nodes. We store this rather than the set of nodes
+ # that should be an ellipsis because for very large histories
+ # we expect this to be significantly smaller.
+ packer.full_nodes = relevant_nodes
+ # Maps ellipsis revs to their roots at the changelog level.
+ packer.precomputed_ellipsis = ellipsisroots
+ # Maps CL revs to per-revlog revisions. Cleared in close() at
+ # the end of each group.
+ packer.clrev_to_localrev = {}
+ packer.next_clrev_to_localrev = {}
+ # Maps changelog nodes to changelog revs. Filled in once
+ # during changelog stage and then left unmodified.
+ packer.clnode_to_rev = {}
+ packer.changelog_done = False
+ # If true, informs the packer that it is serving shallow content and might
+ # need to pack file contents not introduced by the changes being packed.
+ packer.is_shallow = depth is not None
+
+ return packer.generate(common, visitnodes, False, source)
+
+# Serve a changegroup for a client with a narrow clone.
+def getbundlechangegrouppart_narrow(bundler, repo, source,
+ bundlecaps=None, b2caps=None, heads=None,
+ common=None, **kwargs):
+ cgversions = b2caps.get('changegroup')
+ getcgkwargs = {}
+ if cgversions: # 3.1 and 3.2 ship with an empty value
+ cgversions = [v for v in cgversions
+ if v in changegroup.supportedoutgoingversions(repo)]
+ if not cgversions:
+ raise ValueError(_('no common changegroup version'))
+ version = getcgkwargs['version'] = max(cgversions)
+ else:
+ raise ValueError(_("server does not advertise changegroup version,"
+ " can't negotiate support for ellipsis nodes"))
+
+ include = sorted(filter(bool, kwargs.get('includepats', [])))
+ exclude = sorted(filter(bool, kwargs.get('excludepats', [])))
+ newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
+ if not repo.ui.configbool("experimental", "narrowservebrokenellipses"):
+ outgoing = exchange._computeoutgoing(repo, heads, common)
+ if not outgoing.missing:
+ return
+ if util.safehasattr(changegroup, 'getsubsetraw'):
+ # getsubsetraw was replaced with makestream in hg in 92f1e2be8ab6
+ # (2017/09/10).
+ packer = changegroup.getbundler(version, repo)
+ packer._narrow_matcher = lambda : newmatch
+ cg = changegroup.getsubsetraw(repo, outgoing, packer, source)
+ else:
+ def wrappedgetbundler(orig, *args, **kwargs):
+ bundler = orig(*args, **kwargs)
+ bundler._narrow_matcher = lambda : newmatch
+ return bundler
+ with extensions.wrappedfunction(changegroup, 'getbundler',
+ wrappedgetbundler):
+ cg = changegroup.makestream(repo, outgoing, version, source)
+ part = bundler.newpart('changegroup', data=cg)
+ part.addparam('version', version)
+ if 'treemanifest' in repo.requirements:
+ part.addparam('treemanifest', '1')
+
+ if include or exclude:
+ narrowspecpart = bundler.newpart(specpart)
+ if include:
+ narrowspecpart.addparam(
+ specpart_include, '\n'.join(include), mandatory=True)
+ if exclude:
+ narrowspecpart.addparam(
+ specpart_exclude, '\n'.join(exclude), mandatory=True)
+
+ return
+
+ depth = kwargs.get('depth', None)
+ if depth is not None:
+ depth = int(depth)
+ if depth < 1:
+ raise error.Abort(_('depth must be positive, got %d') % depth)
+
+ heads = set(heads or repo.heads())
+ common = set(common or [nullid])
+ oldinclude = sorted(filter(bool, kwargs.get('oldincludepats', [])))
+ oldexclude = sorted(filter(bool, kwargs.get('oldexcludepats', [])))
+ known = {bin(n) for n in kwargs.get('known', [])}
+ if known and (oldinclude != include or oldexclude != exclude):
+ # Steps:
+ # 1. Send kill for "$known & ::common"
+ #
+ # 2. Send changegroup for ::common
+ #
+ # 3. Proceed.
+ #
+ # In the future, we can send kills for only the specific
+ # nodes we know should go away or change shape, and then
+ # send a data stream that tells the client something like this:
+ #
+ # a) apply this changegroup
+ # b) apply nodes XXX, YYY, ZZZ that you already have
+ # c) goto a
+ #
+ # until they've built up the full new state.
+ # Convert to revnums and intersect with "common". The client should
+ # have made it a subset of "common" already, but let's be safe.
+ known = set(repo.revs("%ln & ::%ln", known, common))
+ # TODO: we could send only roots() of this set, and the
+ # list of nodes in common, and the client could work out
+ # what to strip, instead of us explicitly sending every
+ # single node.
+ deadrevs = known
+ def genkills():
+ for r in deadrevs:
+ yield killnodesignal
+ yield repo.changelog.node(r)
+ yield donesignal
+ bundler.newpart(changespecpart, data=genkills())
+ newvisit, newfull, newellipsis = _computeellipsis(
+ repo, set(), common, known, newmatch)
+ if newvisit:
+ cg = _packellipsischangegroup(
+ repo, common, newmatch, newfull, newellipsis,
+ newvisit, depth, source, version)
+ part = bundler.newpart('changegroup', data=cg)
+ part.addparam('version', version)
+ if 'treemanifest' in repo.requirements:
+ part.addparam('treemanifest', '1')
+
+ visitnodes, relevant_nodes, ellipsisroots = _computeellipsis(
+ repo, common, heads, set(), newmatch, depth=depth)
+
+ repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
+ if visitnodes:
+ cg = _packellipsischangegroup(
+ repo, common, newmatch, relevant_nodes, ellipsisroots,
+ visitnodes, depth, source, version)
+ part = bundler.newpart('changegroup', data=cg)
+ part.addparam('version', version)
+ if 'treemanifest' in repo.requirements:
+ part.addparam('treemanifest', '1')
+
+def applyacl_narrow(repo, kwargs):
+ username = repo.ui.shortuser(repo.ui.username())
+ user_includes = repo.ui.configlist(
+ narrowacl_section, username + '.includes',
+ repo.ui.configlist(narrowacl_section, 'default.includes'))
+ user_excludes = repo.ui.configlist(
+ narrowacl_section, username + '.excludes',
+ repo.ui.configlist(narrowacl_section, 'default.excludes'))
+ if not user_includes:
+ raise error.Abort(_("{} configuration for user {} is empty")
+ .format(narrowacl_section, username))
+
+ user_includes = [
+ 'path:.' if p == '*' else 'path:' + p for p in user_includes]
+ user_excludes = [
+ 'path:.' if p == '*' else 'path:' + p for p in user_excludes]
+
+ req_includes = set(kwargs.get('includepats', []))
+ req_excludes = set(kwargs.get('excludepats', []))
+
+ invalid_includes = []
+ req_includes, req_excludes = narrowspec.restrictpatterns(
+ req_includes, req_excludes,
+ user_includes, user_excludes, invalid_includes)
+
+ if invalid_includes:
+ raise error.Abort(
+ _("The following includes are not accessible for {}: {}")
+ .format(username, invalid_includes))
+
+ new_args = {}
+ new_args.update(kwargs)
+ new_args['includepats'] = req_includes
+ if req_excludes:
+ new_args['excludepats'] = req_excludes
+ return new_args
+
+@bundle2.parthandler(specpart, (specpart_include, specpart_exclude))
+def _handlechangespec_2(op, inpart):
+ includepats = set(inpart.params.get(specpart_include, '').splitlines())
+ excludepats = set(inpart.params.get(specpart_exclude, '').splitlines())
+ narrowspec.save(op.repo, includepats, excludepats)
+ if not narrowrepo.requirement in op.repo.requirements:
+ op.repo.requirements.add(narrowrepo.requirement)
+ op.repo._writerequirements()
+ op.repo.invalidate(clearfilecache=True)
+
+@bundle2.parthandler(changespecpart)
+def _handlechangespec(op, inpart):
+ repo = op.repo
+ cl = repo.changelog
+
+ # changesets which need to be stripped entirely. either they're no longer
+ # needed in the new narrow spec, or the server is sending a replacement
+ # in the changegroup part.
+ clkills = set()
+
+ # A changespec part contains all the updates to ellipsis nodes
+ # that will happen as a result of widening or narrowing a
+ # repo. All the changes that this block encounters are ellipsis
+ # nodes or flags to kill an existing ellipsis.
+ chunksignal = changegroup.readexactly(inpart, 4)
+ while chunksignal != donesignal:
+ if chunksignal == killnodesignal:
+ # a node used to be an ellipsis but isn't anymore
+ ck = changegroup.readexactly(inpart, 20)
+ if cl.hasnode(ck):
+ clkills.add(ck)
+ else:
+ raise error.Abort(
+ _('unexpected changespec node chunk type: %s') % chunksignal)
+ chunksignal = changegroup.readexactly(inpart, 4)
+
+ if clkills:
+ # preserve bookmarks that repair.strip() would otherwise strip
+ bmstore = repo._bookmarks
+ class dummybmstore(dict):
+ def applychanges(self, repo, tr, changes):
+ pass
+ def recordchange(self, tr): # legacy version
+ pass
+ repo._bookmarks = dummybmstore()
+ chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
+ topic='widen')
+ repo._bookmarks = bmstore
+ if chgrpfile:
+ # presence of _widen_bundle attribute activates widen handler later
+ op._widen_bundle = chgrpfile
+ # Set the new narrowspec if we're widening. The setnewnarrowpats() method
+ # will currently always be there when using the core+narrowhg server, but
+ # other servers may include a changespec part even when not widening (e.g.
+ # because we're deepening a shallow repo).
+ if util.safehasattr(repo, 'setnewnarrowpats'):
+ repo.setnewnarrowpats()
+
+def handlechangegroup_widen(op, inpart):
+ """Changegroup exchange handler which restores temporarily-stripped nodes"""
+ # We saved a bundle with stripped node data we must now restore.
+ # This approach is based on mercurial/repair.py@6ee26a53c111.
+ repo = op.repo
+ ui = op.ui
+
+ chgrpfile = op._widen_bundle
+ del op._widen_bundle
+ vfs = repo.vfs
+
+ ui.note(_("adding branch\n"))
+ f = vfs.open(chgrpfile, "rb")
+ try:
+ gen = exchange.readbundle(ui, f, chgrpfile, vfs)
+ if not ui.verbose:
+ # silence internal shuffling chatter
+ ui.pushbuffer()
+ if isinstance(gen, bundle2.unbundle20):
+ with repo.transaction('strip') as tr:
+ bundle2.processbundle(repo, gen, lambda: tr)
+ else:
+ gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
+ if not ui.verbose:
+ ui.popbuffer()
+ finally:
+ f.close()
+
+ # remove undo files
+ for undovfs, undofile in repo.undofiles():
+ try:
+ undovfs.unlink(undofile)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ ui.warn(_('error removing %s: %s\n') %
+ (undovfs.join(undofile), str(e)))
+
+ # Remove partial backup only if there were no exceptions
+ vfs.unlink(chgrpfile)
+
+def setup():
+ """Enable narrow repo support in bundle2-related extension points."""
+ extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
+
+ wireproto.gboptsmap['narrow'] = 'boolean'
+ wireproto.gboptsmap['depth'] = 'plain'
+ wireproto.gboptsmap['oldincludepats'] = 'csv'
+ wireproto.gboptsmap['oldexcludepats'] = 'csv'
+ wireproto.gboptsmap['includepats'] = 'csv'
+ wireproto.gboptsmap['excludepats'] = 'csv'
+ wireproto.gboptsmap['known'] = 'csv'
+
+ # Extend changegroup serving to handle requests from narrow clients.
+ origcgfn = exchange.getbundle2partsmapping['changegroup']
+ def wrappedcgfn(*args, **kwargs):
+ repo = args[1]
+ if repo.ui.has_section(narrowacl_section):
+ getbundlechangegrouppart_narrow(
+ *args, **applyacl_narrow(repo, kwargs))
+ elif kwargs.get('narrow', False):
+ getbundlechangegrouppart_narrow(*args, **kwargs)
+ else:
+ origcgfn(*args, **kwargs)
+ exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
+
+ # Extend changegroup receiver so client can fixup after widen requests.
+ origcghandler = bundle2.parthandlermapping['changegroup']
+ def wrappedcghandler(op, inpart):
+ origcghandler(op, inpart)
+ if util.safehasattr(op, '_widen_bundle'):
+ handlechangegroup_widen(op, inpart)
+ wrappedcghandler.params = origcghandler.params
+ bundle2.parthandlermapping['changegroup'] = wrappedcghandler
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowchangegroup.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,385 @@
+# narrowchangegroup.py - narrow clone changegroup creation and consumption
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+ changegroup,
+ error,
+ extensions,
+ manifest,
+ mdiff,
+ node,
+ util,
+)
+
+from . import (
+ narrowrepo,
+ narrowrevlog,
+)
+
+def setup():
+
+ def supportedoutgoingversions(orig, repo):
+ versions = orig(repo)
+ if narrowrepo.requirement in repo.requirements:
+ versions.discard('01')
+ versions.discard('02')
+ return versions
+
+ extensions.wrapfunction(changegroup, 'supportedoutgoingversions',
+ supportedoutgoingversions)
+
+ def prune(orig, self, revlog, missing, commonrevs):
+ if isinstance(revlog, manifest.manifestrevlog):
+ matcher = getattr(self._repo, 'narrowmatch',
+ getattr(self, '_narrow_matcher', None))
+ if (matcher is not None and
+ not matcher().visitdir(revlog._dir[:-1] or '.')):
+ return []
+ return orig(self, revlog, missing, commonrevs)
+
+ extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
+
+ def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
+ source):
+ matcher = getattr(self._repo, 'narrowmatch',
+ getattr(self, '_narrow_matcher', None))
+ if matcher is not None:
+ narrowmatch = matcher()
+ changedfiles = filter(narrowmatch, changedfiles)
+ if getattr(self, 'is_shallow', False):
+ # See comment in generate() for why this sadness is a thing.
+ mfdicts = self._mfdicts
+ del self._mfdicts
+ # In a shallow clone, the linknodes callback needs to also include
+ # those file nodes that are in the manifests we sent but weren't
+ # introduced by those manifests.
+ commonctxs = [self._repo[c] for c in commonrevs]
+ oldlinknodes = linknodes
+ clrev = self._repo.changelog.rev
+ def linknodes(flog, fname):
+ for c in commonctxs:
+ try:
+ fnode = c.filenode(fname)
+ self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
+ except error.ManifestLookupError:
+ pass
+ links = oldlinknodes(flog, fname)
+ if len(links) != len(mfdicts):
+ for mf, lr in mfdicts:
+ fnode = mf.get(fname, None)
+ if fnode in links:
+ links[fnode] = min(links[fnode], lr, key=clrev)
+ elif fnode:
+ links[fnode] = lr
+ return links
+ return orig(self, changedfiles, linknodes, commonrevs, source)
+ extensions.wrapfunction(
+ changegroup.cg1packer, 'generatefiles', generatefiles)
+
+ def ellipsisdata(packer, rev, revlog, p1, p2, data, linknode):
+ n = revlog.node(rev)
+ p1n, p2n = revlog.node(p1), revlog.node(p2)
+ flags = revlog.flags(rev)
+ flags |= narrowrevlog.ELLIPSIS_NODE_FLAG
+ meta = packer.builddeltaheader(
+ n, p1n, p2n, node.nullid, linknode, flags)
+ # TODO: try and actually send deltas for ellipsis data blocks
+ diffheader = mdiff.trivialdiffheader(len(data))
+ l = len(meta) + len(diffheader) + len(data)
+ return ''.join((changegroup.chunkheader(l),
+ meta,
+ diffheader,
+ data))
+
+ def close(orig, self):
+ getattr(self, 'clrev_to_localrev', {}).clear()
+ if getattr(self, 'next_clrev_to_localrev', {}):
+ self.clrev_to_localrev = self.next_clrev_to_localrev
+ del self.next_clrev_to_localrev
+ self.changelog_done = True
+ return orig(self)
+ extensions.wrapfunction(changegroup.cg1packer, 'close', close)
+
+ # In a perfect world, we'd generate better ellipsis-ified graphs
+ # for non-changelog revlogs. In practice, we haven't started doing
+ # that yet, so the resulting DAGs for the manifestlog and filelogs
+ # are actually full of bogus parentage on all the ellipsis
+ # nodes. This has the side effect that, while the contents are
+ # correct, the individual DAGs might be completely out of whack in
+ # a case like 882681bc3166 and its ancestors (back about 10
+ # revisions or so) in the main hg repo.
+ #
+ # The one invariant we *know* holds is that the new (potentially
+ # bogus) DAG shape will be valid if we order the nodes in the
+ # order that they're introduced in dramatis personae by the
+ # changelog, so what we do is we sort the non-changelog histories
+ # by the order in which they are used by the changelog.
+ def _sortgroup(orig, self, revlog, nodelist, lookup):
+ if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
+ return orig(self, revlog, nodelist, lookup)
+ key = lambda n: self.clnode_to_rev[lookup(n)]
+ return [revlog.rev(n) for n in sorted(nodelist, key=key)]
+
+ extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
+
+ def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
+ '''yield a sequence of changegroup chunks (strings)'''
+ # Note: other than delegating to orig, the only deviation in
+ # logic from normal hg's generate is marked with BEGIN/END
+ # NARROW HACK.
+ if not util.safehasattr(self, 'full_nodes'):
+ # not sending a narrow bundle
+ for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
+ yield x
+ return
+
+ repo = self._repo
+ cl = repo.changelog
+ mfl = repo.manifestlog
+ mfrevlog = mfl._revlog
+
+ clrevorder = {}
+ mfs = {} # needed manifests
+ fnodes = {} # needed file nodes
+ changedfiles = set()
+
+ # Callback for the changelog, used to collect changed files and manifest
+ # nodes.
+ # Returns the linkrev node (identity in the changelog case).
+ def lookupcl(x):
+ c = cl.read(x)
+ clrevorder[x] = len(clrevorder)
+ # BEGIN NARROW HACK
+ #
+ # Only update mfs if x is going to be sent. Otherwise we
+ # end up with bogus linkrevs specified for manifests and
+ # we skip some manifest nodes that we should otherwise
+ # have sent.
+ if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
+ n = c[0]
+ # record the first changeset introducing this manifest version
+ mfs.setdefault(n, x)
+ # Set this narrow-specific dict so we have the lowest manifest
+ # revnum to look up for this cl revnum. (Part of mapping
+ # changelog ellipsis parents to manifest ellipsis parents)
+ self.next_clrev_to_localrev.setdefault(cl.rev(x),
+ mfrevlog.rev(n))
+ # We can't trust the changed files list in the changeset if the
+ # client requested a shallow clone.
+ if self.is_shallow:
+ changedfiles.update(mfl[c[0]].read().keys())
+ else:
+ changedfiles.update(c[3])
+ # END NARROW HACK
+ # Record a complete list of potentially-changed files in
+ # this manifest.
+ return x
+
+ self._verbosenote(_('uncompressed size of bundle content:\n'))
+ size = 0
+ for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
+ size += len(chunk)
+ yield chunk
+ self._verbosenote(_('%8.i (changelog)\n') % size)
+
+ # We need to make sure that the linkrev in the changegroup refers to
+ # the first changeset that introduced the manifest or file revision.
+ # The fastpath is usually safer than the slowpath, because the filelogs
+ # are walked in revlog order.
+ #
+ # When taking the slowpath with reorder=None and the manifest revlog
+ # uses generaldelta, the manifest may be walked in the "wrong" order.
+ # Without 'clrevorder', we would get an incorrect linkrev (see fix in
+ # cc0ff93d0c0c).
+ #
+ # When taking the fastpath, we are only vulnerable to reordering
+ # of the changelog itself. The changelog never uses generaldelta, so
+ # it is only reordered when reorder=True. To handle this case, we
+ # simply take the slowpath, which already has the 'clrevorder' logic.
+ # This was also fixed in cc0ff93d0c0c.
+ fastpathlinkrev = fastpathlinkrev and not self._reorder
+ # Treemanifests don't work correctly with fastpathlinkrev
+ # either, because we don't discover which directory nodes to
+ # send along with files. This could probably be fixed.
+ fastpathlinkrev = fastpathlinkrev and (
+ 'treemanifest' not in repo.requirements)
+ # Shallow clones also don't work correctly with fastpathlinkrev
+ # because file nodes may need to be sent for a manifest even if they
+ # weren't introduced by that manifest.
+ fastpathlinkrev = fastpathlinkrev and not self.is_shallow
+
+ moreargs = []
+ if self.generatemanifests.func_code.co_argcount == 7:
+ # The source argument was added to generatemanifests in hg in
+ # 75cc1f1e11f2 (2017/09/11).
+ moreargs.append(source)
+ for chunk in self.generatemanifests(commonrevs, clrevorder,
+ fastpathlinkrev, mfs, fnodes, *moreargs):
+ yield chunk
+ # BEGIN NARROW HACK
+ mfdicts = None
+ if self.is_shallow:
+ mfdicts = [(self._repo.manifestlog[n].read(), lr)
+ for (n, lr) in mfs.iteritems()]
+ # END NARROW HACK
+ mfs.clear()
+ clrevs = set(cl.rev(x) for x in clnodes)
+
+ if not fastpathlinkrev:
+ def linknodes(unused, fname):
+ return fnodes.get(fname, {})
+ else:
+ cln = cl.node
+ def linknodes(filerevlog, fname):
+ llr = filerevlog.linkrev
+ fln = filerevlog.node
+ revs = ((r, llr(r)) for r in filerevlog)
+ return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
+
+ # BEGIN NARROW HACK
+ #
+ # We need to pass the mfdicts variable down into
+ # generatefiles(), but more than one command might have
+ # wrapped generatefiles so we can't modify the function
+ # signature. Instead, we pass the data to ourselves using an
+ # instance attribute. I'm sorry.
+ self._mfdicts = mfdicts
+ # END NARROW HACK
+ for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
+ source):
+ yield chunk
+
+ yield self.close()
+
+ if clnodes:
+ repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
+ extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
+
+ def revchunk(orig, self, revlog, rev, prev, linknode):
+ if not util.safehasattr(self, 'full_nodes'):
+ # not sending a narrow changegroup
+ for x in orig(self, revlog, rev, prev, linknode):
+ yield x
+ return
+ # build up some mapping information that's useful later. See
+ # the local() nested function below.
+ if not self.changelog_done:
+ self.clnode_to_rev[linknode] = rev
+ linkrev = rev
+ self.clrev_to_localrev[linkrev] = rev
+ else:
+ linkrev = self.clnode_to_rev[linknode]
+ self.clrev_to_localrev[linkrev] = rev
+ # This is a node to send in full, because the changeset it
+ # corresponds to was a full changeset.
+ if linknode in self.full_nodes:
+ for x in orig(self, revlog, rev, prev, linknode):
+ yield x
+ return
+ # At this point, a node can either be one we should skip or an
+ # ellipsis. If it's not an ellipsis, bail immediately.
+ if linkrev not in self.precomputed_ellipsis:
+ return
+ linkparents = self.precomputed_ellipsis[linkrev]
+ def local(clrev):
+ """Turn a changelog revnum into a local revnum.
+
+ The ellipsis dag is stored as revnums on the changelog,
+ but when we're producing ellipsis entries for
+ non-changelog revlogs, we need to turn those numbers into
+ something local. This does that for us, and during the
+ changelog sending phase will also expand the stored
+ mappings as needed.
+ """
+ if clrev == node.nullrev:
+ return node.nullrev
+ if not self.changelog_done:
+ # If we're doing the changelog, it's possible that we
+ # have a parent that is already on the client, and we
+ # need to store some extra mapping information so that
+ # our contained ellipsis nodes will be able to resolve
+ # their parents.
+ if clrev not in self.clrev_to_localrev:
+ clnode = revlog.node(clrev)
+ self.clnode_to_rev[clnode] = clrev
+ return clrev
+ # Walk the ellipsis-ized changelog breadth-first looking for a
+ # change that has been linked from the current revlog.
+ #
+ # For a flat manifest revlog only a single step should be necessary
+ # as all relevant changelog entries are relevant to the flat
+ # manifest.
+ #
+ # For a filelog or tree manifest dirlog however not every changelog
+ # entry will have been relevant, so we need to skip some changelog
+ # nodes even after ellipsis-izing.
+ walk = [clrev]
+ while walk:
+ p = walk[0]
+ walk = walk[1:]
+ if p in self.clrev_to_localrev:
+ return self.clrev_to_localrev[p]
+ elif p in self.full_nodes:
+ walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
+ if pp != node.nullrev])
+ elif p in self.precomputed_ellipsis:
+ walk.extend([pp for pp in self.precomputed_ellipsis[p]
+ if pp != node.nullrev])
+ else:
+ # In this case, we've got an ellipsis with parents
+ # outside the current bundle (likely an
+ # incremental pull). We "know" that we can use the
+ # value of this same revlog at whatever revision
+ # is pointed to by linknode. "Know" is in scare
+ # quotes because I haven't done enough examination
+ # of edge cases to convince myself this is really
+ # a fact - it works for all the (admittedly
+ # thorough) cases in our testsuite, but I would be
+ # somewhat unsurprised to find a case in the wild
+ # where this breaks down a bit. That said, I don't
+ # know if it would hurt anything.
+ for i in xrange(rev, 0, -1):
+ if revlog.linkrev(i) == clrev:
+ return i
+ # We failed to resolve a parent for this node, so
+ # we crash the changegroup construction.
+ raise error.Abort(
+ 'unable to resolve parent while packing %r %r'
+ ' for changeset %r' % (revlog.indexfile, rev, clrev))
+ return node.nullrev
+
+ if not linkparents or (
+ revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
+ p1, p2 = node.nullrev, node.nullrev
+ elif len(linkparents) == 1:
+ p1, = sorted(local(p) for p in linkparents)
+ p2 = node.nullrev
+ else:
+ p1, p2 = sorted(local(p) for p in linkparents)
+ yield ellipsisdata(
+ self, rev, revlog, p1, p2, revlog.revision(rev), linknode)
+ extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
+
+ def deltaparent(orig, self, revlog, rev, p1, p2, prev):
+ if util.safehasattr(self, 'full_nodes'):
+ # TODO: send better deltas when in narrow mode.
+ #
+ # changegroup.group() loops over revisions to send,
+ # including revisions we'll skip. What this means is that
+ # `prev` will be a potentially useless delta base for all
+ # ellipsis nodes, as the client likely won't have it. In
+ # the future we should do bookkeeping about which nodes
+ # have been sent to the client, and try to be
+ # significantly smarter about delta bases. This is
+ # slightly tricky because this same code has to work for
+ # all revlogs, and we don't have the linkrev/linknode here.
+ return p1
+ return orig(self, revlog, rev, p1, p2, prev)
+ extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowcommands.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,402 @@
+# narrowcommands.py - command modifications for narrowhg extension
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
+
+import itertools
+
+from mercurial.i18n import _
+from mercurial import (
+ cmdutil,
+ commands,
+ discovery,
+ error,
+ exchange,
+ extensions,
+ hg,
+ merge,
+ node,
+ registrar,
+ repair,
+ repoview,
+ util,
+)
+
+from . import (
+ narrowbundle2,
+ narrowrepo,
+ narrowspec,
+)
+
+table = {}
+command = registrar.command(table)
+
+def setup():
+ """Wraps user-facing mercurial commands with narrow-aware versions."""
+
+ entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
+ entry[1].append(('', 'narrow', None,
+ _("create a narrow clone of select files")))
+ entry[1].append(('', 'depth', '',
+ _("limit the history fetched by distance from heads")))
+ # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
+ if 'sparse' not in extensions.enabled():
+ entry[1].append(('', 'include', [],
+ _("specifically fetch this file/directory")))
+ entry[1].append(
+ ('', 'exclude', [],
+ _("do not fetch this file/directory, even if included")))
+
+ entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
+ entry[1].append(('', 'depth', '',
+ _("limit the history fetched by distance from heads")))
+
+ extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
+
+def expandpull(pullop, includepats, excludepats):
+ if not narrowspec.needsexpansion(includepats):
+ return includepats, excludepats
+
+ heads = pullop.heads or pullop.rheads
+ includepats, excludepats = pullop.remote.expandnarrow(
+ includepats, excludepats, heads)
+ pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
+ includepats, excludepats))
+ return set(includepats), set(excludepats)
+
+def clonenarrowcmd(orig, ui, repo, *args, **opts):
+ """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
+ wrappedextraprepare = util.nullcontextmanager()
+ opts_narrow = opts['narrow']
+ if opts_narrow:
+ def pullbundle2extraprepare_widen(orig, pullop, kwargs):
+ # Create narrow spec patterns from clone flags
+ includepats = narrowspec.parsepatterns(opts['include'])
+ excludepats = narrowspec.parsepatterns(opts['exclude'])
+
+ # If necessary, ask the server to expand the narrowspec.
+ includepats, excludepats = expandpull(
+ pullop, includepats, excludepats)
+
+ if not includepats and excludepats:
+ # If nothing was included, we assume the user meant to include
+ # everything, except what they asked to exclude.
+ includepats = {'path:.'}
+
+ narrowspec.save(pullop.repo, includepats, excludepats)
+
+ # This will populate 'includepats' etc with the values from the
+ # narrowspec we just saved.
+ orig(pullop, kwargs)
+
+ if opts.get('depth'):
+ kwargs['depth'] = opts['depth']
+ wrappedextraprepare = extensions.wrappedfunction(exchange,
+ '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
+
+ def pullnarrow(orig, repo, *args, **kwargs):
+ narrowrepo.wraprepo(repo.unfiltered(), opts_narrow)
+ if isinstance(repo, repoview.repoview):
+ repo.__class__.__bases__ = (repo.__class__.__bases__[0],
+ repo.unfiltered().__class__)
+ if opts_narrow:
+ repo.requirements.add(narrowrepo.requirement)
+ repo._writerequirements()
+
+ return orig(repo, *args, **kwargs)
+
+ wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
+
+ with wrappedextraprepare, wrappedpull:
+ return orig(ui, repo, *args, **opts)
+
+def pullnarrowcmd(orig, ui, repo, *args, **opts):
+ """Wraps pull command to allow modifying narrow spec."""
+ wrappedextraprepare = util.nullcontextmanager()
+ if narrowrepo.requirement in repo.requirements:
+
+ def pullbundle2extraprepare_widen(orig, pullop, kwargs):
+ orig(pullop, kwargs)
+ if opts.get('depth'):
+ kwargs['depth'] = opts['depth']
+ wrappedextraprepare = extensions.wrappedfunction(exchange,
+ '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
+
+ with wrappedextraprepare:
+ return orig(ui, repo, *args, **opts)
+
+def archivenarrowcmd(orig, ui, repo, *args, **opts):
+ """Wraps archive command to narrow the default includes."""
+ if narrowrepo.requirement in repo.requirements:
+ repo_includes, repo_excludes = repo.narrowpats
+ includes = set(opts.get('include', []))
+ excludes = set(opts.get('exclude', []))
+ includes, excludes = narrowspec.restrictpatterns(
+ includes, excludes, repo_includes, repo_excludes)
+ if includes:
+ opts['include'] = includes
+ if excludes:
+ opts['exclude'] = excludes
+ return orig(ui, repo, *args, **opts)
+
+def pullbundle2extraprepare(orig, pullop, kwargs):
+ repo = pullop.repo
+ if narrowrepo.requirement not in repo.requirements:
+ return orig(pullop, kwargs)
+
+ if narrowbundle2.narrowcap not in pullop.remotebundle2caps:
+ raise error.Abort(_("server doesn't support narrow clones"))
+ orig(pullop, kwargs)
+ kwargs['narrow'] = True
+ include, exclude = repo.narrowpats
+ kwargs['oldincludepats'] = include
+ kwargs['oldexcludepats'] = exclude
+ kwargs['includepats'] = include
+ kwargs['excludepats'] = exclude
+ kwargs['known'] = [node.hex(ctx.node()) for ctx in
+ repo.set('::%ln', pullop.common)
+ if ctx.node() != node.nullid]
+ if not kwargs['known']:
+ # Mercurial serialized an empty list as '' and deserializes it as
+ # [''], so delete it instead to avoid handling the empty string on the
+ # server.
+ del kwargs['known']
+
+extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
+ pullbundle2extraprepare)
+
+def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
+ newincludes, newexcludes, force):
+ oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
+ newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
+
+ # This is essentially doing "hg outgoing" to find all local-only
+ # commits. We will then check that the local-only commits don't
+ # have any changes to files that will be untracked.
+ unfi = repo.unfiltered()
+ outgoing = discovery.findcommonoutgoing(unfi, remote,
+ commoninc=commoninc)
+ ui.status(_('looking for local changes to affected paths\n'))
+ localnodes = []
+ for n in itertools.chain(outgoing.missing, outgoing.excluded):
+ if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
+ localnodes.append(n)
+ revstostrip = unfi.revs('descendants(%ln)', localnodes)
+ hiddenrevs = repoview.filterrevs(repo, 'visible')
+ visibletostrip = list(repo.changelog.node(r)
+ for r in (revstostrip - hiddenrevs))
+ if visibletostrip:
+ ui.status(_('The following changeset(s) or their ancestors have '
+ 'local changes not on the remote:\n'))
+ maxnodes = 10
+ if ui.verbose or len(visibletostrip) <= maxnodes:
+ for n in visibletostrip:
+ ui.status('%s\n' % node.short(n))
+ else:
+ for n in visibletostrip[:maxnodes]:
+ ui.status('%s\n' % node.short(n))
+ ui.status(_('...and %d more, use --verbose to list all\n') %
+ (len(visibletostrip) - maxnodes))
+ if not force:
+ raise error.Abort(_('local changes found'),
+ hint=_('use --force-delete-local-changes to '
+ 'ignore'))
+
+ if revstostrip:
+ tostrip = [unfi.changelog.node(r) for r in revstostrip]
+ if repo['.'].node() in tostrip:
+ # stripping working copy, so move to a different commit first
+ urev = max(repo.revs('(::%n) - %ln + null',
+ repo['.'].node(), visibletostrip))
+ hg.clean(repo, urev)
+ repair.strip(ui, unfi, tostrip, topic='narrow')
+
+ todelete = []
+ for f, f2, size in repo.store.datafiles():
+ if f.startswith('data/'):
+ file = f[5:-2]
+ if not newmatch(file):
+ todelete.append(f)
+ elif f.startswith('meta/'):
+ dir = f[5:-13]
+ dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
+ include = True
+ for d in dirs:
+ visit = newmatch.visitdir(d)
+ if not visit:
+ include = False
+ break
+ if visit == 'all':
+ break
+ if not include:
+ todelete.append(f)
+
+ repo.destroying()
+
+ with repo.transaction("narrowing"):
+ for f in todelete:
+ ui.status(_('deleting %s\n') % f)
+ util.unlinkpath(repo.svfs.join(f))
+ repo.store.markremoved(f)
+
+ for f in repo.dirstate:
+ if not newmatch(f):
+ repo.dirstate.drop(f)
+ repo.wvfs.unlinkpath(f)
+ repo.setnarrowpats(newincludes, newexcludes)
+
+ repo.destroyed()
+
+def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
+ newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
+
+ # TODO(martinvonz): Get expansion working with widening/narrowing.
+ if narrowspec.needsexpansion(newincludes):
+ raise error.Abort('Expansion not yet supported on pull')
+
+ def pullbundle2extraprepare_widen(orig, pullop, kwargs):
+ orig(pullop, kwargs)
+ # The old{in,ex}cludepats have already been set by orig()
+ kwargs['includepats'] = newincludes
+ kwargs['excludepats'] = newexcludes
+ wrappedextraprepare = extensions.wrappedfunction(exchange,
+ '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
+
+ # define a function that narrowbundle2 can call after creating the
+ # backup bundle, but before applying the bundle from the server
+ def setnewnarrowpats():
+ repo.setnarrowpats(newincludes, newexcludes)
+ repo.setnewnarrowpats = setnewnarrowpats
+
+ ds = repo.dirstate
+ p1, p2 = ds.p1(), ds.p2()
+ with ds.parentchange():
+ ds.setparents(node.nullid, node.nullid)
+ common = commoninc[0]
+ with wrappedextraprepare:
+ exchange.pull(repo, remote, heads=common)
+ with ds.parentchange():
+ ds.setparents(p1, p2)
+
+ actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
+ addgaction = actions['g'].append
+
+ mf = repo['.'].manifest().matches(newmatch)
+ for f, fn in mf.iteritems():
+ if f not in repo.dirstate:
+ addgaction((f, (mf.flags(f), False),
+ "add from widened narrow clone"))
+
+ merge.applyupdates(repo, actions, wctx=repo[None],
+ mctx=repo['.'], overwrite=False)
+ merge.recordupdates(repo, actions, branchmerge=False)
+
+# TODO(rdamazio): Make new matcher format and update description
+@command('tracked',
+ [('', 'addinclude', [], _('new paths to include')),
+ ('', 'removeinclude', [], _('old paths to no longer include')),
+ ('', 'addexclude', [], _('new paths to exclude')),
+ ('', 'removeexclude', [], _('old paths to no longer exclude')),
+ ('', 'clear', False, _('whether to replace the existing narrowspec')),
+ ('', 'force-delete-local-changes', False,
+ _('forces deletion of local changes when narrowing')),
+ ] + commands.remoteopts,
+ _('[OPTIONS]... [REMOTE]'),
+ inferrepo=True)
+def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
+ """show or change the current narrowspec
+
+ With no argument, shows the current narrowspec entries, one per line. Each
+ line will be prefixed with 'I' or 'X' for included or excluded patterns,
+ respectively.
+
+ The narrowspec is comprised of expressions to match remote files and/or
+ directories that should be pulled into your client.
+ The narrowspec has *include* and *exclude* expressions, with excludes always
+ trumping includes: that is, if a file matches an exclude expression, it will
+ be excluded even if it also matches an include expression.
+ Excluding files that were never included has no effect.
+
+ Each included or excluded entry is in the format described by
+ 'hg help patterns'.
+
+ The options allow you to add or remove included and excluded expressions.
+
+ If --clear is specified, then all previous includes and excludes are DROPPED
+ and replaced by the new ones specified to --addinclude and --addexclude.
+ If --clear is specified without any further options, the narrowspec will be
+ empty and will not match any files.
+ """
+ if narrowrepo.requirement not in repo.requirements:
+ ui.warn(_('The narrow command is only supported on respositories cloned'
+ ' with --narrow.\n'))
+ return 1
+
+ # Before supporting, decide whether it "hg tracked --clear" should mean
+ # tracking no paths or all paths.
+ if opts['clear']:
+ ui.warn(_('The --clear option is not yet supported.\n'))
+ return 1
+
+ if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
+ raise error.Abort('Expansion not yet supported on widen/narrow')
+
+ addedincludes = narrowspec.parsepatterns(opts['addinclude'])
+ removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
+ addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
+ removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
+ widening = addedincludes or removedexcludes
+ narrowing = removedincludes or addedexcludes
+ only_show = not widening and not narrowing
+
+ # Only print the current narrowspec.
+ if only_show:
+ include, exclude = repo.narrowpats
+
+ ui.pager('tracked')
+ fm = ui.formatter('narrow', opts)
+ for i in sorted(include):
+ fm.startitem()
+ fm.write('status', '%s ', 'I', label='narrow.included')
+ fm.write('pat', '%s\n', i, label='narrow.included')
+ for i in sorted(exclude):
+ fm.startitem()
+ fm.write('status', '%s ', 'X', label='narrow.excluded')
+ fm.write('pat', '%s\n', i, label='narrow.excluded')
+ fm.end()
+ return 0
+
+ with repo.wlock(), repo.lock():
+ cmdutil.bailifchanged(repo)
+
+ # Find the revisions we have in common with the remote. These will
+ # be used for finding local-only changes for narrowing. They will
+ # also define the set of revisions to update for widening.
+ remotepath = ui.expandpath(remotepath or 'default')
+ url, branches = hg.parseurl(remotepath)
+ ui.status(_('comparing with %s\n') % util.hidepassword(url))
+ remote = hg.peer(repo, opts, url)
+ commoninc = discovery.findcommonincoming(repo, remote)
+
+ oldincludes, oldexcludes = repo.narrowpats
+ if narrowing:
+ newincludes = oldincludes - removedincludes
+ newexcludes = oldexcludes | addedexcludes
+ _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
+ newincludes, newexcludes,
+ opts['force_delete_local_changes'])
+ # _narrow() updated the narrowspec and _widen() below needs to
+ # use the updated values as its base (otherwise removed includes
+ # and addedexcludes will be lost in the resulting narrowspec)
+ oldincludes = newincludes
+ oldexcludes = newexcludes
+
+ if widening:
+ newincludes = oldincludes | addedincludes
+ newexcludes = oldexcludes - removedexcludes
+ _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
+
+ return 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowcopies.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,35 @@
+# narrowcopies.py - extensions to mercurial copies module to support narrow
+# clones
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+ copies,
+ extensions,
+ util,
+)
+
+def setup(repo):
+ def _computeforwardmissing(orig, a, b, match=None):
+ missing = orig(a, b, match)
+ if util.safehasattr(repo, 'narrowmatch'):
+ narrowmatch = repo.narrowmatch()
+ missing = filter(narrowmatch, missing)
+ return missing
+
+ def _checkcopies(orig, srcctx, dstctx, f, base, tca, remotebase, limit,
+ data):
+ if util.safehasattr(repo, 'narrowmatch'):
+ narrowmatch = repo.narrowmatch()
+ if not narrowmatch(f):
+ return
+ orig(srcctx, dstctx, f, base, tca, remotebase, limit, data)
+
+ extensions.wrapfunction(copies, '_computeforwardmissing',
+ _computeforwardmissing)
+ extensions.wrapfunction(copies, '_checkcopies', _checkcopies)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowdirstate.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,80 @@
+# narrowdirstate.py - extensions to mercurial dirstate to support narrow clones
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+ dirstate,
+ error,
+ extensions,
+ match as matchmod,
+ util as hgutil,
+)
+
+from . import narrowspec
+
+def setup(repo):
+ """Add narrow spec dirstate ignore, block changes outside narrow spec."""
+
+ def walk(orig, self, match, subrepos, unknown, ignored, full=True,
+ narrowonly=True):
+ if narrowonly:
+ narrowmatch = repo.narrowmatch()
+ match = matchmod.intersectmatchers(match, narrowmatch)
+ return orig(self, match, subrepos, unknown, ignored, full)
+
+ extensions.wrapfunction(dirstate.dirstate, 'walk', walk)
+
+ # Prevent adding files that are outside the sparse checkout
+ editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
+ for func in editfuncs:
+ def _wrapper(orig, self, *args):
+ dirstate = repo.dirstate
+ narrowmatch = repo.narrowmatch()
+ for f in args:
+ if f is not None and not narrowmatch(f) and f not in dirstate:
+ raise error.Abort(_("cannot track '%s' - it is outside " +
+ "the narrow clone") % f)
+ return orig(self, *args)
+ extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
+
+ def filterrebuild(orig, self, parent, allfiles, changedfiles=None):
+ if changedfiles is None:
+ # Rebuilding entire dirstate, let's filter allfiles to match the
+ # narrowspec.
+ allfiles = [f for f in allfiles if repo.narrowmatch()(f)]
+ orig(self, parent, allfiles, changedfiles)
+
+ extensions.wrapfunction(dirstate.dirstate, 'rebuild', filterrebuild)
+
+ def _narrowbackupname(backupname):
+ assert 'dirstate' in backupname
+ return backupname.replace('dirstate', narrowspec.FILENAME)
+
+ def restorebackup(orig, self, tr, backupname):
+ self._opener.rename(_narrowbackupname(backupname), narrowspec.FILENAME,
+ checkambig=True)
+ orig(self, tr, backupname)
+
+ extensions.wrapfunction(dirstate.dirstate, 'restorebackup', restorebackup)
+
+ def savebackup(orig, self, tr, backupname):
+ orig(self, tr, backupname)
+
+ narrowbackupname = _narrowbackupname(backupname)
+ self._opener.tryunlink(narrowbackupname)
+ hgutil.copyfile(self._opener.join(narrowspec.FILENAME),
+ self._opener.join(narrowbackupname), hardlink=True)
+
+ extensions.wrapfunction(dirstate.dirstate, 'savebackup', savebackup)
+
+ def clearbackup(orig, self, tr, backupname):
+ orig(self, tr, backupname)
+ self._opener.unlink(_narrowbackupname(backupname))
+
+ extensions.wrapfunction(dirstate.dirstate, 'clearbackup', clearbackup)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowmerge.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,76 @@
+# narrowmerge.py - extensions to mercurial merge module to support narrow clones
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+ copies,
+ error,
+ extensions,
+ merge,
+ util,
+)
+
+def setup():
+ def _manifestmerge(orig, repo, wctx, p2, pa, branchmerge, *args, **kwargs):
+ """Filter updates to only lay out files that match the narrow spec."""
+ actions, diverge, renamedelete = orig(
+ repo, wctx, p2, pa, branchmerge, *args, **kwargs)
+
+ if not util.safehasattr(repo, 'narrowmatch'):
+ return actions, diverge, renamedelete
+
+ nooptypes = set(['k']) # TODO: handle with nonconflicttypes
+ nonconflicttypes = set('a am c cm f g r e'.split())
+ narrowmatch = repo.narrowmatch()
+ for f, action in actions.items():
+ if narrowmatch(f):
+ pass
+ elif not branchmerge:
+ del actions[f] # just updating, ignore changes outside clone
+ elif action[0] in nooptypes:
+ del actions[f] # merge does not affect file
+ elif action[0] in nonconflicttypes:
+ raise error.Abort(_('merge affects file \'%s\' outside narrow, '
+ 'which is not yet supported') % f,
+ hint=_('merging in the other direction '
+ 'may work'))
+ else:
+ raise error.Abort(_('conflict in file \'%s\' is outside '
+ 'narrow clone') % f)
+
+ return actions, diverge, renamedelete
+
+ extensions.wrapfunction(merge, 'manifestmerge', _manifestmerge)
+
+ def _checkcollision(orig, repo, wmf, actions):
+ if util.safehasattr(repo, 'narrowmatch'):
+ narrowmatch = repo.narrowmatch()
+ wmf = wmf.matches(narrowmatch)
+ if actions:
+ narrowactions = {}
+ for m, actionsfortype in actions.iteritems():
+ narrowactions[m] = []
+ for (f, args, msg) in actionsfortype:
+ if narrowmatch(f):
+ narrowactions[m].append((f, args, msg))
+ actions = narrowactions
+ return orig(repo, wmf, actions)
+
+ extensions.wrapfunction(merge, '_checkcollision', _checkcollision)
+
+ def _computenonoverlap(orig, repo, *args, **kwargs):
+ u1, u2 = orig(repo, *args, **kwargs)
+ if not util.safehasattr(repo, 'narrowmatch'):
+ return u1, u2
+
+ narrowmatch = repo.narrowmatch()
+ u1 = [f for f in u1 if narrowmatch(f)]
+ u2 = [f for f in u2 if narrowmatch(f)]
+ return u1, u2
+ extensions.wrapfunction(copies, '_computenonoverlap', _computenonoverlap)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowpatch.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,42 @@
+# narrowpatch.py - extensions to mercurial patch module to support narrow clones
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+ extensions,
+ patch,
+ util,
+)
+
+def setup(repo):
+ def _filepairs(orig, *args):
+ """Only includes files within the narrow spec in the diff."""
+ if util.safehasattr(repo, 'narrowmatch'):
+ narrowmatch = repo.narrowmatch()
+ for x in orig(*args):
+ f1, f2, copyop = x
+ if ((not f1 or narrowmatch(f1)) and
+ (not f2 or narrowmatch(f2))):
+ yield x
+ else:
+ for x in orig(*args):
+ yield x
+
+ def trydiff(orig, repo, revs, ctx1, ctx2, modified, added, removed,
+ copy, getfilectx, *args, **kwargs):
+ if util.safehasattr(repo, 'narrowmatch'):
+ narrowmatch = repo.narrowmatch()
+ modified = filter(narrowmatch, modified)
+ added = filter(narrowmatch, added)
+ removed = filter(narrowmatch, removed)
+ copy = {k: v for k, v in copy.iteritems() if narrowmatch(k)}
+ return orig(repo, revs, ctx1, ctx2, modified, added, removed, copy,
+ getfilectx, *args, **kwargs)
+
+ extensions.wrapfunction(patch, '_filepairs', _filepairs)
+ extensions.wrapfunction(patch, 'trydiff', trydiff)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowrepo.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,110 @@
+# narrowrepo.py - repository which supports narrow revlogs, lazy loading
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+ bundlerepo,
+ localrepo,
+ match as matchmod,
+ scmutil,
+)
+
+from .. import (
+ share,
+)
+
+from . import (
+ narrowrevlog,
+ narrowspec,
+)
+
+requirement = 'narrowhg'
+
+def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
+ orig(sourcerepo, destrepo, **kwargs)
+ if requirement in sourcerepo.requirements:
+ with destrepo.wlock():
+ with destrepo.vfs('shared', 'a') as fp:
+ fp.write(narrowspec.FILENAME + '\n')
+
+def unsharenarrowspec(orig, ui, repo, repopath):
+ if (requirement in repo.requirements
+ and repo.path == repopath and repo.shared()):
+ srcrepo = share._getsrcrepo(repo)
+ with srcrepo.vfs(narrowspec.FILENAME) as f:
+ spec = f.read()
+ with repo.vfs(narrowspec.FILENAME, 'w') as f:
+ f.write(spec)
+ return orig(ui, repo, repopath)
+
+def wraprepo(repo, opts_narrow):
+ """Enables narrow clone functionality on a single local repository."""
+
+ cacheprop = localrepo.storecache
+ if isinstance(repo, bundlerepo.bundlerepository):
+ # We have to use a different caching property decorator for
+ # bundlerepo because storecache blows up in strange ways on a
+ # bundlerepo. Fortunately, there's no risk of data changing in
+ # a bundlerepo.
+ cacheprop = lambda name: localrepo.unfilteredpropertycache
+
+ class narrowrepository(repo.__class__):
+
+ def _constructmanifest(self):
+ manifest = super(narrowrepository, self)._constructmanifest()
+ narrowrevlog.makenarrowmanifestrevlog(manifest, repo)
+ return manifest
+
+ @cacheprop('00manifest.i')
+ def manifestlog(self):
+ mfl = super(narrowrepository, self).manifestlog
+ narrowrevlog.makenarrowmanifestlog(mfl, self)
+ return mfl
+
+ def file(self, f):
+ fl = super(narrowrepository, self).file(f)
+ narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
+ return fl
+
+ @localrepo.repofilecache(narrowspec.FILENAME)
+ def narrowpats(self):
+ return narrowspec.load(self)
+
+ @localrepo.repofilecache(narrowspec.FILENAME)
+ def _narrowmatch(self):
+ include, exclude = self.narrowpats
+ if not opts_narrow and not include and not exclude:
+ return matchmod.always(self.root, '')
+ return narrowspec.match(self.root, include=include, exclude=exclude)
+
+ # TODO(martinvonz): make this property-like instead?
+ def narrowmatch(self):
+ return self._narrowmatch
+
+ def setnarrowpats(self, newincludes, newexcludes):
+ narrowspec.save(self, newincludes, newexcludes)
+ self.invalidate(clearfilecache=True)
+
+ # I'm not sure this is the right place to do this filter.
+ # context._manifestmatches() would probably be better, or perhaps
+ # move it to a later place, in case some of the callers do want to know
+ # which directories changed. This seems to work for now, though.
+ def status(self, *args, **kwargs):
+ s = super(narrowrepository, self).status(*args, **kwargs)
+ narrowmatch = self.narrowmatch()
+ modified = filter(narrowmatch, s.modified)
+ added = filter(narrowmatch, s.added)
+ removed = filter(narrowmatch, s.removed)
+ deleted = filter(narrowmatch, s.deleted)
+ unknown = filter(narrowmatch, s.unknown)
+ ignored = filter(narrowmatch, s.ignored)
+ clean = filter(narrowmatch, s.clean)
+ return scmutil.status(modified, added, removed, deleted, unknown,
+ ignored, clean)
+
+ repo.__class__ = narrowrepository
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowrevlog.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,163 @@
+# narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+ manifest,
+ revlog,
+ util,
+)
+
+ELLIPSIS_NODE_FLAG = 1 << 14
+revlog.REVIDX_KNOWN_FLAGS |= ELLIPSIS_NODE_FLAG
+if (util.safehasattr(revlog, 'REVIDX_FLAGS_ORDER') and
+ ELLIPSIS_NODE_FLAG not in revlog.REVIDX_FLAGS_ORDER):
+ revlog.REVIDX_FLAGS_ORDER.append(ELLIPSIS_NODE_FLAG)
+
+def readtransform(self, text):
+ return text, False
+
+def writetransform(self, text):
+ return text, False
+
+def rawtransform(self, text):
+ return False
+
+if util.safehasattr(revlog, 'addflagprocessor'):
+ revlog.addflagprocessor(ELLIPSIS_NODE_FLAG,
+ (readtransform, writetransform, rawtransform))
+
+def setup():
+ # We just wanted to add the flag processor, which is done at module
+ # load time.
+ pass
+
+class excludeddir(manifest.treemanifest):
+ def __init__(self, dir, node):
+ super(excludeddir, self).__init__(dir)
+ self._node = node
+ # Add an empty file, which will be included by iterators and such,
+ # appearing as the directory itself (i.e. something like "dir/")
+ self._files[''] = node
+ self._flags[''] = 't'
+
+ # Manifests outside the narrowspec should never be modified, so avoid
+ # copying. This makes a noticeable difference when there are very many
+ # directories outside the narrowspec. Also, it makes sense for the copy to
+ # be of the same type as the original, which would not happen with the
+ # super type's copy().
+ def copy(self):
+ return self
+
+class excludeddirmanifestctx(manifest.treemanifestctx):
+ def __init__(self, dir, node):
+ self._dir = dir
+ self._node = node
+
+ def read(self):
+ return excludeddir(self._dir, self._node)
+
+ def write(self, *args):
+ raise AssertionError('Attempt to write manifest from excluded dir %s' %
+ self._dir)
+
+class excludedmanifestrevlog(manifest.manifestrevlog):
+ def __init__(self, dir):
+ self._dir = dir
+
+ def __len__(self):
+ raise AssertionError('Attempt to get length of excluded dir %s' %
+ self._dir)
+
+ def rev(self, node):
+ raise AssertionError('Attempt to get rev from excluded dir %s' %
+ self._dir)
+
+ def linkrev(self, node):
+ raise AssertionError('Attempt to get linkrev from excluded dir %s' %
+ self._dir)
+
+ def node(self, rev):
+ raise AssertionError('Attempt to get node from excluded dir %s' %
+ self._dir)
+
+ def add(self, *args, **kwargs):
+ # We should never write entries in dirlogs outside the narrow clone.
+ # However, the method still gets called from writesubtree() in
+ # _addtree(), so we need to handle it. We should possibly make that
+ # avoid calling add() with a clean manifest (_dirty is always False
+ # in excludeddir instances).
+ pass
+
+def makenarrowmanifestrevlog(mfrevlog, repo):
+ if util.safehasattr(mfrevlog, '_narrowed'):
+ return
+
+ class narrowmanifestrevlog(mfrevlog.__class__):
+ # This function is called via debug{revlog,index,data}, but also during
+ # at least some push operations. This will be used to wrap/exclude the
+ # child directories when using treemanifests.
+ def dirlog(self, dir):
+ if dir and not dir.endswith('/'):
+ dir = dir + '/'
+ if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
+ return excludedmanifestrevlog(dir)
+ result = super(narrowmanifestrevlog, self).dirlog(dir)
+ makenarrowmanifestrevlog(result, repo)
+ return result
+
+ mfrevlog.__class__ = narrowmanifestrevlog
+ mfrevlog._narrowed = True
+
+def makenarrowmanifestlog(mfl, repo):
+ class narrowmanifestlog(mfl.__class__):
+ def get(self, dir, node, verify=True):
+ if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
+ return excludeddirmanifestctx(dir, node)
+ return super(narrowmanifestlog, self).get(dir, node, verify=verify)
+ mfl.__class__ = narrowmanifestlog
+
+def makenarrowfilelog(fl, narrowmatch):
+ class narrowfilelog(fl.__class__):
+ def renamed(self, node):
+ m = super(narrowfilelog, self).renamed(node)
+ if m and not narrowmatch(m[0]):
+ return None
+ return m
+
+ def size(self, rev):
+ # We take advantage of the fact that remotefilelog
+ # lacks a node() method to just skip the
+ # rename-checking logic when on remotefilelog. This
+ # might be incorrect on other non-revlog-based storage
+ # engines, but for now this seems to be fine.
+ if util.safehasattr(self, 'node'):
+ node = self.node(rev)
+ # Because renamed() is overridden above to
+ # sometimes return None even if there is metadata
+ # in the revlog, size can be incorrect for
+ # copies/renames, so we need to make sure we call
+ # the super class's implementation of renamed()
+ # for the purpose of size calculation.
+ if super(narrowfilelog, self).renamed(node):
+ return len(self.read(node))
+ return super(narrowfilelog, self).size(rev)
+
+ def cmp(self, node, text):
+ different = super(narrowfilelog, self).cmp(node, text)
+ if different:
+ # Similar to size() above, if the file was copied from
+ # a file outside the narrowspec, the super class's
+ # would have returned True because we tricked it into
+ # thinking that the file was not renamed.
+ if super(narrowfilelog, self).renamed(node):
+ t2 = self.read(node)
+ return t2 != text
+ return different
+
+ fl.__class__ = narrowfilelog
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowspec.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,204 @@
+# narrowspec.py - methods for working with a narrow view of a repository
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import errno
+
+from mercurial.i18n import _
+from mercurial import (
+ error,
+ match as matchmod,
+ util,
+)
+
+from .. import (
+ share,
+)
+
+FILENAME = 'narrowspec'
+
+def _parsestoredpatterns(text):
+ """Parses the narrowspec format that's stored on disk."""
+ patlist = None
+ includepats = []
+ excludepats = []
+ for l in text.splitlines():
+ if l == '[includes]':
+ if patlist is None:
+ patlist = includepats
+ else:
+ raise error.Abort(_('narrowspec includes section must appear '
+ 'at most once, before excludes'))
+ elif l == '[excludes]':
+ if patlist is not excludepats:
+ patlist = excludepats
+ else:
+ raise error.Abort(_('narrowspec excludes section must appear '
+ 'at most once'))
+ else:
+ patlist.append(l)
+
+ return set(includepats), set(excludepats)
+
+def parseserverpatterns(text):
+ """Parses the narrowspec format that's returned by the server."""
+ includepats = set()
+ excludepats = set()
+
+ # We get one entry per line, in the format "<key> <value>".
+ # It's OK for value to contain other spaces.
+ for kp in (l.split(' ', 1) for l in text.splitlines()):
+ if len(kp) != 2:
+ raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
+ key = kp[0]
+ pat = kp[1]
+ if key == 'include':
+ includepats.add(pat)
+ elif key == 'exclude':
+ excludepats.add(pat)
+ else:
+ raise error.Abort(_('Invalid key "%s" in server response') % key)
+
+ return includepats, excludepats
+
+def normalizesplitpattern(kind, pat):
+ """Returns the normalized version of a pattern and kind.
+
+ Returns a tuple with the normalized kind and normalized pattern.
+ """
+ pat = pat.rstrip('/')
+ _validatepattern(pat)
+ return kind, pat
+
+def _numlines(s):
+ """Returns the number of lines in s, including ending empty lines."""
+ # We use splitlines because it is Unicode-friendly and thus Python 3
+ # compatible. However, it does not count empty lines at the end, so trick
+ # it by adding a character at the end.
+ return len((s + 'x').splitlines())
+
+def _validatepattern(pat):
+ """Validates the pattern and aborts if it is invalid."""
+
+ # We use newlines as separators in the narrowspec file, so don't allow them
+ # in patterns.
+ if _numlines(pat) > 1:
+ raise error.Abort('newlines are not allowed in narrowspec paths')
+
+ components = pat.split('/')
+ if '.' in components or '..' in components:
+ raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
+
+def normalizepattern(pattern, defaultkind='path'):
+ """Returns the normalized version of a text-format pattern.
+
+ If the pattern has no kind, the default will be added.
+ """
+ kind, pat = matchmod._patsplit(pattern, defaultkind)
+ return '%s:%s' % normalizesplitpattern(kind, pat)
+
+def parsepatterns(pats):
+ """Parses a list of patterns into a typed pattern set."""
+ return set(normalizepattern(p) for p in pats)
+
+def format(includes, excludes):
+ output = '[includes]\n'
+ for i in sorted(includes - excludes):
+ output += i + '\n'
+ output += '[excludes]\n'
+ for e in sorted(excludes):
+ output += e + '\n'
+ return output
+
+def match(root, include=None, exclude=None):
+ if not include:
+ # Passing empty include and empty exclude to matchmod.match()
+ # gives a matcher that matches everything, so explicitly use
+ # the nevermatcher.
+ return matchmod.never(root, '')
+ return matchmod.match(root, '', [], include=include or [],
+ exclude=exclude or [])
+
+def needsexpansion(includes):
+ return [i for i in includes if i.startswith('include:')]
+
+def load(repo):
+ if repo.shared():
+ repo = share._getsrcrepo(repo)
+ try:
+ spec = repo.vfs.read(FILENAME)
+ except IOError as e:
+ # Treat "narrowspec does not exist" the same as "narrowspec file exists
+ # and is empty".
+ if e.errno == errno.ENOENT:
+ # Without this the next call to load will use the cached
+ # non-existence of the file, which can cause some odd issues.
+ repo.invalidate(clearfilecache=True)
+ return set(), set()
+ raise
+ return _parsestoredpatterns(spec)
+
+def save(repo, includepats, excludepats):
+ spec = format(includepats, excludepats)
+ if repo.shared():
+ repo = share._getsrcrepo(repo)
+ repo.vfs.write(FILENAME, spec)
+
+def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes,
+ invalid_includes=None):
+ r""" Restricts the patterns according to repo settings,
+ results in a logical AND operation
+
+ :param req_includes: requested includes
+ :param req_excludes: requested excludes
+ :param repo_includes: repo includes
+ :param repo_excludes: repo excludes
+ :param invalid_includes: an array to collect invalid includes
+ :return: include and exclude patterns
+
+ >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
+ (set(['f1']), {})
+ >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
+ (set(['f1']), {})
+ >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
+ (set(['f1/fc1']), {})
+ >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
+ ([], set(['path:.']))
+ >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
+ (set(['f2/fc2']), {})
+ >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
+ ([], set(['path:.']))
+ >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
+ (set(['f1/$non_exitent_var']), {})
+ """
+ res_excludes = req_excludes.copy()
+ res_excludes.update(repo_excludes)
+ if not req_includes:
+ res_includes = set(repo_includes)
+ elif 'path:.' not in repo_includes:
+ res_includes = []
+ for req_include in req_includes:
+ req_include = util.expandpath(util.normpath(req_include))
+ if req_include in repo_includes:
+ res_includes.append(req_include)
+ continue
+ valid = False
+ for repo_include in repo_includes:
+ if req_include.startswith(repo_include + '/'):
+ valid = True
+ res_includes.append(req_include)
+ break
+ if not valid and invalid_includes is not None:
+ invalid_includes.append(req_include)
+ if len(res_includes) == 0:
+ res_excludes = {'path:.'}
+ else:
+ res_includes = set(res_includes)
+ else:
+ res_includes = set(req_includes)
+ return res_includes, res_excludes
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowtemplates.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,50 @@
+# narrowtemplates.py - added template keywords for narrow clones
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+ revset,
+ templatekw,
+ util,
+)
+
+from . import narrowrevlog
+
+def _isellipsis(repo, rev):
+ if repo.changelog.flags(rev) & narrowrevlog.ELLIPSIS_NODE_FLAG:
+ return True
+ return False
+
+def ellipsis(repo, ctx, templ, **args):
+ """:ellipsis: String. 'ellipsis' if the change is an ellipsis node,
+ else ''."""
+ if _isellipsis(repo, ctx.rev()):
+ return 'ellipsis'
+ return ''
+
+def outsidenarrow(repo, ctx, templ, **args):
+ """:outsidenarrow: String. 'outsidenarrow' if the change affects no
+ tracked files, else ''."""
+ if util.safehasattr(repo, 'narrowmatch'):
+ m = repo.narrowmatch()
+ if not any(m(f) for f in ctx.files()):
+ return 'outsidenarrow'
+ return ''
+
+def ellipsisrevset(repo, subset, x):
+ """``ellipsis()``
+ Changesets that are ellipsis nodes.
+ """
+ return subset.filter(lambda r: _isellipsis(repo, r))
+
+def setup():
+ templatekw.keywords['ellipsis'] = ellipsis
+ templatekw.keywords['outsidenarrow'] = outsidenarrow
+
+ revset.symbols['ellipsis'] = ellipsisrevset
+ revset.safesymbols.add('ellipsis')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowwirepeer.py Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,51 @@
+# narrowwirepeer.py - passes narrow spec with unbundle command
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+ error,
+ extensions,
+ hg,
+ node,
+)
+
+from . import narrowspec
+
+def uisetup():
+ def peersetup(ui, peer):
+ # We must set up the expansion before reposetup below, since it's used
+ # at clone time before we have a repo.
+ class expandingpeer(peer.__class__):
+ def expandnarrow(self, narrow_include, narrow_exclude, nodes):
+ ui.status(_("expanding narrowspec\n"))
+ if not self.capable('expandnarrow'):
+ raise error.Abort(
+ 'peer does not support expanding narrowspecs')
+
+ hex_nodes = (node.hex(n) for n in nodes)
+ new_narrowspec = self._call(
+ 'expandnarrow',
+ includepats=','.join(narrow_include),
+ excludepats=','.join(narrow_exclude),
+ nodes=','.join(hex_nodes))
+
+ return narrowspec.parseserverpatterns(new_narrowspec)
+ peer.__class__ = expandingpeer
+ hg.wirepeersetupfuncs.append(peersetup)
+
+def reposetup(repo):
+ def wirereposetup(ui, peer):
+ def wrapped(orig, cmd, *args, **kwargs):
+ if cmd == 'unbundle':
+ include, exclude = repo.narrowpats
+ kwargs["includepats"] = ','.join(include)
+ kwargs["excludepats"] = ','.join(exclude)
+ return orig(cmd, *args, **kwargs)
+ extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
+ hg.wirepeersetupfuncs.append(wirereposetup)
--- a/setup.py Mon Feb 12 16:51:30 2018 -0500
+++ b/setup.py Mon Jan 29 16:19:33 2018 -0500
@@ -792,7 +792,8 @@
'mercurial.thirdparty.attr',
'hgext', 'hgext.convert', 'hgext.fsmonitor',
'hgext.fsmonitor.pywatchman', 'hgext.highlight',
- 'hgext.largefiles', 'hgext.lfs', 'hgext.zeroconf', 'hgext3rd',
+ 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
+ 'hgext.zeroconf', 'hgext3rd',
'hgdemandimport']
common_depends = ['mercurial/bitmanipulation.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/narrow-library.sh Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,9 @@
+cat >> $HGRCPATH <<EOF
+[extensions]
+narrow=
+[ui]
+ssh=python "$TESTDIR/dummyssh"
+[experimental]
+bundle2-exp = True
+changegroup3 = True
+EOF
--- a/tests/test-help.t Mon Feb 12 16:51:30 2018 -0500
+++ b/tests/test-help.t Mon Jan 29 16:19:33 2018 -0500
@@ -1494,6 +1494,8 @@
Extensions:
clonebundles advertise pre-generated bundles to seed clones
+ narrow create clones which fetch history data for subset of files
+ (EXPERIMENTAL)
prefixedname matched against word "clone"
relink recreates hardlinks between repository clones
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-acl.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,42 @@
+Make a narrow clone then archive it
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+
+ $ for x in `$TESTDIR/seq.py 3`; do
+ > echo $x > "f$x"
+ > hg add "f$x"
+ > hg commit -m "Add $x"
+ > done
+ $ cat >> .hg/hgrc << EOF
+ > [narrowhgacl]
+ > default.includes=f1 f2
+ > EOF
+ $ hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid
+ $ cat hg.pid >> "$DAEMON_PIDS"
+
+ $ cd ..
+ $ hg clone http://localhost:$HGPORT1 narrowclone1
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 2 files
+ new changesets * (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+The clone directory should only contain f1 and f2
+ $ ls -1 narrowclone1 | sort
+ f1
+ f2
+
+Requirements should contain narrowhg
+ $ cat narrowclone1/.hg/requires | grep narrowhg
+ narrowhg
+
+NarrowHG should track f1 and f2
+ $ hg -R narrowclone1 tracked
+ I path:f1
+ I path:f2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-archive.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,32 @@
+Make a narrow clone then archive it
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+
+ $ for x in `$TESTDIR/seq.py 3`; do
+ > echo $x > "f$x"
+ > hg add "f$x"
+ > hg commit -m "Add $x"
+ > done
+
+ $ hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid
+ $ cat hg.pid >> "$DAEMON_PIDS"
+
+ $ cd ..
+ $ hg clone --narrow --include f1 --include f2 http://localhost:$HGPORT1/ narrowclone1
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 2 files
+ new changesets * (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+The tar should only contain f1 and f2
+ $ cd narrowclone1
+ $ hg archive -t tgz repo.tgz
+ $ tar tfz repo.tgz
+ repo/f1
+ repo/f2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-clone-no-ellipsis.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,130 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ mkdir dir
+ $ mkdir dir/src
+ $ cd dir/src
+ $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done
+ $ cd ..
+ $ mkdir tests
+ $ cd tests
+ $ for x in `$TESTDIR/seq.py 20`; do echo $x > "t$x"; hg add "t$x"; hg commit -m "Commit test $x"; done
+ $ cd ../../..
+
+narrow clone a file, f10
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 40 changesets with 1 changes to 1 files
+ new changesets *:* (glob)
+ $ cd narrow
+ $ cat .hg/requires | grep -v generaldelta
+ dotencode
+ fncache
+ narrowhg
+ revlogv1
+ store
+
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir/src/f10
+ [excludes]
+ $ hg update
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ find * | sort
+ dir
+ dir/src
+ dir/src/f10
+ $ cat dir/src/f10
+ 10
+
+ $ cd ..
+
+narrow clone a directory, tests/, except tests/t19
+
+ $ hg clone --narrow ssh://user@dummy/master narrowdir --noupdate --include "dir/tests/" --exclude "dir/tests/t19"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 40 changesets with 19 changes to 19 files
+ new changesets *:* (glob)
+ $ cd narrowdir
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir/tests
+ [excludes]
+ path:dir/tests/t19
+ $ hg update
+ 19 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ find * | sort
+ dir
+ dir/tests
+ dir/tests/t1
+ dir/tests/t10
+ dir/tests/t11
+ dir/tests/t12
+ dir/tests/t13
+ dir/tests/t14
+ dir/tests/t15
+ dir/tests/t16
+ dir/tests/t17
+ dir/tests/t18
+ dir/tests/t2
+ dir/tests/t20
+ dir/tests/t3
+ dir/tests/t4
+ dir/tests/t5
+ dir/tests/t6
+ dir/tests/t7
+ dir/tests/t8
+ dir/tests/t9
+
+ $ cd ..
+
+narrow clone everything but a directory (tests/)
+
+ $ hg clone --narrow ssh://user@dummy/master narrowroot --noupdate --exclude "dir/tests"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 40 changesets with 20 changes to 20 files
+ new changesets *:* (glob)
+ $ cd narrowroot
+ $ cat .hg/narrowspec
+ [includes]
+ path:.
+ [excludes]
+ path:dir/tests
+ $ hg update
+ 20 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ find * | sort
+ dir
+ dir/src
+ dir/src/f1
+ dir/src/f10
+ dir/src/f11
+ dir/src/f12
+ dir/src/f13
+ dir/src/f14
+ dir/src/f15
+ dir/src/f16
+ dir/src/f17
+ dir/src/f18
+ dir/src/f19
+ dir/src/f2
+ dir/src/f20
+ dir/src/f3
+ dir/src/f4
+ dir/src/f5
+ dir/src/f6
+ dir/src/f7
+ dir/src/f8
+ dir/src/f9
+
+ $ cd ..
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-clone-non-narrow-server.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,53 @@
+Test attempting a narrow clone against a server that doesn't support narrowhg.
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+
+ $ for x in `$TESTDIR/seq.py 10`; do
+ > echo $x > "f$x"
+ > hg add "f$x"
+ > hg commit -m "Add $x"
+ > done
+
+ $ hg serve -a localhost -p $HGPORT1 --config extensions.narrow=! -d \
+ > --pid-file=hg.pid
+ $ cat hg.pid >> "$DAEMON_PIDS"
+ $ hg serve -a localhost -p $HGPORT2 -d --pid-file=hg.pid
+ $ cat hg.pid >> "$DAEMON_PIDS"
+
+Verify that narrow is advertised in the bundle2 capabilities:
+ $ echo capabilities | hg -R . serve --stdio | \
+ > python -c "import sys, urllib; print urllib.unquote_plus(list(sys.stdin)[1])" | grep narrow
+ narrow=v0
+
+ $ cd ..
+
+ $ hg clone --narrow --include f1 http://localhost:$HGPORT1/ narrowclone
+ requesting all changes
+ abort: server doesn't support narrow clones
+ [255]
+
+Make a narrow clone (via HGPORT2), then try to narrow and widen
+into it (from HGPORT1) to prove that narrowing is fine and widening fails
+gracefully:
+ $ hg clone -r 0 --narrow --include f1 http://localhost:$HGPORT2/ narrowclone
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ new changesets * (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrowclone
+ $ hg tracked --addexclude f2 http://localhost:$HGPORT1/
+ comparing with http://localhost:$HGPORT1/
+ searching for changes
+ looking for local changes to affected paths
+ $ hg tracked --addinclude f1 http://localhost:$HGPORT1/
+ comparing with http://localhost:$HGPORT1/
+ searching for changes
+ no changes found
+ abort: server doesn't support narrow clones
+ [255]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-clone-nonlinear.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,148 @@
+Testing narrow clones when changesets modifying a matching file exist on
+multiple branches
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ hg branch default
+ marked working directory as branch default
+ (branches are permanent and global, did you want a bookmark?)
+ $ for x in `$TESTDIR/seq.py 10`; do
+ > echo $x > "f$x"
+ > hg add "f$x"
+ > hg commit -m "Add $x"
+ > done
+
+ $ hg branch release-v1
+ marked working directory as branch release-v1
+ (branches are permanent and global, did you want a bookmark?)
+ $ hg commit -m "Start release for v1"
+
+ $ hg update default
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ for x in `$TESTDIR/seq.py 10`; do
+ > echo "$x v2" > "f$x"
+ > hg commit -m "Update $x to v2"
+ > done
+
+ $ hg update release-v1
+ 10 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch release-v1
+ marked working directory as branch release-v1
+ $ for x in `$TESTDIR/seq.py 1 5`; do
+ > echo "$x v1 hotfix" > "f$x"
+ > hg commit -m "Hotfix $x in v1"
+ > done
+
+ $ hg update default
+ 10 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch release-v2
+ marked working directory as branch release-v2
+ $ hg commit -m "Start release for v2"
+
+ $ hg update default
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch default
+ marked working directory as branch default
+ $ for x in `$TESTDIR/seq.py 10`; do
+ > echo "$x v3" > "f$x"
+ > hg commit -m "Update $x to v3"
+ > done
+
+ $ hg update release-v2
+ 10 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg branch release-v2
+ marked working directory as branch release-v2
+ $ for x in `$TESTDIR/seq.py 4 9`; do
+ > echo "$x v2 hotfix" > "f$x"
+ > hg commit -m "Hotfix $x in v2"
+ > done
+
+ $ hg heads -T '{rev} <- {p1rev} ({branch}): {desc}\n'
+ 42 <- 41 (release-v2): Hotfix 9 in v2
+ 36 <- 35 (default): Update 10 to v3
+ 25 <- 24 (release-v1): Hotfix 5 in v1
+
+ $ cd ..
+
+We now have 3 branches: default, which has v3 of all files, release-v1 which
+has v1 of all files, and release-v2 with v2 of all files.
+
+Narrow clone which should get all branches
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include "f5"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 12 changesets with 5 changes to 1 files (+2 heads)
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ hg log -G -T "{if(ellipsis, '...')}{node|short} ({branch}): {desc}\n"
+ o ...031f516143fe (release-v2): Hotfix 9 in v2
+ |
+ o 9cd7f7bb9ca1 (release-v2): Hotfix 5 in v2
+ |
+ o ...37bbc88f3ef0 (release-v2): Hotfix 4 in v2
+ |
+ | @ ...dae2f368ca07 (default): Update 10 to v3
+ | |
+ | o 9c224e89cb31 (default): Update 5 to v3
+ | |
+ | o ...04fb59c7c9dc (default): Update 4 to v3
+ |/
+ | o b2253e82401f (release-v1): Hotfix 5 in v1
+ | |
+ | o ...960ac37d74fd (release-v1): Hotfix 4 in v1
+ | |
+ o | 986298e3f347 (default): Update 5 to v2
+ | |
+ o | ...75d539c667ec (default): Update 4 to v2
+ |/
+ o 04c71bd5707f (default): Add 5
+ |
+ o ...881b3891d041 (default): Add 4
+
+
+Narrow clone the first file, hitting edge condition where unaligned
+changeset and manifest revnums cross branches.
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include "f1"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 10 changesets with 4 changes to 1 files (+2 heads)
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ hg log -G -T "{if(ellipsis, '...')}{node|short} ({branch}): {desc}\n"
+ o ...031f516143fe (release-v2): Hotfix 9 in v2
+ |
+ | @ ...dae2f368ca07 (default): Update 10 to v3
+ | |
+ | o 1f5d184b8e96 (default): Update 1 to v3
+ |/
+ | o ...b2253e82401f (release-v1): Hotfix 5 in v1
+ | |
+ | o 133502f6b7e5 (release-v1): Hotfix 1 in v1
+ | |
+ o | ...79165c83d644 (default): Update 10 to v2
+ | |
+ o | c7b7a5f2f088 (default): Update 1 to v2
+ | |
+ | o ...f0531a3db7a9 (release-v1): Start release for v1
+ |/
+ o ...6a3f0f0abef3 (default): Add 10
+ |
+ o e012ac15eaaa (default): Add 1
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-clone.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,225 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ mkdir dir
+ $ mkdir dir/src
+ $ cd dir/src
+ $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done
+ $ cd ..
+ $ mkdir tests
+ $ cd tests
+ $ for x in `$TESTDIR/seq.py 20`; do echo $x > "t$x"; hg add "t$x"; hg commit -m "Commit test $x"; done
+ $ cd ../../..
+
+narrow clone a file, f10
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 1 changes to 1 files
+ new changesets *:* (glob)
+ $ cd narrow
+ $ cat .hg/requires | grep -v generaldelta
+ dotencode
+ fncache
+ narrowhg
+ revlogv1
+ store
+
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir/src/f10
+ [excludes]
+ $ hg tracked
+ I path:dir/src/f10
+ $ hg update
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ find * | sort
+ dir
+ dir/src
+ dir/src/f10
+ $ cat dir/src/f10
+ 10
+
+ $ cd ..
+
+narrow clone with a newline should fail
+
+ $ hg clone --narrow ssh://user@dummy/master narrow_fail --noupdate --include 'dir/src/f10
+ > '
+ requesting all changes
+ abort: newlines are not allowed in narrowspec paths
+ [255]
+
+narrow clone a directory, tests/, except tests/t19
+
+ $ hg clone --narrow ssh://user@dummy/master narrowdir --noupdate --include "dir/tests/" --exclude "dir/tests/t19"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 21 changesets with 19 changes to 19 files
+ new changesets *:* (glob)
+ $ cd narrowdir
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir/tests
+ [excludes]
+ path:dir/tests/t19
+ $ hg tracked
+ I path:dir/tests
+ X path:dir/tests/t19
+ $ hg update
+ 19 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ find * | sort
+ dir
+ dir/tests
+ dir/tests/t1
+ dir/tests/t10
+ dir/tests/t11
+ dir/tests/t12
+ dir/tests/t13
+ dir/tests/t14
+ dir/tests/t15
+ dir/tests/t16
+ dir/tests/t17
+ dir/tests/t18
+ dir/tests/t2
+ dir/tests/t20
+ dir/tests/t3
+ dir/tests/t4
+ dir/tests/t5
+ dir/tests/t6
+ dir/tests/t7
+ dir/tests/t8
+ dir/tests/t9
+
+ $ cd ..
+
+narrow clone everything but a directory (tests/)
+
+ $ hg clone --narrow ssh://user@dummy/master narrowroot --noupdate --exclude "dir/tests"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 21 changesets with 20 changes to 20 files
+ new changesets *:* (glob)
+ $ cd narrowroot
+ $ cat .hg/narrowspec
+ [includes]
+ path:.
+ [excludes]
+ path:dir/tests
+ $ hg tracked
+ I path:.
+ X path:dir/tests
+ $ hg update
+ 20 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ find * | sort
+ dir
+ dir/src
+ dir/src/f1
+ dir/src/f10
+ dir/src/f11
+ dir/src/f12
+ dir/src/f13
+ dir/src/f14
+ dir/src/f15
+ dir/src/f16
+ dir/src/f17
+ dir/src/f18
+ dir/src/f19
+ dir/src/f2
+ dir/src/f20
+ dir/src/f3
+ dir/src/f4
+ dir/src/f5
+ dir/src/f6
+ dir/src/f7
+ dir/src/f8
+ dir/src/f9
+
+ $ cd ..
+
+narrow clone no paths at all
+
+ $ hg clone --narrow ssh://user@dummy/master narrowempty --noupdate
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 0 changes to 0 files
+ new changesets * (glob)
+ $ cd narrowempty
+ $ hg tracked
+ $ hg update
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ ls
+
+ $ cd ..
+
+simple clone
+ $ hg clone ssh://user@dummy/master simpleclone
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 40 changesets with 40 changes to 40 files
+ new changesets * (glob)
+ updating to branch default
+ 40 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd simpleclone
+ $ find * | sort
+ dir
+ dir/src
+ dir/src/f1
+ dir/src/f10
+ dir/src/f11
+ dir/src/f12
+ dir/src/f13
+ dir/src/f14
+ dir/src/f15
+ dir/src/f16
+ dir/src/f17
+ dir/src/f18
+ dir/src/f19
+ dir/src/f2
+ dir/src/f20
+ dir/src/f3
+ dir/src/f4
+ dir/src/f5
+ dir/src/f6
+ dir/src/f7
+ dir/src/f8
+ dir/src/f9
+ dir/tests
+ dir/tests/t1
+ dir/tests/t10
+ dir/tests/t11
+ dir/tests/t12
+ dir/tests/t13
+ dir/tests/t14
+ dir/tests/t15
+ dir/tests/t16
+ dir/tests/t17
+ dir/tests/t18
+ dir/tests/t19
+ dir/tests/t2
+ dir/tests/t20
+ dir/tests/t3
+ dir/tests/t4
+ dir/tests/t5
+ dir/tests/t6
+ dir/tests/t7
+ dir/tests/t8
+ dir/tests/t9
+
+ $ cd ..
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-commit-tree.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,21 @@
+ $ cd $TESTDIR && python $RUNTESTDIR/run-tests.py \
+ > --extra-config-opt experimental.treemanifest=1 test-narrow-commit.t 2>&1 | \
+ > grep -v 'unexpected mercurial lib' | egrep -v '\(expected'
+
+ --- */tests/test-narrow-commit.t (glob)
+ +++ */tests/test-narrow-commit.t.err (glob)
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ created new head
+ $ hg files -r .
+ inside/f1
+ - outside/f1
+ + outside/
+ Some filesystems (notably FAT/exFAT only store timestamps with 2
+ seconds of precision, so by sleeping for 3 seconds, we can ensure that
+ the timestamps of files stored by dirstate will appear older than the
+
+ ERROR: test-narrow-commit.t output changed
+ !
+ Failed test-narrow-commit.t: output changed
+ # Ran 1 tests, 0 skipped, 1 failed.
+ python hash seed: * (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-commit.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,79 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+
+ $ mkdir inside
+ $ echo inside > inside/f1
+ $ mkdir outside
+ $ echo outside > outside/f1
+ $ hg ci -Aqm 'initial'
+
+ $ echo modified > inside/f1
+ $ hg ci -qm 'modify inside'
+
+ $ echo modified > outside/f1
+ $ hg ci -qm 'modify outside'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+
+ $ hg update -q 0
+
+Can not modify dirstate outside
+
+ $ mkdir outside
+ $ touch outside/f1
+ $ hg debugwalk -I 'relglob:f1'
+ matcher: <includematcher includes='(?:(?:|.*/)f1(?:/|$))'>
+ f inside/f1 inside/f1
+ $ hg add outside/f1
+ abort: cannot track 'outside/f1' - it is outside the narrow clone
+ [255]
+ $ touch outside/f3
+ $ hg add outside/f3
+ abort: cannot track 'outside/f3' - it is outside the narrow clone
+ [255]
+ $ rm -r outside
+
+Can modify dirstate inside
+
+ $ echo modified > inside/f1
+ $ touch inside/f3
+ $ hg add inside/f3
+ $ hg status
+ M inside/f1
+ A inside/f3
+ $ hg revert -qC .
+ $ rm inside/f3
+
+Can commit changes inside. Leaves outside unchanged.
+
+ $ hg update -q 'desc("initial")'
+ $ echo modified2 > inside/f1
+ $ hg commit -m 'modify inside/f1'
+ created new head
+ $ hg files -r .
+ inside/f1
+ outside/f1
+Some filesystems (notably FAT/exFAT only store timestamps with 2
+seconds of precision, so by sleeping for 3 seconds, we can ensure that
+the timestamps of files stored by dirstate will appear older than the
+dirstate file, and therefore we'll be able to get stable output from
+debugdirstate. If we don't do this, the test can be slightly flaky.
+ $ sleep 3
+ $ hg status
+ $ hg debugdirstate --nodates
+ n 644 10 set inside/f1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-copies.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,57 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+
+ $ mkdir inside
+ $ echo inside > inside/f1
+ $ mkdir outside
+ $ echo outside > outside/f2
+ $ hg ci -Aqm 'initial'
+
+ $ hg mv outside/f2 inside/f2
+ $ hg ci -qm 'move f2 from outside'
+
+ $ echo modified > inside/f2
+ $ hg ci -qm 'modify inside/f2'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 3 changes to 2 files
+ new changesets *:* (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+
+ $ hg co 'desc("move f2")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg status
+ $ hg diff
+ $ hg diff --change . --git
+ diff --git a/inside/f2 b/inside/f2
+ new file mode 100644
+ --- /dev/null
+ +++ b/inside/f2
+ @@ -0,0 +1,1 @@
+ +outside
+
+ $ hg log --follow inside/f2 -r tip
+ changeset: 2:bcfb756e0ca9
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify inside/f2
+
+ changeset: 1:5a016133b2bb
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: move f2 from outside
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-debugcommands.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,43 @@
+ $ . "$TESTDIR/narrow-library.sh"
+ $ hg init repo
+ $ cd repo
+ $ cat << EOF > .hg/narrowspec
+ > [includes]
+ > path:foo
+ > [excludes]
+ > EOF
+ $ echo treemanifest >> .hg/requires
+ $ echo narrowhg >> .hg/requires
+ $ mkdir -p foo/bar
+ $ echo b > foo/f
+ $ echo c > foo/bar/f
+ $ hg commit -Am hi
+ adding foo/bar/f
+ adding foo/f
+ $ hg debugindex -m
+ rev offset length delta linkrev nodeid p1 p2
+ 0 0 47 -1 0 14a5d056d75a 000000000000 000000000000
+ $ hg debugindex --dir foo
+ rev offset length delta linkrev nodeid p1 p2
+ 0 0 77 -1 0 e635c7857aef 000000000000 000000000000
+ $ hg debugindex --dir foo/
+ rev offset length delta linkrev nodeid p1 p2
+ 0 0 77 -1 0 e635c7857aef 000000000000 000000000000
+ $ hg debugindex --dir foo/bar
+ rev offset length delta linkrev nodeid p1 p2
+ 0 0 44 -1 0 e091d4224761 000000000000 000000000000
+ $ hg debugindex --dir foo/bar/
+ rev offset length delta linkrev nodeid p1 p2
+ 0 0 44 -1 0 e091d4224761 000000000000 000000000000
+ $ hg debugdata -m 0
+ foo\x00e635c7857aef92ac761ce5741a99da159abbbb24t (esc)
+ $ hg debugdata --dir foo 0
+ bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc)
+ f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc)
+ $ hg debugdata --dir foo/ 0
+ bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc)
+ f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc)
+ $ hg debugdata --dir foo/bar 0
+ f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc)
+ $ hg debugdata --dir foo/bar/ 0
+ f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-debugrebuilddirstate.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,31 @@
+ $ . "$TESTDIR/narrow-library.sh"
+ $ hg init master
+ $ cd master
+ $ echo treemanifest >> .hg/requires
+ $ echo 'contents of file' > file
+ $ mkdir foo
+ $ echo 'contents of foo/bar' > foo/bar
+ $ hg ci -Am 'some change'
+ adding file
+ adding foo/bar
+
+ $ cd ..
+ $ hg clone --narrow ssh://user@dummy/master copy --include=foo
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ new changesets * (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd copy
+
+ $ hg debugdirstate
+ n * 20 unset foo/bar (glob)
+ $ mv .hg/dirstate .hg/old_dirstate
+ $ dd bs=40 count=1 if=.hg/old_dirstate of=.hg/dirstate 2>/dev/null
+ $ hg debugdirstate
+ $ hg debugrebuilddirstate
+ $ hg debugdirstate
+ n * * unset foo/bar (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-exchange-merges.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,207 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ mkdir inside
+ $ echo 1 > inside/f
+ $ hg commit -Aqm 'initial inside'
+
+ $ mkdir outside
+ $ echo 1 > outside/f
+ $ hg commit -Aqm 'initial outside'
+
+ $ echo 2a > outside/f
+ $ hg commit -Aqm 'outside 2a'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4a > outside/f
+ $ hg commit -Aqm 'outside 4a'
+ $ hg update '.~3'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ echo 2b > outside/f
+ $ hg commit -Aqm 'outside 2b'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4b > outside/f
+ $ hg commit -Aqm 'outside 4b'
+ $ hg update '.~3'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ echo 2c > outside/f
+ $ hg commit -Aqm 'outside 2c'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4c > outside/f
+ $ hg commit -Aqm 'outside 4c'
+ $ hg update '.~3'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ echo 2d > outside/f
+ $ hg commit -Aqm 'outside 2d'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4d > outside/f
+ $ hg commit -Aqm 'outside 4d'
+
+ $ hg update -r 'desc("outside 4a")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 5 > outside/f
+ $ rm outside/f.orig
+ $ hg resolve --mark outside/f
+ (no more unresolved files)
+ $ hg commit -m 'merge a/b 5'
+ $ echo 6 > outside/f
+ $ hg commit -Aqm 'outside 6'
+
+ $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 7 > outside/f
+ $ rm outside/f.orig
+ $ hg resolve --mark outside/f
+ (no more unresolved files)
+ $ hg commit -Aqm 'merge a/b/c 7'
+ $ echo 8 > outside/f
+ $ hg commit -Aqm 'outside 8'
+
+ $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 9 > outside/f
+ $ rm outside/f.orig
+ $ hg resolve --mark outside/f
+ (no more unresolved files)
+ $ hg commit -Aqm 'merge a/b/c/d 9'
+ $ echo 10 > outside/f
+ $ hg commit -Aqm 'outside 10'
+
+ $ echo 11 > inside/f
+ $ hg commit -Aqm 'inside 11'
+ $ echo 12 > outside/f
+ $ hg commit -Aqm 'outside 12'
+
+ $ hg log -G -T '{rev} {node|short} {desc}\n'
+ @ 21 8d874d57adea outside 12
+ |
+ o 20 7ef88b4dd4fa inside 11
+ |
+ o 19 2a20009de83e outside 10
+ |
+ o 18 3ac1f5779de3 merge a/b/c/d 9
+ |\
+ | o 17 38a9c2f7e546 outside 8
+ | |
+ | o 16 094aa62fc898 merge a/b/c 7
+ | |\
+ | | o 15 f29d083d32e4 outside 6
+ | | |
+ | | o 14 2dc11382541d merge a/b 5
+ | | |\
+ o | | | 13 27d07ef97221 outside 4d
+ | | | |
+ o | | | 12 465567bdfb2d inside 3
+ | | | |
+ o | | | 11 d1c61993ec83 outside 2d
+ | | | |
+ | o | | 10 56859a8e33b9 outside 4c
+ | | | |
+ | o | | 9 bb96a08b062a inside 3
+ | | | |
+ | o | | 8 b844052e7b3b outside 2c
+ |/ / /
+ | | o 7 9db2d8fcc2a6 outside 4b
+ | | |
+ | | o 6 6418167787a6 inside 3
+ | | |
+ +---o 5 77344f344d83 outside 2b
+ | |
+ | o 4 9cadde08dc9f outside 4a
+ | |
+ | o 3 019ef06f125b inside 3
+ | |
+ | o 2 75e40c075a19 outside 2a
+ |/
+ o 1 906d6c682641 initial outside
+ |
+ o 0 9f8e82b51004 initial inside
+
+
+Now narrow clone this and get a hopefully correct graph
+
+ $ cd ..
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 14 changesets with 3 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+
+To make updating the tests easier, we print the emitted nodes
+sorted. This makes it easier to identify when the same node structure
+has been emitted, just in a different order.
+
+ $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
+ ...094aa62fc898 6418167787a6 bb96a08b062a merge a/b/c 7
+ ...2a20009de83e 019ef06f125b 3ac1f5779de3 outside 10
+ ...3ac1f5779de3 465567bdfb2d 094aa62fc898 merge a/b/c/d 9
+ ...75e40c075a19 9f8e82b51004 000000000000 outside 2a
+ ...77344f344d83 9f8e82b51004 000000000000 outside 2b
+ ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12
+ ...b844052e7b3b 9f8e82b51004 000000000000 outside 2c
+ ...d1c61993ec83 9f8e82b51004 000000000000 outside 2d
+ 019ef06f125b 75e40c075a19 000000000000 inside 3
+ 465567bdfb2d d1c61993ec83 000000000000 inside 3
+ 6418167787a6 77344f344d83 000000000000 inside 3
+ 7ef88b4dd4fa 2a20009de83e 000000000000 inside 11
+ 9f8e82b51004 000000000000 000000000000 initial inside
+ bb96a08b062a b844052e7b3b 000000000000 inside 3
+
+But seeing the graph is also nice:
+ $ hg log -G -T '{if(ellipsis,"...")}{node|short} {desc}\n'
+ @ ...8d874d57adea outside 12
+ |
+ o 7ef88b4dd4fa inside 11
+ |
+ o ...2a20009de83e outside 10
+ |\
+ | o ...3ac1f5779de3 merge a/b/c/d 9
+ | |\
+ | | o ...094aa62fc898 merge a/b/c 7
+ | | |\
+ | o | | 465567bdfb2d inside 3
+ | | | |
+ | o | | ...d1c61993ec83 outside 2d
+ | | | |
+ | | | o bb96a08b062a inside 3
+ | | | |
+ | +---o ...b844052e7b3b outside 2c
+ | | |
+ | | o 6418167787a6 inside 3
+ | | |
+ | | o ...77344f344d83 outside 2b
+ | |/
+ o | 019ef06f125b inside 3
+ | |
+ o | ...75e40c075a19 outside 2a
+ |/
+ o 9f8e82b51004 initial inside
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-exchange.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,210 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ mkdir inside
+ $ echo 1 > inside/f
+ $ mkdir inside2
+ $ echo 1 > inside2/f
+ $ mkdir outside
+ $ echo 1 > outside/f
+ $ hg ci -Aqm 'initial'
+
+ $ echo 2 > inside/f
+ $ hg ci -qm 'inside 2'
+
+ $ echo 2 > inside2/f
+ $ hg ci -qm 'inside2 2'
+
+ $ echo 2 > outside/f
+ $ hg ci -qm 'outside 2'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ hg clone --narrow ssh://user@dummy/master narrow2 --include inside --include inside2
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 4 changesets with 4 changes to 2 files
+ new changesets *:* (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Can push to wider repo if change does not affect paths in wider repo that are
+not also in narrower repo
+
+ $ cd narrow
+ $ echo 3 > inside/f
+ $ hg ci -m 'inside 3'
+ $ hg push ssh://user@dummy/narrow2
+ pushing to ssh://user@dummy/narrow2
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files
+
+Can push to narrower repo if change affects only paths within remote's
+narrow spec
+
+ $ cd ../narrow2
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ hg co -r 'desc("inside 3")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo 4 > inside/f
+ $ hg ci -m 'inside 4'
+ $ hg push ssh://user@dummy/narrow
+ pushing to ssh://user@dummy/narrow
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files
+
+Can push to narrow repo if change affects only paths outside remote's
+narrow spec
+
+ $ echo 3 > inside2/f
+ $ hg ci -m 'inside2 3'
+TODO: this should be successful
+ $ hg push ssh://user@dummy/narrow
+ pushing to ssh://user@dummy/narrow
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: transaction abort!
+ remote: rollback completed
+ remote: abort: data/inside2/f.i@4a1aa07735e6: unknown parent!
+ abort: stream ended unexpectedly (got 0 bytes, expected 4)
+ [255]
+
+Can pull from wider repo if change affects only paths outside remote's
+narrow spec
+ $ echo 4 > inside2/f
+ $ hg ci -m 'inside2 4'
+ $ hg log -G -T '{rev} {node|short} {files}\n'
+ @ 7 d78a96df731d inside2/f
+ |
+ o 6 8c26f5218962 inside2/f
+ |
+ o 5 ba3480e2f9de inside/f
+ |
+ o 4 4e5edd526618 inside/f
+ |
+ o 3 81e7e07b7ab0 outside/f
+ |
+ o 2 f3993b8c0c2b inside2/f
+ |
+ o 1 8cd66ca966b4 inside/f
+ |
+ o 0 c8057d6f53ab inside/f inside2/f outside/f
+
+ $ cd ../narrow
+ $ hg log -G -T '{rev} {node|short} {files}\n'
+ o 4 ba3480e2f9de inside/f
+ |
+ @ 3 4e5edd526618 inside/f
+ |
+ o 2 81e7e07b7ab0 outside/f
+ |
+ o 1 8cd66ca966b4 inside/f
+ |
+ o 0 c8057d6f53ab inside/f inside2/f outside/f
+
+ $ hg pull ssh://user@dummy/narrow2
+ pulling from ssh://user@dummy/narrow2
+ searching for changes
+ remote: abort: unable to resolve parent while packing 'data/inside2/f.i' 3 for changeset 5 (?)
+ adding changesets
+ remote: abort: unexpected error: unable to resolve parent while packing 'data/inside2/f.i' 3 for changeset 5
+ transaction abort!
+ rollback completed
+ abort: pull failed on remote
+ [255]
+
+Check that the resulting history is valid in the full repo
+
+ $ cd ../narrow2
+ $ hg push ssh://user@dummy/master
+ pushing to ssh://user@dummy/master
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 4 changesets with 4 changes to 2 files
+ $ cd ../master
+ $ hg verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 3 files, 8 changesets, 10 total revisions
+
+Can not push to wider repo if change affects paths in wider repo that are
+not also in narrower repo
+ $ cd ../master
+ $ hg co -r 'desc("inside2 4")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo 5 > inside2/f
+ $ hg ci -m 'inside2 5'
+ $ hg log -G -T '{rev} {node|short} {files}\n'
+ @ 8 5970befb64ba inside2/f
+ |
+ o 7 d78a96df731d inside2/f
+ |
+ o 6 8c26f5218962 inside2/f
+ |
+ o 5 ba3480e2f9de inside/f
+ |
+ o 4 4e5edd526618 inside/f
+ |
+ o 3 81e7e07b7ab0 outside/f
+ |
+ o 2 f3993b8c0c2b inside2/f
+ |
+ o 1 8cd66ca966b4 inside/f
+ |
+ o 0 c8057d6f53ab inside/f inside2/f outside/f
+
+ $ cd ../narrow
+ $ hg pull
+ pulling from ssh://user@dummy/master
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 0 changes to 0 files
+ new changesets * (glob)
+ (run 'hg update' to get a working copy)
+TODO: this should tell the user that their narrow clone does not have the
+necessary content to be able to push to the target
+ $ hg push ssh://user@dummy/narrow2
+ pushing to ssh://user@dummy/narrow2
+ searching for changes
+ remote has heads on branch 'default' that are not known locally: d78a96df731d
+ abort: push creates new remote head 5970befb64ba!
+ (pull and merge or see 'hg help push' for details about pushing new heads)
+ [255]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-expanddirstate.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,170 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+
+ $ mkdir inside
+ $ echo inside > inside/f1
+ $ mkdir outside
+ $ echo outside > outside/f2
+ $ mkdir patchdir
+ $ echo patch_this > patchdir/f3
+ $ hg ci -Aqm 'initial'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ new changesets dff6a2a6d433
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ cd narrow
+
+ $ mkdir outside
+ $ echo other_contents > outside/f2
+ $ grep outside .hg/narrowspec
+ [1]
+ $ grep outside .hg/dirstate
+ [1]
+ $ hg status
+
+`hg status` did not add outside.
+ $ grep outside .hg/narrowspec
+ [1]
+ $ grep outside .hg/dirstate
+ [1]
+
+Unfortunately this is not really a candidate for adding to narrowhg proper,
+since it depends on some other source for providing the manifests (when using
+treemanifests) and file contents. Something like a virtual filesystem and/or
+remotefilelog. We want to be useful when not using those systems, so we do not
+have this method available in narrowhg proper at the moment.
+ $ cat > "$TESTTMP/expand_extension.py" <<EOF
+ > import os
+ > import sys
+ >
+ > from mercurial import extensions
+ > from mercurial import localrepo
+ > from mercurial import match as matchmod
+ > from mercurial import patch
+ > from mercurial import util as hgutil
+ >
+ > def expandnarrowspec(ui, repo, newincludes=None):
+ > if not newincludes:
+ > return
+ > import sys
+ > newincludes = set([newincludes])
+ > narrowhg = extensions.find('narrow')
+ > includes, excludes = repo.narrowpats
+ > currentmatcher = narrowhg.narrowspec.match(repo.root, includes, excludes)
+ > includes = includes | newincludes
+ > if not repo.currenttransaction():
+ > ui.develwarn('expandnarrowspec called outside of transaction!')
+ > repo.setnarrowpats(includes, excludes)
+ > newmatcher = narrowhg.narrowspec.match(repo.root, includes, excludes)
+ > added = matchmod.differencematcher(newmatcher, currentmatcher)
+ > for f in repo['.'].manifest().walk(added):
+ > repo.dirstate.normallookup(f)
+ >
+ > def makeds(ui, repo):
+ > def wrapds(orig, self):
+ > ds = orig(self)
+ > class expandingdirstate(ds.__class__):
+ > # Mercurial 4.4 uses this version.
+ > @hgutil.propertycache
+ > def _map(self):
+ > ret = super(expandingdirstate, self)._map
+ > with repo.wlock(), repo.lock(), repo.transaction(
+ > 'expandnarrowspec'):
+ > expandnarrowspec(ui, repo, os.environ.get('DIRSTATEINCLUDES'))
+ > return ret
+ > # Mercurial 4.3.3 and earlier uses this version. It seems that
+ > # narrowhg does not currently support this version, but we include
+ > # it just in case backwards compatibility is restored.
+ > def _read(self):
+ > ret = super(expandingdirstate, self)._read()
+ > with repo.wlock(), repo.lock(), repo.transaction(
+ > 'expandnarrowspec'):
+ > expandnarrowspec(ui, repo, os.environ.get('DIRSTATEINCLUDES'))
+ > return ret
+ > ds.__class__ = expandingdirstate
+ > return ds
+ > return wrapds
+ >
+ > def reposetup(ui, repo):
+ > extensions.wrapfilecache(localrepo.localrepository, 'dirstate',
+ > makeds(ui, repo))
+ > def overridepatch(orig, *args, **kwargs):
+ > with repo.wlock():
+ > expandnarrowspec(ui, repo, os.environ.get('PATCHINCLUDES'))
+ > return orig(*args, **kwargs)
+ >
+ > extensions.wrapfunction(patch, 'patch', overridepatch)
+ > EOF
+ $ cat >> ".hg/hgrc" <<EOF
+ > [extensions]
+ > expand_extension = $TESTTMP/expand_extension.py
+ > EOF
+
+Since we do not have the ability to rely on a virtual filesystem or
+remotefilelog in the test, we just fake it by copying the data from the 'master'
+repo.
+ $ cp -a ../master/.hg/store/data/* .hg/store/data
+Do that for patchdir as well.
+ $ cp -a ../master/patchdir .
+
+`hg status` will now add outside, but not patchdir.
+ $ DIRSTATEINCLUDES=path:outside hg status
+ M outside/f2
+ $ grep outside .hg/narrowspec
+ path:outside
+ $ grep outside .hg/dirstate > /dev/null
+ $ grep patchdir .hg/narrowspec
+ [1]
+ $ grep patchdir .hg/dirstate
+ [1]
+
+Get rid of the modification to outside/f2.
+ $ hg update -C .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+This patch will not apply cleanly at the moment, so `hg import` will break
+ $ cat > "$TESTTMP/foo.patch" <<EOF
+ > --- patchdir/f3
+ > +++ patchdir/f3
+ > @@ -1,1 +1,1 @@
+ > -this should be "patch_this", but its not, so patch fails
+ > +this text is irrelevant
+ > EOF
+ $ PATCHINCLUDES=path:patchdir hg import -p0 -e "$TESTTMP/foo.patch" -m ignored
+ applying $TESTTMP/foo.patch
+ patching file patchdir/f3
+ Hunk #1 FAILED at 0
+ 1 out of 1 hunks FAILED -- saving rejects to file patchdir/f3.rej
+ abort: patch failed to apply
+ [255]
+ $ grep patchdir .hg/narrowspec
+ [1]
+ $ grep patchdir .hg/dirstate > /dev/null
+ [1]
+
+Let's make it apply cleanly and see that it *did* expand properly
+ $ cat > "$TESTTMP/foo.patch" <<EOF
+ > --- patchdir/f3
+ > +++ patchdir/f3
+ > @@ -1,1 +1,1 @@
+ > -patch_this
+ > +patched_this
+ > EOF
+ $ PATCHINCLUDES=path:patchdir hg import -p0 -e "$TESTTMP/foo.patch" -m message
+ applying $TESTTMP/foo.patch
+ $ cat patchdir/f3
+ patched_this
+ $ grep patchdir .hg/narrowspec
+ path:patchdir
+ $ grep patchdir .hg/dirstate > /dev/null
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-merge-tree.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,28 @@
+ $ cd $TESTDIR && python $RUNTESTDIR/run-tests.py \
+ > --extra-config-opt experimental.treemanifest=1 test-narrow-merge.t 2>&1 | \
+ > grep -v 'unexpected mercurial lib' | egrep -v '\(expected'
+
+ --- */tests/test-narrow-merge.t (glob)
+ +++ */tests/test-narrow-merge.t.err (glob)
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+
+ $ hg update -q 'desc("modify inside/f1")'
+ $ hg merge 'desc("modify outside/f1")'
+ - abort: merge affects file 'outside/f1' outside narrow, which is not yet supported
+ + abort: merge affects file 'outside/' outside narrow, which is not yet supported
+ (merging in the other direction may work)
+ [255]
+
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+
+ $ hg update -q 'desc("modify outside/f1")'
+ $ hg merge 'desc("conflicting outside/f1")'
+ - abort: conflict in file 'outside/f1' is outside narrow clone
+ + abort: conflict in file 'outside/' is outside narrow clone
+ [255]
+
+ ERROR: test-narrow-merge.t output changed
+ !
+ Failed test-narrow-merge.t: output changed
+ # Ran 1 tests, 0 skipped, 1 failed.
+ python hash seed: * (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-merge.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,94 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ mkdir inside
+ $ echo inside1 > inside/f1
+ $ echo inside2 > inside/f2
+ $ mkdir outside
+ $ echo outside1 > outside/f1
+ $ echo outside2 > outside/f2
+ $ hg ci -Aqm 'initial'
+
+ $ echo modified > inside/f1
+ $ hg ci -qm 'modify inside/f1'
+
+ $ hg update -q 0
+ $ echo modified > inside/f2
+ $ hg ci -qm 'modify inside/f2'
+
+ $ hg update -q 0
+ $ echo modified2 > inside/f1
+ $ hg ci -qm 'conflicting inside/f1'
+
+ $ hg update -q 0
+ $ echo modified > outside/f1
+ $ hg ci -qm 'modify outside/f1'
+
+ $ hg update -q 0
+ $ echo modified2 > outside/f1
+ $ hg ci -qm 'conflicting outside/f1'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 6 changesets with 5 changes to 2 files (+4 heads)
+ new changesets *:* (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+
+ $ hg update -q 0
+
+Can merge in when no files outside narrow spec are involved
+
+ $ hg update -q 'desc("modify inside/f1")'
+ $ hg merge 'desc("modify inside/f2")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ hg commit -m 'merge inside changes'
+
+Can merge conflicting changes inside narrow spec
+
+ $ hg update -q 'desc("modify inside/f1")'
+ $ hg merge 'desc("conflicting inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging inside/f1
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo modified3 > inside/f1
+ $ hg resolve -m
+ (no more unresolved files)
+ $ hg commit -m 'merge inside/f1'
+
+TODO: Can merge non-conflicting changes outside narrow spec
+
+ $ hg update -q 'desc("modify inside/f1")'
+ $ hg merge 'desc("modify outside/f1")'
+ abort: merge affects file 'outside/f1' outside narrow, which is not yet supported
+ (merging in the other direction may work)
+ [255]
+
+ $ hg update -q 'desc("modify outside/f1")'
+ $ hg merge 'desc("modify inside/f1")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ hg ci -m 'merge from inside to outside'
+
+Refuses merge of conflicting outside changes
+
+ $ hg update -q 'desc("modify outside/f1")'
+ $ hg merge 'desc("conflicting outside/f1")'
+ abort: conflict in file 'outside/f1' is outside narrow clone
+ [255]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-patch-tree.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,5 @@
+ $ cd $TESTDIR && python $RUNTESTDIR/run-tests.py \
+ > --extra-config-opt experimental.treemanifest=1 test-patch.t 2>&1 | \
+ > grep -v 'unexpected mercurial lib' | egrep -v '\(expected'
+ .
+ # Ran 1 tests, 0 skipped, 0 failed.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-patch.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,76 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+
+ $ mkdir inside
+ $ echo inside > inside/f1
+ $ mkdir outside
+ $ echo outside > outside/f1
+ $ hg ci -Aqm 'initial'
+
+ $ echo modified > inside/f1
+ $ hg ci -qm 'modify inside'
+
+ $ echo modified > outside/f1
+ $ hg ci -qm 'modify outside'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+
+Can show patch touching paths outside
+
+ $ hg log -p
+ changeset: 2:* (glob)
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify outside
+
+
+ changeset: 1:* (glob)
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify inside
+
+ diff -r * -r * inside/f1 (glob)
+ --- a/inside/f1 Thu Jan 01 00:00:00 1970 +0000
+ +++ b/inside/f1 Thu Jan 01 00:00:00 1970 +0000
+ @@ -1,1 +1,1 @@
+ -inside
+ +modified
+
+ changeset: 0:* (glob)
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: initial
+
+ diff -r 000000000000 -r * inside/f1 (glob)
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/inside/f1 Thu Jan 01 00:00:00 1970 +0000
+ @@ -0,0 +1,1 @@
+ +inside
+
+
+ $ hg status --rev 1 --rev 2
+
+Can show copies inside the narrow clone
+
+ $ hg cp inside/f1 inside/f2
+ $ hg diff --git
+ diff --git a/inside/f1 b/inside/f2
+ copy from inside/f1
+ copy to inside/f2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-patterns.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,418 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+initialize nested directories to validate complex include/exclude patterns
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ echo root > root
+ $ hg add root
+ $ hg commit -m 'add root'
+
+ $ for d in dir1 dir2 dir1/dirA dir1/dirB dir2/dirA dir2/dirB
+ > do
+ > mkdir -p $d
+ > echo $d/foo > $d/foo
+ > hg add $d/foo
+ > hg commit -m "add $d/foo"
+ > echo $d/bar > $d/bar
+ > hg add $d/bar
+ > hg commit -m "add $d/bar"
+ > done
+ $ chmod +x dir1/dirA/foo
+ $ hg commit -m "make dir1/dirA/foo executable"
+ $ hg log -G -T '{rev} {node|short} {files}\n'
+ @ 13 c87ca422d521 dir1/dirA/foo
+ |
+ o 12 951b8a83924e dir2/dirB/bar
+ |
+ o 11 01ae5a51b563 dir2/dirB/foo
+ |
+ o 10 5eababdf0ac5 dir2/dirA/bar
+ |
+ o 9 99d690663739 dir2/dirA/foo
+ |
+ o 8 8e80155d5445 dir1/dirB/bar
+ |
+ o 7 406760310428 dir1/dirB/foo
+ |
+ o 6 623466a5f475 dir1/dirA/bar
+ |
+ o 5 06ff3a5be997 dir1/dirA/foo
+ |
+ o 4 33227af02764 dir2/bar
+ |
+ o 3 5e1f9d8d7c69 dir2/foo
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da root
+
+ $ cd ..
+
+clone a narrow portion of the master, such that we can widen it later
+
+ $ hg clone --narrow ssh://user@dummy/master narrow \
+ > --include dir1 \
+ > --include dir2 \
+ > --exclude dir1/dirA \
+ > --exclude dir1/dirB \
+ > --exclude dir2/dirA \
+ > --exclude dir2/dirB
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 6 changesets with 4 changes to 4 files
+ new changesets *:* (glob)
+ updating to branch default
+ 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ cd narrow
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir1
+ path:dir2
+ [excludes]
+ path:dir1/dirA
+ path:dir1/dirB
+ path:dir2/dirA
+ path:dir2/dirB
+ $ hg manifest -r tip
+ dir1/bar
+ dir1/dirA/bar
+ dir1/dirA/foo
+ dir1/dirB/bar
+ dir1/dirB/foo
+ dir1/foo
+ dir2/bar
+ dir2/dirA/bar
+ dir2/dirA/foo
+ dir2/dirB/bar
+ dir2/dirB/foo
+ dir2/foo
+ root
+ $ find * | sort
+ dir1
+ dir1/bar
+ dir1/foo
+ dir2
+ dir2/bar
+ dir2/foo
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n'
+ @ 5 c87ca422d521... dir1/dirA/foo
+ |
+ o 4 33227af02764 dir2/bar
+ |
+ o 3 5e1f9d8d7c69 dir2/foo
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da... root
+
+
+widen the narrow checkout
+
+ $ hg tracked --removeexclude dir1/dirA
+ comparing with ssh://user@dummy/master
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 9 changesets with 6 changes to 6 files
+ new changesets *:* (glob)
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir1
+ path:dir2
+ [excludes]
+ path:dir1/dirB
+ path:dir2/dirA
+ path:dir2/dirB
+ $ find * | sort
+ dir1
+ dir1/bar
+ dir1/dirA
+ dir1/dirA/bar
+ dir1/dirA/foo
+ dir1/foo
+ dir2
+ dir2/bar
+ dir2/foo
+ $ test -x dir1/dirA/foo && echo executable
+ executable
+ $ test -x dir1/dirA/bar || echo not executable
+ not executable
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n'
+ @ 8 c87ca422d521 dir1/dirA/foo
+ |
+ o 7 951b8a83924e... dir2/dirB/bar
+ |
+ o 6 623466a5f475 dir1/dirA/bar
+ |
+ o 5 06ff3a5be997 dir1/dirA/foo
+ |
+ o 4 33227af02764 dir2/bar
+ |
+ o 3 5e1f9d8d7c69 dir2/foo
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da... root
+
+
+widen narrow spec again, but exclude a file in previously included spec
+
+ $ hg tracked --removeexclude dir2/dirB --addexclude dir1/dirA/bar
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/dir1/dirA/bar.i
+ no changes found
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 11 changesets with 7 changes to 7 files
+ new changesets *:* (glob)
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir1
+ path:dir2
+ [excludes]
+ path:dir1/dirA/bar
+ path:dir1/dirB
+ path:dir2/dirA
+ $ find * | sort
+ dir1
+ dir1/bar
+ dir1/dirA
+ dir1/dirA/foo
+ dir1/foo
+ dir2
+ dir2/bar
+ dir2/dirB
+ dir2/dirB/bar
+ dir2/dirB/foo
+ dir2/foo
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n'
+ @ 10 c87ca422d521 dir1/dirA/foo
+ |
+ o 9 951b8a83924e dir2/dirB/bar
+ |
+ o 8 01ae5a51b563 dir2/dirB/foo
+ |
+ o 7 5eababdf0ac5... dir2/dirA/bar
+ |
+ o 6 623466a5f475... dir1/dirA/bar
+ |
+ o 5 06ff3a5be997 dir1/dirA/foo
+ |
+ o 4 33227af02764 dir2/bar
+ |
+ o 3 5e1f9d8d7c69 dir2/foo
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da... root
+
+
+widen narrow spec yet again, excluding a directory in previous spec
+
+ $ hg tracked --removeexclude dir2/dirA --addexclude dir1/dirA
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/dir1/dirA/foo.i
+ no changes found
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 13 changesets with 8 changes to 8 files
+ new changesets *:* (glob)
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir1
+ path:dir2
+ [excludes]
+ path:dir1/dirA
+ path:dir1/dirA/bar
+ path:dir1/dirB
+ $ find * | sort
+ dir1
+ dir1/bar
+ dir1/foo
+ dir2
+ dir2/bar
+ dir2/dirA
+ dir2/dirA/bar
+ dir2/dirA/foo
+ dir2/dirB
+ dir2/dirB/bar
+ dir2/dirB/foo
+ dir2/foo
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n'
+ @ 12 c87ca422d521... dir1/dirA/foo
+ |
+ o 11 951b8a83924e dir2/dirB/bar
+ |
+ o 10 01ae5a51b563 dir2/dirB/foo
+ |
+ o 9 5eababdf0ac5 dir2/dirA/bar
+ |
+ o 8 99d690663739 dir2/dirA/foo
+ |
+ o 7 8e80155d5445... dir1/dirB/bar
+ |
+ o 6 623466a5f475... dir1/dirA/bar
+ |
+ o 5 06ff3a5be997... dir1/dirA/foo
+ |
+ o 4 33227af02764 dir2/bar
+ |
+ o 3 5e1f9d8d7c69 dir2/foo
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da... root
+
+
+include a directory that was previously explicitly excluded
+
+ $ hg tracked --removeexclude dir1/dirA
+ comparing with ssh://user@dummy/master
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 13 changesets with 9 changes to 9 files
+ new changesets *:* (glob)
+ $ cat .hg/narrowspec
+ [includes]
+ path:dir1
+ path:dir2
+ [excludes]
+ path:dir1/dirA/bar
+ path:dir1/dirB
+ $ find * | sort
+ dir1
+ dir1/bar
+ dir1/dirA
+ dir1/dirA/foo
+ dir1/foo
+ dir2
+ dir2/bar
+ dir2/dirA
+ dir2/dirA/bar
+ dir2/dirA/foo
+ dir2/dirB
+ dir2/dirB/bar
+ dir2/dirB/foo
+ dir2/foo
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n'
+ @ 12 c87ca422d521 dir1/dirA/foo
+ |
+ o 11 951b8a83924e dir2/dirB/bar
+ |
+ o 10 01ae5a51b563 dir2/dirB/foo
+ |
+ o 9 5eababdf0ac5 dir2/dirA/bar
+ |
+ o 8 99d690663739 dir2/dirA/foo
+ |
+ o 7 8e80155d5445... dir1/dirB/bar
+ |
+ o 6 623466a5f475... dir1/dirA/bar
+ |
+ o 5 06ff3a5be997 dir1/dirA/foo
+ |
+ o 4 33227af02764 dir2/bar
+ |
+ o 3 5e1f9d8d7c69 dir2/foo
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da... root
+
+
+ $ cd ..
+
+clone a narrow portion of the master, such that we can widen it later
+
+ $ hg clone --narrow ssh://user@dummy/master narrow2 --include dir1/dirA
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 5 changesets with 2 changes to 2 files
+ new changesets *:* (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow2
+ $ find * | sort
+ dir1
+ dir1/dirA
+ dir1/dirA/bar
+ dir1/dirA/foo
+ $ hg tracked --addinclude dir1
+ comparing with ssh://user@dummy/master
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow2/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 10 changesets with 6 changes to 6 files
+ new changesets *:* (glob)
+ $ find * | sort
+ dir1
+ dir1/bar
+ dir1/dirA
+ dir1/dirA/bar
+ dir1/dirA/foo
+ dir1/dirB
+ dir1/dirB/bar
+ dir1/dirB/foo
+ dir1/foo
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n'
+ @ 9 c87ca422d521 dir1/dirA/foo
+ |
+ o 8 951b8a83924e... dir2/dirB/bar
+ |
+ o 7 8e80155d5445 dir1/dirB/bar
+ |
+ o 6 406760310428 dir1/dirB/foo
+ |
+ o 5 623466a5f475 dir1/dirA/bar
+ |
+ o 4 06ff3a5be997 dir1/dirA/foo
+ |
+ o 3 33227af02764... dir2/bar
+ |
+ o 2 594bc4b13d4a dir1/bar
+ |
+ o 1 47f480a08324 dir1/foo
+ |
+ o 0 2a4f0c3b67da... root
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-pull.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,175 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo $x > "f$x"
+ > hg add "f$x"
+ > hg commit -m "Commit f$x"
+ > done
+ $ cd ..
+
+narrow clone a couple files, f2 and f8
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include "f2" --include "f8"
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 5 changesets with 2 changes to 2 files
+ new changesets *:* (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ ls
+ f2
+ f8
+ $ cat f2 f8
+ 2
+ 8
+
+ $ cd ..
+
+change every upstream file twice
+
+ $ cd master
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo "update#1 $x" >> "f$x"
+ > hg commit -m "Update#1 to f$x" "f$x"
+ > done
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo "update#2 $x" >> "f$x"
+ > hg commit -m "Update#2 to f$x" "f$x"
+ > done
+ $ cd ..
+
+look for incoming changes
+
+ $ cd narrow
+ $ hg incoming --limit 3
+ comparing with ssh://user@dummy/master
+ searching for changes
+ changeset: 5:ddc055582556
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: Update#1 to f1
+
+ changeset: 6:f66eb5ad621d
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: Update#1 to f2
+
+ changeset: 7:c42ecff04e99
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: Update#1 to f3
+
+
+Interrupting the pull is safe
+ $ hg --config hooks.pretxnchangegroup.bad=false pull -q
+ transaction abort!
+ rollback completed
+ abort: pretxnchangegroup.bad hook exited with status 1
+ [255]
+ $ hg id
+ 223311e70a6f tip
+
+pull new changes down to the narrow clone. Should get 8 new changesets: 4
+relevant to the narrow spec, and 4 ellipsis nodes gluing them all together.
+
+ $ hg pull
+ pulling from ssh://user@dummy/master
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 9 changesets with 4 changes to 2 files
+ new changesets *:* (glob)
+ (run 'hg update' to get a working copy)
+ $ hg log -T '{rev}: {desc}\n'
+ 13: Update#2 to f10
+ 12: Update#2 to f8
+ 11: Update#2 to f7
+ 10: Update#2 to f2
+ 9: Update#2 to f1
+ 8: Update#1 to f8
+ 7: Update#1 to f7
+ 6: Update#1 to f2
+ 5: Update#1 to f1
+ 4: Commit f10
+ 3: Commit f8
+ 2: Commit f7
+ 1: Commit f2
+ 0: Commit f1
+ $ hg update tip
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+add a change and push it
+
+ $ echo "update#3 2" >> f2
+ $ hg commit -m "Update#3 to f2" f2
+ $ hg log f2 -T '{rev}: {desc}\n'
+ 14: Update#3 to f2
+ 10: Update#2 to f2
+ 6: Update#1 to f2
+ 1: Commit f2
+ $ hg push
+ pushing to ssh://user@dummy/master
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files
+ $ cd ..
+
+ $ cd master
+ $ hg log f2 -T '{rev}: {desc}\n'
+ 30: Update#3 to f2
+ 21: Update#2 to f2
+ 11: Update#1 to f2
+ 1: Commit f2
+ $ hg log -l 3 -T '{rev}: {desc}\n'
+ 30: Update#3 to f2
+ 29: Update#2 to f10
+ 28: Update#2 to f9
+
+Can pull into repo with a single commit
+
+ $ cd ..
+ $ hg clone -q --narrow ssh://user@dummy/master narrow2 --include "f1" -r 0
+ $ cd narrow2
+ $ hg pull -q -r 1
+ transaction abort!
+ rollback completed
+ abort: pull failed on remote
+ [255]
+
+Can use 'hg share':
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > share=
+ > EOF
+
+ $ cd ..
+ $ hg share narrow2 narrow2-share
+ updating working directory
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow2-share
+ $ hg status
+
+We should also be able to unshare without breaking everything:
+ $ hg unshare
+ devel-warn: write with no wlock: "narrowspec" at: */hgext/narrow/narrowrepo.py:41 (unsharenarrowspec) (glob)
+ $ hg verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 1 files, 1 changesets, 1 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-rebase.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,93 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+
+ $ mkdir inside
+ $ echo inside1 > inside/f1
+ $ echo inside2 > inside/f2
+ $ mkdir outside
+ $ echo outside1 > outside/f1
+ $ echo outside2 > outside/f2
+ $ hg ci -Aqm 'initial'
+
+ $ echo modified > inside/f1
+ $ hg ci -qm 'modify inside/f1'
+
+ $ hg update -q 0
+ $ echo modified2 > inside/f2
+ $ hg ci -qm 'modify inside/f2'
+
+ $ hg update -q 0
+ $ echo modified > outside/f1
+ $ hg ci -qm 'modify outside/f1'
+
+ $ hg update -q 0
+ $ echo modified2 > outside/f1
+ $ hg ci -qm 'conflicting outside/f1'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 5 changesets with 4 changes to 2 files (+3 heads)
+ new changesets *:* (glob)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > rebase=
+ > EOF
+
+ $ hg update -q 0
+
+Can rebase onto commit where no files outside narrow spec are involved
+
+ $ hg update -q 0
+ $ echo modified > inside/f2
+ $ hg ci -qm 'modify inside/f2'
+ $ hg rebase -d 'desc("modify inside/f1")'
+ rebasing 5:c2f36d04e05d "modify inside/f2" (tip)
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob)
+
+Can rebase onto conflicting changes inside narrow spec
+
+ $ hg update -q 0
+ $ echo conflicting > inside/f1
+ $ hg ci -qm 'conflicting inside/f1'
+ $ hg rebase -d 'desc("modify inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ rebasing 6:cdce97fbf653 "conflicting inside/f1" (tip)
+ merging inside/f1
+ unresolved conflicts (see hg resolve, then hg rebase --continue)
+ $ echo modified3 > inside/f1
+ $ hg resolve -m 2>&1 | grep -v continue:
+ (no more unresolved files)
+ $ hg rebase --continue
+ rebasing 6:cdce97fbf653 "conflicting inside/f1" (tip)
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob)
+
+Can rebase onto non-conflicting changes outside narrow spec
+
+ $ hg update -q 0
+ $ echo modified > inside/f2
+ $ hg ci -qm 'modify inside/f2'
+ $ hg rebase -d 'desc("modify outside/f1")'
+ rebasing 7:c2f36d04e05d "modify inside/f2" (tip)
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob)
+
+Rebase interrupts on conflicting changes outside narrow spec
+
+ $ hg update -q 'desc("conflicting outside/f1")'
+ $ hg phase -f -d .
+ no phases changed
+ $ hg rebase -d 'desc("modify outside/f1")'
+ rebasing 4:707c035aadb6 "conflicting outside/f1"
+ abort: conflict in file 'outside/f1' is outside narrow clone
+ [255]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-shallow-merges.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,345 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ mkdir inside
+ $ echo 1 > inside/f
+ $ hg commit -Aqm 'initial inside'
+
+ $ mkdir outside
+ $ echo 1 > outside/f
+ $ hg commit -Aqm 'initial outside'
+
+ $ echo 2a > outside/f
+ $ hg commit -Aqm 'outside 2a'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4a > outside/f
+ $ hg commit -Aqm 'outside 4a'
+ $ hg update '.~3'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ echo 2b > outside/f
+ $ hg commit -Aqm 'outside 2b'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4b > outside/f
+ $ hg commit -Aqm 'outside 4b'
+ $ hg update '.~3'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ echo 2c > outside/f
+ $ hg commit -Aqm 'outside 2c'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4c > outside/f
+ $ hg commit -Aqm 'outside 4c'
+ $ hg update '.~3'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ echo 2d > outside/f
+ $ hg commit -Aqm 'outside 2d'
+ $ echo 3 > inside/f
+ $ hg commit -Aqm 'inside 3'
+ $ echo 4d > outside/f
+ $ hg commit -Aqm 'outside 4d'
+
+ $ hg update -r 'desc("outside 4a")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 5 > outside/f
+ $ rm outside/f.orig
+ $ hg resolve --mark outside/f
+ (no more unresolved files)
+ $ hg commit -m 'merge a/b 5'
+ $ echo 6 > outside/f
+ $ hg commit -Aqm 'outside 6'
+
+ $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 7 > outside/f
+ $ rm outside/f.orig
+ $ hg resolve --mark outside/f
+ (no more unresolved files)
+ $ hg commit -Aqm 'merge a/b/c 7'
+ $ echo 8 > outside/f
+ $ hg commit -Aqm 'outside 8'
+
+ $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 9 > outside/f
+ $ rm outside/f.orig
+ $ hg resolve --mark outside/f
+ (no more unresolved files)
+ $ hg commit -Aqm 'merge a/b/c/d 9'
+ $ echo 10 > outside/f
+ $ hg commit -Aqm 'outside 10'
+
+ $ echo 11 > inside/f
+ $ hg commit -Aqm 'inside 11'
+ $ echo 12 > outside/f
+ $ hg commit -Aqm 'outside 12'
+
+ $ hg log -G -T '{rev} {node|short} {desc}\n'
+ @ 21 8d874d57adea outside 12
+ |
+ o 20 7ef88b4dd4fa inside 11
+ |
+ o 19 2a20009de83e outside 10
+ |
+ o 18 3ac1f5779de3 merge a/b/c/d 9
+ |\
+ | o 17 38a9c2f7e546 outside 8
+ | |
+ | o 16 094aa62fc898 merge a/b/c 7
+ | |\
+ | | o 15 f29d083d32e4 outside 6
+ | | |
+ | | o 14 2dc11382541d merge a/b 5
+ | | |\
+ o | | | 13 27d07ef97221 outside 4d
+ | | | |
+ o | | | 12 465567bdfb2d inside 3
+ | | | |
+ o | | | 11 d1c61993ec83 outside 2d
+ | | | |
+ | o | | 10 56859a8e33b9 outside 4c
+ | | | |
+ | o | | 9 bb96a08b062a inside 3
+ | | | |
+ | o | | 8 b844052e7b3b outside 2c
+ |/ / /
+ | | o 7 9db2d8fcc2a6 outside 4b
+ | | |
+ | | o 6 6418167787a6 inside 3
+ | | |
+ +---o 5 77344f344d83 outside 2b
+ | |
+ | o 4 9cadde08dc9f outside 4a
+ | |
+ | o 3 019ef06f125b inside 3
+ | |
+ | o 2 75e40c075a19 outside 2a
+ |/
+ o 1 906d6c682641 initial outside
+ |
+ o 0 9f8e82b51004 initial inside
+
+
+Now narrow and shallow clone this and get a hopefully correct graph
+
+ $ cd ..
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside --depth 7
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 8 changesets with 3 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+
+To make updating the tests easier, we print the emitted nodes
+sorted. This makes it easier to identify when the same node structure
+has been emitted, just in a different order.
+
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n'
+ @ 7 8d874d57adea... outside 12
+ |
+ o 6 7ef88b4dd4fa inside 11
+ |
+ o 5 2a20009de83e... outside 10
+ |
+ o 4 3ac1f5779de3... merge a/b/c/d 9
+ |\
+ | o 3 465567bdfb2d inside 3
+ | |
+ | o 2 d1c61993ec83... outside 2d
+ |
+ o 1 bb96a08b062a inside 3
+ |
+ o 0 b844052e7b3b... outside 2c
+
+
+ $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
+ ...2a20009de83e 000000000000 3ac1f5779de3 outside 10
+ ...3ac1f5779de3 bb96a08b062a 465567bdfb2d merge a/b/c/d 9
+ ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12
+ ...b844052e7b3b 000000000000 000000000000 outside 2c
+ ...d1c61993ec83 000000000000 000000000000 outside 2d
+ 465567bdfb2d d1c61993ec83 000000000000 inside 3
+ 7ef88b4dd4fa 2a20009de83e 000000000000 inside 11
+ bb96a08b062a b844052e7b3b 000000000000 inside 3
+
+ $ cd ..
+
+Incremental test case: show a pull can pull in a conflicted merge even if elided
+
+ $ hg init pullmaster
+ $ cd pullmaster
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ mkdir inside outside
+ $ echo v1 > inside/f
+ $ echo v1 > outside/f
+ $ hg add inside/f outside/f
+ $ hg commit -m init
+
+ $ for line in a b c d
+ > do
+ > hg update -r 0
+ > echo v2$line > outside/f
+ > hg commit -m "outside 2$line"
+ > echo v2$line > inside/f
+ > hg commit -m "inside 2$line"
+ > echo v3$line > outside/f
+ > hg commit -m "outside 3$line"
+ > echo v4$line > outside/f
+ > hg commit -m "outside 4$line"
+ > done
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ created new head
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ created new head
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ created new head
+
+ $ cd ..
+ $ hg clone --narrow ssh://user@dummy/pullmaster pullshallow \
+ > --include inside --depth 3
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 12 changesets with 5 changes to 1 files (+3 heads)
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd pullshallow
+
+ $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n'
+ @ 11 0ebbd712a0c8... outside 4d
+ |
+ o 10 0d4c867aeb23 inside 2d
+ |
+ o 9 e932969c3961... outside 2d
+
+ o 8 33d530345455... outside 4c
+ |
+ o 7 0ce6481bfe07 inside 2c
+ |
+ o 6 caa65c940632... outside 2c
+
+ o 5 3df233defecc... outside 4b
+ |
+ o 4 7162cc6d11a4 inside 2b
+ |
+ o 3 f2a632f0082d... outside 2b
+
+ o 2 b8a3da16ba49... outside 4a
+ |
+ o 1 53f543eb8e45 inside 2a
+ |
+ o 0 1be3e5221c6a... outside 2a
+
+ $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
+ ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d
+ ...1be3e5221c6a 000000000000 000000000000 outside 2a
+ ...33d530345455 0ce6481bfe07 000000000000 outside 4c
+ ...3df233defecc 7162cc6d11a4 000000000000 outside 4b
+ ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a
+ ...caa65c940632 000000000000 000000000000 outside 2c
+ ...e932969c3961 000000000000 000000000000 outside 2d
+ ...f2a632f0082d 000000000000 000000000000 outside 2b
+ 0ce6481bfe07 caa65c940632 000000000000 inside 2c
+ 0d4c867aeb23 e932969c3961 000000000000 inside 2d
+ 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a
+ 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b
+
+ $ cd ../pullmaster
+ $ hg update -r 'desc("outside 4a")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging inside/f
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 3 > inside/f
+ $ echo 5 > outside/f
+ $ rm -f {in,out}side/f.orig
+ $ hg resolve --mark inside/f outside/f
+ (no more unresolved files)
+ $ hg commit -m 'merge a/b 5'
+
+ $ hg update -r 'desc("outside 4c")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
+ merging inside/f
+ merging outside/f
+ 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
+ use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+ $ echo 3 > inside/f
+ $ echo 5 > outside/f
+ $ rm -f {in,out}side/f.orig
+ $ hg resolve --mark inside/f outside/f
+ (no more unresolved files)
+ $ hg commit -m 'merge c/d 5'
+
+ $ hg update -r 'desc("merge a/b 5")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg merge -r 'desc("merge c/d 5")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ echo 6 > outside/f
+ $ hg commit -m 'outside 6'
+ $ echo 7 > outside/f
+ $ hg commit -m 'outside 7'
+ $ echo 8 > outside/f
+ $ hg commit -m 'outside 8'
+
+ $ cd ../pullshallow
+ $ hg pull --depth 3
+ pulling from ssh://user@dummy/pullmaster
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 4 changesets with 3 changes to 1 files (-3 heads)
+ new changesets *:* (glob)
+ (run 'hg update' to get a working copy)
+
+ $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort
+ ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d
+ ...1be3e5221c6a 000000000000 000000000000 outside 2a
+ ...33d530345455 0ce6481bfe07 000000000000 outside 4c
+ ...3df233defecc 7162cc6d11a4 000000000000 outside 4b
+ ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a
+ ...bf545653453e 968003d40c60 000000000000 outside 8
+ ...caa65c940632 000000000000 000000000000 outside 2c
+ ...e932969c3961 000000000000 000000000000 outside 2d
+ ...f2a632f0082d 000000000000 000000000000 outside 2b
+ 0ce6481bfe07 caa65c940632 000000000000 inside 2c
+ 0d4c867aeb23 e932969c3961 000000000000 inside 2d
+ 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a
+ 67d49c0bdbda b8a3da16ba49 3df233defecc merge a/b 5
+ 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b
+ 968003d40c60 67d49c0bdbda e867021d52c2 outside 6
+ e867021d52c2 33d530345455 0ebbd712a0c8 merge c/d 5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-shallow.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,122 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo $x > "f$x"
+ > hg add "f$x"
+ > done
+ $ hg commit -m "Add root files"
+ $ mkdir d1 d2
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo d1/$x > "d1/f$x"
+ > hg add "d1/f$x"
+ > echo d2/$x > "d2/f$x"
+ > hg add "d2/f$x"
+ > done
+ $ hg commit -m "Add d1 and d2"
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo f$x rev2 > "f$x"
+ > echo d1/f$x rev2 > "d1/f$x"
+ > echo d2/f$x rev2 > "d2/f$x"
+ > hg commit -m "Commit rev2 of f$x, d1/f$x, d2/f$x"
+ > done
+ $ cd ..
+
+narrow and shallow clone the d2 directory
+
+ $ hg clone --narrow ssh://user@dummy/master shallow --include "d2" --depth 2
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 4 changesets with 13 changes to 10 files
+ new changesets *:* (glob)
+ updating to branch default
+ 10 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd shallow
+ $ hg log -T '{rev}{if(ellipsis,"...")}: {desc}\n'
+ 3: Commit rev2 of f10, d1/f10, d2/f10
+ 2: Commit rev2 of f9, d1/f9, d2/f9
+ 1: Commit rev2 of f8, d1/f8, d2/f8
+ 0...: Commit rev2 of f7, d1/f7, d2/f7
+ $ hg update 0
+ 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat d2/f7 d2/f8
+ d2/f7 rev2
+ d2/8
+
+ $ cd ..
+
+change every upstream file once
+
+ $ cd master
+ $ for x in `$TESTDIR/seq.py 10`
+ > do
+ > echo f$x rev3 > "f$x"
+ > echo d1/f$x rev3 > "d1/f$x"
+ > echo d2/f$x rev3 > "d2/f$x"
+ > hg commit -m "Commit rev3 of f$x, d1/f$x, d2/f$x"
+ > done
+ $ cd ..
+
+pull new changes with --depth specified. There were 10 changes to the d2
+directory but the shallow pull should only fetch 3.
+
+ $ cd shallow
+ $ hg pull --depth 2
+ pulling from ssh://user@dummy/master
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 4 changesets with 10 changes to 10 files
+ new changesets *:* (glob)
+ (run 'hg update' to get a working copy)
+ $ hg log -T '{rev}{if(ellipsis,"...")}: {desc}\n'
+ 7: Commit rev3 of f10, d1/f10, d2/f10
+ 6: Commit rev3 of f9, d1/f9, d2/f9
+ 5: Commit rev3 of f8, d1/f8, d2/f8
+ 4...: Commit rev3 of f7, d1/f7, d2/f7
+ 3: Commit rev2 of f10, d1/f10, d2/f10
+ 2: Commit rev2 of f9, d1/f9, d2/f9
+ 1: Commit rev2 of f8, d1/f8, d2/f8
+ 0...: Commit rev2 of f7, d1/f7, d2/f7
+ $ hg update 4
+ merging d2/f1
+ merging d2/f2
+ merging d2/f3
+ merging d2/f4
+ merging d2/f5
+ merging d2/f6
+ merging d2/f7
+ 3 files updated, 7 files merged, 0 files removed, 0 files unresolved
+ $ cat d2/f7 d2/f8
+ d2/f7 rev3
+ d2/f8 rev2
+ $ hg update 7
+ 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat d2/f10
+ d2/f10 rev3
+
+ $ cd ..
+
+cannot clone with zero or negative depth
+
+ $ hg clone --narrow ssh://user@dummy/master bad --include "d2" --depth 0
+ requesting all changes
+ remote: abort: depth must be positive, got 0
+ abort: pull failed on remote
+ [255]
+ $ hg clone --narrow ssh://user@dummy/master bad --include "d2" --depth -1
+ requesting all changes
+ remote: abort: depth must be positive, got -1
+ abort: pull failed on remote
+ [255]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-strip-tree.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,52 @@
+ $ cd $TESTDIR && python $RUNTESTDIR/run-tests.py \
+ > --extra-config-opt experimental.treemanifest=1 test-narrow-strip.t 2>&1 | \
+ > grep -v 'unexpected mercurial lib' | egrep -v '\(expected'
+
+ --- */test-narrow-strip.t (glob)
+ +++ */test-narrow-strip.t.err (glob)
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ o 0 initial
+
+ $ hg debugdata -m 1
+ - inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc)
+ - outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc)
+ + inside\x006a8bc41df94075d501f9740587a0c0e13c170dc5t (esc)
+ + outside\x00255c2627ebdd3c7dcaa6945246f9b9f02bd45a09t (esc)
+
+ $ rm -f $TESTTMP/narrow/.hg/strip-backup/*-backup.hg
+ $ hg strip .
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+
+ Check that hash of file outside narrow spec got restored
+ $ hg debugdata -m 2
+ - inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc)
+ - outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc)
+ + inside\x006a8bc41df94075d501f9740587a0c0e13c170dc5t (esc)
+ + outside\x00255c2627ebdd3c7dcaa6945246f9b9f02bd45a09t (esc)
+
+ Also verify we can apply the bundle with 'hg pull':
+ $ hg co -r 'desc("modify inside")'
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: initial
+
+ - changeset: 1:9e48d953700d
+ + changeset: 1:3888164bccf0
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify outside again
+
+ - changeset: 2:f505d5e96aa8
+ + changeset: 2:40b66f95a209
+ tag: tip
+ - parent: 0:a99f4d53924d
+ + parent: 0:c2a5fabcca3c
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify inside
+
+ ERROR: test-narrow-strip.t output changed
+ !
+ Failed test-narrow-strip.t: output changed
+ # Ran 1 tests, 0 skipped, 1 failed.
+ python hash seed: * (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-strip.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,148 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ mkdir inside
+ $ echo inside > inside/f1
+ $ mkdir outside
+ $ echo outside > outside/f1
+ $ hg ci -Aqm 'initial'
+
+ $ echo modified > inside/f1
+ $ hg ci -qm 'modify inside'
+
+ $ hg co -q 0
+ $ echo modified > outside/f1
+ $ hg ci -qm 'modify outside'
+
+ $ echo modified again >> outside/f1
+ $ hg ci -qm 'modify outside again'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 1 files (+1 heads)
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > strip=
+ > EOF
+
+Can strip and recover changesets affecting only files within narrow spec
+
+ $ hg co -r 'desc("modify inside")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ rm -f $TESTTMP/narrow/.hg/strip-backup/*-backup.hg
+ $ hg strip .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
+ $ hg unbundle .hg/strip-backup/*-backup.hg
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ new changesets * (glob)
+ (run 'hg heads' to see heads, 'hg merge' to merge)
+
+Can strip and recover changesets affecting files outside of narrow spec
+
+ $ hg co -r 'desc("modify outside")'
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg log -G -T '{rev} {desc}\n'
+ o 2 modify inside
+ |
+ | @ 1 modify outside again
+ |/
+ o 0 initial
+
+ $ hg debugdata -m 1
+ inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc)
+ outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc)
+
+ $ rm -f $TESTTMP/narrow/.hg/strip-backup/*-backup.hg
+ $ hg strip .
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
+ $ hg unbundle .hg/strip-backup/*-backup.hg
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 0 changes to 0 files (+1 heads)
+ new changesets * (glob)
+ (run 'hg heads' to see heads, 'hg merge' to merge)
+ $ hg log -G -T '{rev} {desc}\n'
+ o 2 modify outside again
+ |
+ | o 1 modify inside
+ |/
+ @ 0 initial
+
+Check that hash of file outside narrow spec got restored
+ $ hg debugdata -m 2
+ inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc)
+ outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc)
+
+Also verify we can apply the bundle with 'hg pull':
+ $ hg co -r 'desc("modify inside")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ rm .hg/strip-backup/*-backup.hg
+ $ hg strip .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
+ $ hg pull .hg/strip-backup/*-backup.hg
+ pulling from .hg/strip-backup/*-backup.hg (glob)
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ new changesets * (glob)
+ (run 'hg heads' to see heads, 'hg merge' to merge)
+
+ $ rm .hg/strip-backup/*-backup.hg
+ $ hg strip 0
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
+ $ hg incoming .hg/strip-backup/*-backup.hg
+ comparing with .hg/strip-backup/*-backup.hg (glob)
+ changeset: 0:* (glob)
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: initial
+
+ changeset: 1:9e48d953700d
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify outside again
+
+ changeset: 2:f505d5e96aa8
+ tag: tip
+ parent: 0:a99f4d53924d
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: modify inside
+
+ $ hg pull .hg/strip-backup/*-backup.hg
+ pulling from .hg/strip-backup/*-backup.hg (glob)
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 1 files (+1 heads)
+ new changesets *:* (glob)
+ (run 'hg heads' to see heads, 'hg merge' to merge)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-tree.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,68 @@
+ $ cd $TESTDIR && python $RUNTESTDIR/run-tests.py \
+ > --extra-config-opt experimental.treemanifest=1 test-narrow-narrow.t 2>&1 | \
+ > grep -v 'unexpected mercurial lib' | egrep -v '\(expected'
+
+ --- /*/tests/test-narrow-narrow.t (glob)
+ +++ /*/tests/test-narrow-narrow.t.err (glob)
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ * (glob)
+ * (glob)
+ deleting data/d0/f.i
+ + deleting meta/d0/00manifest.i
+ $ hg log -T "{node|short}: {desc} {outsidenarrow}\n"
+ *: local change to d3 (glob)
+ *: add d10/f outsidenarrow (glob)
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ looking for local changes to affected paths
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d0/f.i
+ + deleting meta/d0/00manifest.i
+ Updates off of stripped commit if necessary
+ $ hg co -r 'desc("local change to d3")' -q
+ $ echo local change >> d6/f
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d3/f.i
+ + deleting meta/d3/00manifest.i
+ $ hg log -T '{desc}\n' -r .
+ add d10/f
+ Updates to nullid if necessary
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d3/f.i
+ + deleting meta/d3/00manifest.i
+ $ hg id
+ 000000000000
+ $ cd ..
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d0/f.i
+ + deleting meta/d0/00manifest.i
+ $ hg tracked
+ $ hg files
+ [1]
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d6/f.i
+ + deleting meta/d6/00manifest.i
+ $ hg tracked
+ I path:d0
+ I path:d3
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d0/f.i
+ + deleting meta/d0/00manifest.i
+ $ hg tracked
+ I path:d3
+ I path:d9
+
+ ERROR: test-narrow-narrow.t output changed
+ !
+ Failed test-narrow-narrow.t: output changed
+ # Ran 1 tests, 0 skipped, 1 failed.
+ python hash seed: * (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-update.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,76 @@
+
+ $ . "$TESTDIR/narrow-library.sh"
+
+create full repo
+
+ $ hg init master
+ $ cd master
+ $ echo init > init
+ $ hg ci -Aqm 'initial'
+
+ $ mkdir inside
+ $ echo inside > inside/f1
+ $ mkdir outside
+ $ echo outside > outside/f1
+ $ hg ci -Aqm 'add inside and outside'
+
+ $ echo modified > inside/f1
+ $ hg ci -qm 'modify inside'
+
+ $ echo modified > outside/f1
+ $ hg ci -qm 'modify outside'
+
+ $ cd ..
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 4 changesets with 2 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ hg debugindex -c
+ rev offset length base linkrev nodeid p1 p2
+ 0 0 64 0 0 9958b1af2add 000000000000 000000000000
+ 1 64 81 1 1 2db4ce2a3bfe 9958b1af2add 000000000000
+ 2 145 75 2 2 0980ee31a742 2db4ce2a3bfe 000000000000
+ 3 220 (76|77) 3 3 4410145019b7 0980ee31a742 000000000000 (re)
+
+ $ hg update -q 0
+
+Can update to revision with changes inside
+
+ $ hg update -q 'desc("add inside and outside")'
+ $ hg update -q 'desc("modify inside")'
+ $ find *
+ inside
+ inside/f1 (glob)
+ $ cat inside/f1
+ modified
+
+Can update to revision with changes outside
+
+ $ hg update -q 'desc("modify outside")'
+ $ find *
+ inside
+ inside/f1 (glob)
+ $ cat inside/f1
+ modified
+
+Can update with a deleted file inside
+
+ $ hg rm inside/f1
+ $ hg update -q 'desc("modify inside")'
+ $ hg update -q 'desc("modify outside")'
+ $ hg update -q 'desc("initial")'
+ $ hg update -q 'desc("modify inside")'
+
+Can update with a moved file inside
+
+ $ hg mv inside/f1 inside/f2
+ $ hg update -q 'desc("modify outside")'
+ $ hg update -q 'desc("initial")'
+ $ hg update -q 'desc("modify inside")'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-widen-tree.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,28 @@
+ $ cd $TESTDIR && python $RUNTESTDIR/run-tests.py \
+ > --extra-config-opt experimental.treemanifest=1 test-narrow-widen.t 2>&1 | \
+ > grep -v 'unexpected mercurial lib' | egrep -v '\(expected'
+
+ --- */test-narrow-widen.t (glob)
+ +++ */test-narrow-widen.t.err (glob)
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ $ hg verify
+ checking changesets
+ checking manifests
+ + checking directory manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 4 files, 8 changesets, 4 total revisions
+ @@ -\d+,\d+ \+\d+,\d+ @@ (re)
+ $ hg verify
+ checking changesets
+ checking manifests
+ + checking directory manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 5 files, 9 changesets, 5 total revisions
+
+ ERROR: test-narrow-widen.t output changed
+ !
+ Failed test-narrow-widen.t: output changed
+ # Ran 1 tests, 0 skipped, 1 failed.
+ python hash seed: * (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-widen.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,355 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+
+ $ mkdir inside
+ $ echo 'inside' > inside/f
+ $ hg add inside/f
+ $ hg commit -m 'add inside'
+
+ $ mkdir widest
+ $ echo 'widest' > widest/f
+ $ hg add widest/f
+ $ hg commit -m 'add widest'
+
+ $ mkdir outside
+ $ echo 'outside' > outside/f
+ $ hg add outside/f
+ $ hg commit -m 'add outside'
+
+ $ cd ..
+
+narrow clone the inside file
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 2 changesets with 1 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ hg tracked
+ I path:inside
+ $ ls
+ inside
+ $ cat inside/f
+ inside
+ $ cd ..
+
+add more upstream files which we will include in a wider narrow spec
+
+ $ cd master
+
+ $ mkdir wider
+ $ echo 'wider' > wider/f
+ $ hg add wider/f
+ $ echo 'widest v2' > widest/f
+ $ hg commit -m 'add wider, update widest'
+
+ $ echo 'widest v3' > widest/f
+ $ hg commit -m 'update widest v3'
+
+ $ echo 'inside v2' > inside/f
+ $ hg commit -m 'update inside'
+
+ $ mkdir outside2
+ $ echo 'outside2' > outside2/f
+ $ hg add outside2/f
+ $ hg commit -m 'add outside2'
+
+ $ echo 'widest v4' > widest/f
+ $ hg commit -m 'update widest v4'
+
+ $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
+ *: update widest v4 (glob)
+ *: add outside2 (glob)
+ *: update inside (glob)
+ *: update widest v3 (glob)
+ *: add wider, update widest (glob)
+ *: add outside (glob)
+ *: add widest (glob)
+ *: add inside (glob)
+
+ $ cd ..
+
+Widen the narrow spec to see the wider file. This should not get the newly
+added upstream revisions.
+
+ $ cd narrow
+ $ hg tracked --addinclude wider/f
+ comparing with ssh://user@dummy/master
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 2 changesets with 1 changes to 1 files
+ new changesets *:* (glob)
+ $ hg tracked
+ I path:inside
+ I path:wider/f
+
+Pull down the newly added upstream revision.
+
+ $ hg pull
+ pulling from ssh://user@dummy/master
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 4 changesets with 2 changes to 2 files
+ new changesets *:* (glob)
+ (run 'hg update' to get a working copy)
+ $ hg update -r 'desc("add wider")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat wider/f
+ wider
+
+ $ hg update -r 'desc("update inside")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat wider/f
+ wider
+ $ cat inside/f
+ inside v2
+
+ $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
+ ...*: update widest v4 (glob)
+ *: update inside (glob)
+ ...*: update widest v3 (glob)
+ *: add wider, update widest (glob)
+ ...*: add outside (glob)
+ *: add inside (glob)
+
+Check that widening with a newline fails
+
+ $ hg tracked --addinclude 'widest
+ > '
+ abort: newlines are not allowed in narrowspec paths
+ [255]
+
+widen the narrow spec to include the widest file
+
+ $ hg tracked --addinclude widest
+ comparing with ssh://user@dummy/master
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 8 changesets with 7 changes to 3 files
+ new changesets *:* (glob)
+ $ hg tracked
+ I path:inside
+ I path:wider/f
+ I path:widest
+ $ hg update 'desc("add widest")'
+ 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ cat widest/f
+ widest
+ $ hg update 'desc("add wider, update widest")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat wider/f
+ wider
+ $ cat widest/f
+ widest v2
+ $ hg update 'desc("update widest v3")'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat widest/f
+ widest v3
+ $ hg update 'desc("update widest v4")'
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat widest/f
+ widest v4
+
+ $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
+ *: update widest v4 (glob)
+ ...*: add outside2 (glob)
+ *: update inside (glob)
+ *: update widest v3 (glob)
+ *: add wider, update widest (glob)
+ ...*: add outside (glob)
+ *: add widest (glob)
+ *: add inside (glob)
+
+separate suite of tests: files from 0-10 modified in changes 0-10. This allows
+more obvious precise tests tickling particular corner cases.
+
+ $ cd ..
+ $ hg init upstream
+ $ cd upstream
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ for x in `$TESTDIR/seq.py 0 10`
+ > do
+ > mkdir d$x
+ > echo $x > d$x/f
+ > hg add d$x/f
+ > hg commit -m "add d$x/f"
+ > done
+ $ hg log -T "{node|short}: {desc}\n"
+ *: add d10/f (glob)
+ *: add d9/f (glob)
+ *: add d8/f (glob)
+ *: add d7/f (glob)
+ *: add d6/f (glob)
+ *: add d5/f (glob)
+ *: add d4/f (glob)
+ *: add d3/f (glob)
+ *: add d2/f (glob)
+ *: add d1/f (glob)
+ *: add d0/f (glob)
+
+make narrow clone with every third node.
+
+ $ cd ..
+ $ hg clone --narrow ssh://user@dummy/upstream narrow2 --include d0 --include d3 --include d6 --include d9
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 8 changesets with 4 changes to 4 files
+ new changesets *:* (glob)
+ updating to branch default
+ 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow2
+ $ hg tracked
+ I path:d0
+ I path:d3
+ I path:d6
+ I path:d9
+ $ hg verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 4 files, 8 changesets, 4 total revisions
+ $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
+ ...*: add d10/f (glob)
+ *: add d9/f (glob)
+ ...*: add d8/f (glob)
+ *: add d6/f (glob)
+ ...*: add d5/f (glob)
+ *: add d3/f (glob)
+ ...*: add d2/f (glob)
+ *: add d0/f (glob)
+ $ hg tracked --addinclude d1
+ comparing with ssh://user@dummy/upstream
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow2/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 9 changesets with 5 changes to 5 files
+ new changesets *:* (glob)
+ $ hg tracked
+ I path:d0
+ I path:d1
+ I path:d3
+ I path:d6
+ I path:d9
+ $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
+ ...*: add d10/f (glob)
+ *: add d9/f (glob)
+ ...*: add d8/f (glob)
+ *: add d6/f (glob)
+ ...*: add d5/f (glob)
+ *: add d3/f (glob)
+ ...*: add d2/f (glob)
+ *: add d1/f (glob)
+ *: add d0/f (glob)
+
+Verify shouldn't claim the repo is corrupt after a widen.
+
+ $ hg verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 5 files, 9 changesets, 5 total revisions
+
+Widening preserves parent of local commit
+
+ $ cd ..
+ $ hg clone -q --narrow ssh://user@dummy/upstream narrow3 --include d2 -r 2
+ $ cd narrow3
+ $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
+ *: add d2/f (glob)
+ ...*: add d1/f (glob)
+ $ hg pull -q -r 3
+ $ hg co -q tip
+ $ hg pull -q -r 4
+ $ echo local > d2/f
+ $ hg ci -m local
+ created new head
+ $ hg tracked -q --addinclude d0 --addinclude d9
+
+Widening preserves bookmarks
+
+ $ cd ..
+ $ hg clone -q --narrow ssh://user@dummy/upstream narrow-bookmarks --include d4
+ $ cd narrow-bookmarks
+ $ echo local > d4/f
+ $ hg ci -m local
+ $ hg bookmarks bookmark
+ $ hg bookmarks
+ * bookmark 3:* (glob)
+ $ hg -q tracked --addinclude d2
+ $ hg bookmarks
+ * bookmark 5:* (glob)
+ $ hg log -r bookmark -T '{desc}\n'
+ local
+
+Widening that fails can be recovered from
+
+ $ cd ..
+ $ hg clone -q --narrow ssh://user@dummy/upstream interrupted --include d0
+ $ cd interrupted
+ $ echo local > d0/f
+ $ hg ci -m local
+ $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+ 2: local
+ ...1: add d10/f
+ 0: add d0/f
+ $ hg bookmarks bookmark
+ $ hg --config hooks.pretxnchangegroup.bad=false tracked --addinclude d1
+ comparing with ssh://user@dummy/upstream
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/interrupted/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 2 files
+ transaction abort!
+ rollback completed
+ abort: pretxnchangegroup.bad hook exited with status 1
+ [255]
+ $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+ $ hg bookmarks
+ no bookmarks set
+ $ hg unbundle .hg/strip-backup/*-widen.hg
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 1 files
+ new changesets *:* (glob)
+ (run 'hg update' to get a working copy)
+ $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+ 2: local
+ ...1: add d10/f
+ 0: add d0/f
+ $ hg bookmarks
+ * bookmark 2:* (glob)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow.t Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,358 @@
+ $ . "$TESTDIR/narrow-library.sh"
+
+ $ hg init master
+ $ cd master
+ $ cat >> .hg/hgrc <<EOF
+ > [narrow]
+ > serveellipses=True
+ > EOF
+ $ for x in `$TESTDIR/seq.py 0 10`
+ > do
+ > mkdir d$x
+ > echo $x > d$x/f
+ > hg add d$x/f
+ > hg commit -m "add d$x/f"
+ > done
+ $ hg log -T "{node|short}: {desc}\n"
+ *: add d10/f (glob)
+ *: add d9/f (glob)
+ *: add d8/f (glob)
+ *: add d7/f (glob)
+ *: add d6/f (glob)
+ *: add d5/f (glob)
+ *: add d4/f (glob)
+ *: add d3/f (glob)
+ *: add d2/f (glob)
+ *: add d1/f (glob)
+ *: add d0/f (glob)
+ $ cd ..
+
+Error if '.' or '..' are in the directory to track.
+ $ hg clone --narrow ssh://user@dummy/master foo --include ./asdf
+ requesting all changes
+ abort: "." and ".." are not allowed in narrowspec paths
+ [255]
+ $ hg clone --narrow ssh://user@dummy/master foo --include asdf/..
+ requesting all changes
+ abort: "." and ".." are not allowed in narrowspec paths
+ [255]
+ $ hg clone --narrow ssh://user@dummy/master foo --include a/./c
+ requesting all changes
+ abort: "." and ".." are not allowed in narrowspec paths
+ [255]
+
+Names with '.' in them are OK.
+ $ hg clone --narrow ssh://user@dummy/master $RANDOM --include a/.b/c
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 0 changes to 0 files
+ new changesets * (glob)
+ updating to branch default
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Test repo with local changes
+ $ hg clone --narrow ssh://user@dummy/master narrow-local-changes --include d0 --include d3 --include d6
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 6 changesets with 3 changes to 3 files
+ new changesets *:* (glob)
+ updating to branch default
+ 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow-local-changes
+ $ cat >> $HGRCPATH << EOF
+ > [experimental]
+ > evolution=createmarkers
+ > EOF
+ $ echo local change >> d0/f
+ $ hg ci -m 'local change to d0'
+ $ hg co '.^'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ echo local change >> d3/f
+ $ hg ci -m 'local hidden change to d3'
+ created new head
+ $ hg ci --amend -m 'local change to d3'
+ $ hg tracked --removeinclude d0
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ The following changeset(s) or their ancestors have local changes not on the remote:
+ * (glob)
+ abort: local changes found
+ (use --force-delete-local-changes to ignore)
+ [255]
+Check that nothing was removed by the failed attempts
+ $ hg tracked
+ I path:d0
+ I path:d3
+ I path:d6
+ $ hg files
+ d0/f
+ d3/f
+ d6/f
+ $ find *
+ d0
+ d0/f
+ d3
+ d3/f
+ d6
+ d6/f
+ $ hg verify -q
+Force deletion of local changes
+ $ hg log -T "{node|short}: {desc} {outsidenarrow}\n"
+ *: local change to d3 (glob)
+ *: local change to d0 (glob)
+ *: add d10/f outsidenarrow (glob)
+ *: add d6/f (glob)
+ *: add d5/f outsidenarrow (glob)
+ *: add d3/f (glob)
+ *: add d2/f outsidenarrow (glob)
+ *: add d0/f (glob)
+ $ hg tracked --removeinclude d0 --force-delete-local-changes
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ The following changeset(s) or their ancestors have local changes not on the remote:
+ * (glob)
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d0/f.i
+ $ hg log -T "{node|short}: {desc} {outsidenarrow}\n"
+ *: local change to d3 (glob)
+ *: add d10/f outsidenarrow (glob)
+ *: add d6/f (glob)
+ *: add d5/f outsidenarrow (glob)
+ *: add d3/f (glob)
+ *: add d2/f outsidenarrow (glob)
+ *: add d0/f outsidenarrow (glob)
+Can restore stripped local changes after widening
+ $ hg tracked --addinclude d0 -q
+ $ hg unbundle .hg/strip-backup/*-narrow.hg -q
+ $ hg --hidden co -r 'desc("local change to d0")' -q
+ $ cat d0/f
+ 0
+ local change
+Pruned commits affecting removed paths should not prevent narrowing
+ $ hg co '.^'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg debugobsolete `hg log -T '{node}' -r 'desc("local change to d0")'`
+ obsoleted 1 changesets
+ $ hg tracked --removeinclude d0
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d0/f.i
+Updates off of stripped commit if necessary
+ $ hg co -r 'desc("local change to d3")' -q
+ $ echo local change >> d6/f
+ $ hg ci -m 'local change to d6'
+ $ hg tracked --removeinclude d3 --force-delete-local-changes
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ The following changeset(s) or their ancestors have local changes not on the remote:
+ * (glob)
+ * (glob)
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d3/f.i
+ $ hg log -T '{desc}\n' -r .
+ add d10/f
+Updates to nullid if necessary
+ $ hg tracked --addinclude d3 -q
+ $ hg co null -q
+ $ mkdir d3
+ $ echo local change > d3/f
+ $ hg add d3/f
+ $ hg ci -m 'local change to d3'
+ created new head
+ $ hg tracked --removeinclude d3 --force-delete-local-changes
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ The following changeset(s) or their ancestors have local changes not on the remote:
+ * (glob)
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
+ deleting data/d3/f.i
+ $ hg id
+ 000000000000
+ $ cd ..
+
+Can remove last include, making repo empty
+ $ hg clone --narrow ssh://user@dummy/master narrow-empty --include d0 -r 5
+ adding changesets
+ adding manifests
+ adding file changes
+ added 2 changesets with 1 changes to 1 files
+ new changesets *:* (glob)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow-empty
+ $ hg tracked --removeinclude d0
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d0/f.i
+ $ hg tracked
+ $ hg files
+ [1]
+ $ test -d d0
+ [1]
+Do some work in the empty clone
+ $ hg diff --change .
+ $ hg branch foo
+ marked working directory as branch foo
+ (branches are permanent and global, did you want a bookmark?)
+ $ hg ci -m empty
+ $ hg pull -q
+Can widen the empty clone
+ $ hg tracked --addinclude d0
+ comparing with ssh://user@dummy/master
+ searching for changes
+ no changes found
+ saved backup bundle to $TESTTMP/narrow-empty/.hg/strip-backup/*-widen.hg (glob)
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 1 changes to 1 files
+ new changesets *:* (glob)
+ $ hg tracked
+ I path:d0
+ $ hg files
+ d0/f
+ $ find *
+ d0
+ d0/f
+ $ cd ..
+
+TODO(martinvonz): test including e.g. d3/g and then removing it once
+https://bitbucket.org/Google/narrowhg/issues/6 is fixed
+
+ $ hg clone --narrow ssh://user@dummy/master narrow --include d0 --include d3 --include d6 --include d9
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 8 changesets with 4 changes to 4 files
+ new changesets *:* (glob)
+ updating to branch default
+ 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd narrow
+ $ hg tracked
+ I path:d0
+ I path:d3
+ I path:d6
+ I path:d9
+ $ hg tracked --removeinclude d6
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d6/f.i
+ $ hg tracked
+ I path:d0
+ I path:d3
+ I path:d9
+ $ hg debugrebuildfncache
+ fncache already up to date
+ $ find *
+ d0
+ d0/f
+ d3
+ d3/f
+ d9
+ d9/f
+ $ hg verify -q
+ $ hg tracked --addexclude d3/f
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d3/f.i
+ $ hg tracked
+ I path:d0
+ I path:d3
+ I path:d9
+ X path:d3/f
+ $ hg debugrebuildfncache
+ fncache already up to date
+ $ find *
+ d0
+ d0/f
+ d9
+ d9/f
+ $ hg verify -q
+ $ hg tracked --addexclude d0
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ deleting data/d0/f.i
+ $ hg tracked
+ I path:d3
+ I path:d9
+ X path:d0
+ X path:d3/f
+ $ hg debugrebuildfncache
+ fncache already up to date
+ $ find *
+ d9
+ d9/f
+
+Make a 15 of changes to d9 to test the path without --verbose
+(Note: using regexes instead of "* (glob)" because if the test fails, it
+produces more sensible diffs)
+ $ hg tracked
+ I path:d3
+ I path:d9
+ X path:d0
+ X path:d3/f
+ $ for x in `$TESTDIR/seq.py 1 15`
+ > do
+ > echo local change >> d9/f
+ > hg commit -m "change $x to d9/f"
+ > done
+ $ hg tracked --removeinclude d9
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ The following changeset(s) or their ancestors have local changes not on the remote:
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ...and 5 more, use --verbose to list all
+ abort: local changes found
+ (use --force-delete-local-changes to ignore)
+ [255]
+Now test it *with* verbose.
+ $ hg tracked --removeinclude d9 --verbose
+ comparing with ssh://user@dummy/master
+ searching for changes
+ looking for local changes to affected paths
+ The following changeset(s) or their ancestors have local changes not on the remote:
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ ^[0-9a-f]{12}$ (re)
+ abort: local changes found
+ (use --force-delete-local-changes to ignore)
+ [255]