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