##// END OF EJS Templates
store: pass in decoded filename to narrow matcher
Yuya Nishihara -
r40620:a694a715 default
parent child Browse files
Show More
@@ -1,609 +1,609 b''
1 1 # store.py - repository store handling for Mercurial
2 2 #
3 3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import os
13 13 import stat
14 14
15 15 from .i18n import _
16 16 from . import (
17 17 error,
18 18 node,
19 19 policy,
20 20 pycompat,
21 21 util,
22 22 vfs as vfsmod,
23 23 )
24 24
25 25 parsers = policy.importmod(r'parsers')
26 26
27 27 def _matchtrackedpath(path, matcher):
28 28 """parses a fncache entry and returns whether the entry is tracking a path
29 29 matched by matcher or not.
30 30
31 31 If matcher is None, returns True"""
32 32
33 33 if matcher is None:
34 34 return True
35 35 path = decodedir(path)
36 36 if path.startswith('data/'):
37 37 return matcher(path[len('data/'):-len('.i')])
38 38 elif path.startswith('meta/'):
39 39 return matcher.visitdir(path[len('meta/'):-len('/00manifest.i')] or '.')
40 40
41 41 # This avoids a collision between a file named foo and a dir named
42 42 # foo.i or foo.d
43 43 def _encodedir(path):
44 44 '''
45 45 >>> _encodedir(b'data/foo.i')
46 46 'data/foo.i'
47 47 >>> _encodedir(b'data/foo.i/bla.i')
48 48 'data/foo.i.hg/bla.i'
49 49 >>> _encodedir(b'data/foo.i.hg/bla.i')
50 50 'data/foo.i.hg.hg/bla.i'
51 51 >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
52 52 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
53 53 '''
54 54 return (path
55 55 .replace(".hg/", ".hg.hg/")
56 56 .replace(".i/", ".i.hg/")
57 57 .replace(".d/", ".d.hg/"))
58 58
59 59 encodedir = getattr(parsers, 'encodedir', _encodedir)
60 60
61 61 def decodedir(path):
62 62 '''
63 63 >>> decodedir(b'data/foo.i')
64 64 'data/foo.i'
65 65 >>> decodedir(b'data/foo.i.hg/bla.i')
66 66 'data/foo.i/bla.i'
67 67 >>> decodedir(b'data/foo.i.hg.hg/bla.i')
68 68 'data/foo.i.hg/bla.i'
69 69 '''
70 70 if ".hg/" not in path:
71 71 return path
72 72 return (path
73 73 .replace(".d.hg/", ".d/")
74 74 .replace(".i.hg/", ".i/")
75 75 .replace(".hg.hg/", ".hg/"))
76 76
77 77 def _reserved():
78 78 ''' characters that are problematic for filesystems
79 79
80 80 * ascii escapes (0..31)
81 81 * ascii hi (126..255)
82 82 * windows specials
83 83
84 84 these characters will be escaped by encodefunctions
85 85 '''
86 86 winreserved = [ord(x) for x in u'\\:*?"<>|']
87 87 for x in range(32):
88 88 yield x
89 89 for x in range(126, 256):
90 90 yield x
91 91 for x in winreserved:
92 92 yield x
93 93
94 94 def _buildencodefun():
95 95 '''
96 96 >>> enc, dec = _buildencodefun()
97 97
98 98 >>> enc(b'nothing/special.txt')
99 99 'nothing/special.txt'
100 100 >>> dec(b'nothing/special.txt')
101 101 'nothing/special.txt'
102 102
103 103 >>> enc(b'HELLO')
104 104 '_h_e_l_l_o'
105 105 >>> dec(b'_h_e_l_l_o')
106 106 'HELLO'
107 107
108 108 >>> enc(b'hello:world?')
109 109 'hello~3aworld~3f'
110 110 >>> dec(b'hello~3aworld~3f')
111 111 'hello:world?'
112 112
113 113 >>> enc(b'the\\x07quick\\xADshot')
114 114 'the~07quick~adshot'
115 115 >>> dec(b'the~07quick~adshot')
116 116 'the\\x07quick\\xadshot'
117 117 '''
118 118 e = '_'
119 119 xchr = pycompat.bytechr
120 120 asciistr = list(map(xchr, range(127)))
121 121 capitals = list(range(ord("A"), ord("Z") + 1))
122 122
123 123 cmap = dict((x, x) for x in asciistr)
124 124 for x in _reserved():
125 125 cmap[xchr(x)] = "~%02x" % x
126 126 for x in capitals + [ord(e)]:
127 127 cmap[xchr(x)] = e + xchr(x).lower()
128 128
129 129 dmap = {}
130 130 for k, v in cmap.iteritems():
131 131 dmap[v] = k
132 132 def decode(s):
133 133 i = 0
134 134 while i < len(s):
135 135 for l in pycompat.xrange(1, 4):
136 136 try:
137 137 yield dmap[s[i:i + l]]
138 138 i += l
139 139 break
140 140 except KeyError:
141 141 pass
142 142 else:
143 143 raise KeyError
144 144 return (lambda s: ''.join([cmap[s[c:c + 1]]
145 145 for c in pycompat.xrange(len(s))]),
146 146 lambda s: ''.join(list(decode(s))))
147 147
148 148 _encodefname, _decodefname = _buildencodefun()
149 149
150 150 def encodefilename(s):
151 151 '''
152 152 >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
153 153 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
154 154 '''
155 155 return _encodefname(encodedir(s))
156 156
157 157 def decodefilename(s):
158 158 '''
159 159 >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
160 160 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
161 161 '''
162 162 return decodedir(_decodefname(s))
163 163
164 164 def _buildlowerencodefun():
165 165 '''
166 166 >>> f = _buildlowerencodefun()
167 167 >>> f(b'nothing/special.txt')
168 168 'nothing/special.txt'
169 169 >>> f(b'HELLO')
170 170 'hello'
171 171 >>> f(b'hello:world?')
172 172 'hello~3aworld~3f'
173 173 >>> f(b'the\\x07quick\\xADshot')
174 174 'the~07quick~adshot'
175 175 '''
176 176 xchr = pycompat.bytechr
177 177 cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)])
178 178 for x in _reserved():
179 179 cmap[xchr(x)] = "~%02x" % x
180 180 for x in range(ord("A"), ord("Z") + 1):
181 181 cmap[xchr(x)] = xchr(x).lower()
182 182 def lowerencode(s):
183 183 return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
184 184 return lowerencode
185 185
186 186 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
187 187
188 188 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
189 189 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
190 190 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
191 191 def _auxencode(path, dotencode):
192 192 '''
193 193 Encodes filenames containing names reserved by Windows or which end in
194 194 period or space. Does not touch other single reserved characters c.
195 195 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
196 196 Additionally encodes space or period at the beginning, if dotencode is
197 197 True. Parameter path is assumed to be all lowercase.
198 198 A segment only needs encoding if a reserved name appears as a
199 199 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
200 200 doesn't need encoding.
201 201
202 202 >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
203 203 >>> _auxencode(s.split(b'/'), True)
204 204 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
205 205 >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
206 206 >>> _auxencode(s.split(b'/'), False)
207 207 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
208 208 >>> _auxencode([b'foo. '], True)
209 209 ['foo.~20']
210 210 >>> _auxencode([b' .foo'], True)
211 211 ['~20.foo']
212 212 '''
213 213 for i, n in enumerate(path):
214 214 if not n:
215 215 continue
216 216 if dotencode and n[0] in '. ':
217 217 n = "~%02x" % ord(n[0:1]) + n[1:]
218 218 path[i] = n
219 219 else:
220 220 l = n.find('.')
221 221 if l == -1:
222 222 l = len(n)
223 223 if ((l == 3 and n[:3] in _winres3) or
224 224 (l == 4 and n[3:4] <= '9' and n[3:4] >= '1'
225 225 and n[:3] in _winres4)):
226 226 # encode third letter ('aux' -> 'au~78')
227 227 ec = "~%02x" % ord(n[2:3])
228 228 n = n[0:2] + ec + n[3:]
229 229 path[i] = n
230 230 if n[-1] in '. ':
231 231 # encode last period or space ('foo...' -> 'foo..~2e')
232 232 path[i] = n[:-1] + "~%02x" % ord(n[-1:])
233 233 return path
234 234
235 235 _maxstorepathlen = 120
236 236 _dirprefixlen = 8
237 237 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
238 238
239 239 def _hashencode(path, dotencode):
240 240 digest = node.hex(hashlib.sha1(path).digest())
241 241 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
242 242 parts = _auxencode(le, dotencode)
243 243 basename = parts[-1]
244 244 _root, ext = os.path.splitext(basename)
245 245 sdirs = []
246 246 sdirslen = 0
247 247 for p in parts[:-1]:
248 248 d = p[:_dirprefixlen]
249 249 if d[-1] in '. ':
250 250 # Windows can't access dirs ending in period or space
251 251 d = d[:-1] + '_'
252 252 if sdirslen == 0:
253 253 t = len(d)
254 254 else:
255 255 t = sdirslen + 1 + len(d)
256 256 if t > _maxshortdirslen:
257 257 break
258 258 sdirs.append(d)
259 259 sdirslen = t
260 260 dirs = '/'.join(sdirs)
261 261 if len(dirs) > 0:
262 262 dirs += '/'
263 263 res = 'dh/' + dirs + digest + ext
264 264 spaceleft = _maxstorepathlen - len(res)
265 265 if spaceleft > 0:
266 266 filler = basename[:spaceleft]
267 267 res = 'dh/' + dirs + filler + digest + ext
268 268 return res
269 269
270 270 def _hybridencode(path, dotencode):
271 271 '''encodes path with a length limit
272 272
273 273 Encodes all paths that begin with 'data/', according to the following.
274 274
275 275 Default encoding (reversible):
276 276
277 277 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
278 278 characters are encoded as '~xx', where xx is the two digit hex code
279 279 of the character (see encodefilename).
280 280 Relevant path components consisting of Windows reserved filenames are
281 281 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
282 282
283 283 Hashed encoding (not reversible):
284 284
285 285 If the default-encoded path is longer than _maxstorepathlen, a
286 286 non-reversible hybrid hashing of the path is done instead.
287 287 This encoding uses up to _dirprefixlen characters of all directory
288 288 levels of the lowerencoded path, but not more levels than can fit into
289 289 _maxshortdirslen.
290 290 Then follows the filler followed by the sha digest of the full path.
291 291 The filler is the beginning of the basename of the lowerencoded path
292 292 (the basename is everything after the last path separator). The filler
293 293 is as long as possible, filling in characters from the basename until
294 294 the encoded path has _maxstorepathlen characters (or all chars of the
295 295 basename have been taken).
296 296 The extension (e.g. '.i' or '.d') is preserved.
297 297
298 298 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
299 299 encoding was used.
300 300 '''
301 301 path = encodedir(path)
302 302 ef = _encodefname(path).split('/')
303 303 res = '/'.join(_auxencode(ef, dotencode))
304 304 if len(res) > _maxstorepathlen:
305 305 res = _hashencode(path, dotencode)
306 306 return res
307 307
308 308 def _pathencode(path):
309 309 de = encodedir(path)
310 310 if len(path) > _maxstorepathlen:
311 311 return _hashencode(de, True)
312 312 ef = _encodefname(de).split('/')
313 313 res = '/'.join(_auxencode(ef, True))
314 314 if len(res) > _maxstorepathlen:
315 315 return _hashencode(de, True)
316 316 return res
317 317
318 318 _pathencode = getattr(parsers, 'pathencode', _pathencode)
319 319
320 320 def _plainhybridencode(f):
321 321 return _hybridencode(f, False)
322 322
323 323 def _calcmode(vfs):
324 324 try:
325 325 # files in .hg/ will be created using this mode
326 326 mode = vfs.stat().st_mode
327 327 # avoid some useless chmods
328 328 if (0o777 & ~util.umask) == (0o777 & mode):
329 329 mode = None
330 330 except OSError:
331 331 mode = None
332 332 return mode
333 333
334 334 _data = ('narrowspec data meta 00manifest.d 00manifest.i'
335 335 ' 00changelog.d 00changelog.i phaseroots obsstore')
336 336
337 337 def isrevlog(f, kind, st):
338 338 return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
339 339
340 340 class basicstore(object):
341 341 '''base class for local repository stores'''
342 342 def __init__(self, path, vfstype):
343 343 vfs = vfstype(path)
344 344 self.path = vfs.base
345 345 self.createmode = _calcmode(vfs)
346 346 vfs.createmode = self.createmode
347 347 self.rawvfs = vfs
348 348 self.vfs = vfsmod.filtervfs(vfs, encodedir)
349 349 self.opener = self.vfs
350 350
351 351 def join(self, f):
352 352 return self.path + '/' + encodedir(f)
353 353
354 354 def _walk(self, relpath, recurse, filefilter=isrevlog):
355 355 '''yields (unencoded, encoded, size)'''
356 356 path = self.path
357 357 if relpath:
358 358 path += '/' + relpath
359 359 striplen = len(self.path) + 1
360 360 l = []
361 361 if self.rawvfs.isdir(path):
362 362 visit = [path]
363 363 readdir = self.rawvfs.readdir
364 364 while visit:
365 365 p = visit.pop()
366 366 for f, kind, st in readdir(p, stat=True):
367 367 fp = p + '/' + f
368 368 if filefilter(f, kind, st):
369 369 n = util.pconvert(fp[striplen:])
370 370 l.append((decodedir(n), n, st.st_size))
371 371 elif kind == stat.S_IFDIR and recurse:
372 372 visit.append(fp)
373 373 l.sort()
374 374 return l
375 375
376 376 def datafiles(self, matcher=None):
377 377 return self._walk('data', True) + self._walk('meta', True)
378 378
379 379 def topfiles(self):
380 380 # yield manifest before changelog
381 381 return reversed(self._walk('', False))
382 382
383 383 def walk(self, matcher=None):
384 384 '''yields (unencoded, encoded, size)
385 385
386 386 if a matcher is passed, storage files of only those tracked paths
387 387 are passed with matches the matcher
388 388 '''
389 389 # yield data files first
390 390 for x in self.datafiles(matcher):
391 391 yield x
392 392 for x in self.topfiles():
393 393 yield x
394 394
395 395 def copylist(self):
396 396 return ['requires'] + _data.split()
397 397
398 398 def write(self, tr):
399 399 pass
400 400
401 401 def invalidatecaches(self):
402 402 pass
403 403
404 404 def markremoved(self, fn):
405 405 pass
406 406
407 407 def __contains__(self, path):
408 408 '''Checks if the store contains path'''
409 409 path = "/".join(("data", path))
410 410 # file?
411 411 if self.vfs.exists(path + ".i"):
412 412 return True
413 413 # dir?
414 414 if not path.endswith("/"):
415 415 path = path + "/"
416 416 return self.vfs.exists(path)
417 417
418 418 class encodedstore(basicstore):
419 419 def __init__(self, path, vfstype):
420 420 vfs = vfstype(path + '/store')
421 421 self.path = vfs.base
422 422 self.createmode = _calcmode(vfs)
423 423 vfs.createmode = self.createmode
424 424 self.rawvfs = vfs
425 425 self.vfs = vfsmod.filtervfs(vfs, encodefilename)
426 426 self.opener = self.vfs
427 427
428 428 def datafiles(self, matcher=None):
429 429 for a, b, size in super(encodedstore, self).datafiles():
430 if not _matchtrackedpath(a, matcher):
431 continue
432 430 try:
433 431 a = decodefilename(a)
434 432 except KeyError:
435 433 a = None
434 if a is not None and not _matchtrackedpath(a, matcher):
435 continue
436 436 yield a, b, size
437 437
438 438 def join(self, f):
439 439 return self.path + '/' + encodefilename(f)
440 440
441 441 def copylist(self):
442 442 return (['requires', '00changelog.i'] +
443 443 ['store/' + f for f in _data.split()])
444 444
445 445 class fncache(object):
446 446 # the filename used to be partially encoded
447 447 # hence the encodedir/decodedir dance
448 448 def __init__(self, vfs):
449 449 self.vfs = vfs
450 450 self.entries = None
451 451 self._dirty = False
452 452
453 453 def _load(self):
454 454 '''fill the entries from the fncache file'''
455 455 self._dirty = False
456 456 try:
457 457 fp = self.vfs('fncache', mode='rb')
458 458 except IOError:
459 459 # skip nonexistent file
460 460 self.entries = set()
461 461 return
462 462 self.entries = set(decodedir(fp.read()).splitlines())
463 463 if '' in self.entries:
464 464 fp.seek(0)
465 465 for n, line in enumerate(util.iterfile(fp)):
466 466 if not line.rstrip('\n'):
467 467 t = _('invalid entry in fncache, line %d') % (n + 1)
468 468 raise error.Abort(t)
469 469 fp.close()
470 470
471 471 def write(self, tr):
472 472 if self._dirty:
473 473 assert self.entries is not None
474 474 tr.addbackup('fncache')
475 475 fp = self.vfs('fncache', mode='wb', atomictemp=True)
476 476 if self.entries:
477 477 fp.write(encodedir('\n'.join(self.entries) + '\n'))
478 478 fp.close()
479 479 self._dirty = False
480 480
481 481 def add(self, fn):
482 482 if self.entries is None:
483 483 self._load()
484 484 if fn not in self.entries:
485 485 self._dirty = True
486 486 self.entries.add(fn)
487 487
488 488 def remove(self, fn):
489 489 if self.entries is None:
490 490 self._load()
491 491 try:
492 492 self.entries.remove(fn)
493 493 self._dirty = True
494 494 except KeyError:
495 495 pass
496 496
497 497 def __contains__(self, fn):
498 498 if self.entries is None:
499 499 self._load()
500 500 return fn in self.entries
501 501
502 502 def __iter__(self):
503 503 if self.entries is None:
504 504 self._load()
505 505 return iter(self.entries)
506 506
507 507 class _fncachevfs(vfsmod.abstractvfs, vfsmod.proxyvfs):
508 508 def __init__(self, vfs, fnc, encode):
509 509 vfsmod.proxyvfs.__init__(self, vfs)
510 510 self.fncache = fnc
511 511 self.encode = encode
512 512
513 513 def __call__(self, path, mode='r', *args, **kw):
514 514 encoded = self.encode(path)
515 515 if mode not in ('r', 'rb') and (path.startswith('data/') or
516 516 path.startswith('meta/')):
517 517 # do not trigger a fncache load when adding a file that already is
518 518 # known to exist.
519 519 notload = self.fncache.entries is None and self.vfs.exists(encoded)
520 520 if notload and 'a' in mode and not self.vfs.stat(encoded).st_size:
521 521 # when appending to an existing file, if the file has size zero,
522 522 # it should be considered as missing. Such zero-size files are
523 523 # the result of truncation when a transaction is aborted.
524 524 notload = False
525 525 if not notload:
526 526 self.fncache.add(path)
527 527 return self.vfs(encoded, mode, *args, **kw)
528 528
529 529 def join(self, path):
530 530 if path:
531 531 return self.vfs.join(self.encode(path))
532 532 else:
533 533 return self.vfs.join(path)
534 534
535 535 class fncachestore(basicstore):
536 536 def __init__(self, path, vfstype, dotencode):
537 537 if dotencode:
538 538 encode = _pathencode
539 539 else:
540 540 encode = _plainhybridencode
541 541 self.encode = encode
542 542 vfs = vfstype(path + '/store')
543 543 self.path = vfs.base
544 544 self.pathsep = self.path + '/'
545 545 self.createmode = _calcmode(vfs)
546 546 vfs.createmode = self.createmode
547 547 self.rawvfs = vfs
548 548 fnc = fncache(vfs)
549 549 self.fncache = fnc
550 550 self.vfs = _fncachevfs(vfs, fnc, encode)
551 551 self.opener = self.vfs
552 552
553 553 def join(self, f):
554 554 return self.pathsep + self.encode(f)
555 555
556 556 def getsize(self, path):
557 557 return self.rawvfs.stat(path).st_size
558 558
559 559 def datafiles(self, matcher=None):
560 560 for f in sorted(self.fncache):
561 561 if not _matchtrackedpath(f, matcher):
562 562 continue
563 563 ef = self.encode(f)
564 564 try:
565 565 yield f, ef, self.getsize(ef)
566 566 except OSError as err:
567 567 if err.errno != errno.ENOENT:
568 568 raise
569 569
570 570 def copylist(self):
571 571 d = ('narrowspec data meta dh fncache phaseroots obsstore'
572 572 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
573 573 return (['requires', '00changelog.i'] +
574 574 ['store/' + f for f in d.split()])
575 575
576 576 def write(self, tr):
577 577 self.fncache.write(tr)
578 578
579 579 def invalidatecaches(self):
580 580 self.fncache.entries = None
581 581
582 582 def markremoved(self, fn):
583 583 self.fncache.remove(fn)
584 584
585 585 def _exists(self, f):
586 586 ef = self.encode(f)
587 587 try:
588 588 self.getsize(ef)
589 589 return True
590 590 except OSError as err:
591 591 if err.errno != errno.ENOENT:
592 592 raise
593 593 # nonexistent entry
594 594 return False
595 595
596 596 def __contains__(self, path):
597 597 '''Checks if the store contains path'''
598 598 path = "/".join(("data", path))
599 599 # check for files (exact match)
600 600 e = path + '.i'
601 601 if e in self.fncache and self._exists(e):
602 602 return True
603 603 # now check for directories (prefix match)
604 604 if not path.endswith('/'):
605 605 path += '/'
606 606 for e in self.fncache:
607 607 if e.startswith(path) and self._exists(e):
608 608 return True
609 609 return False
@@ -1,110 +1,93 b''
1 1 #testcases tree flat-fncache flat-nofncache
2 2
3 3 Tests narrow stream clones
4 4
5 5 $ . "$TESTDIR/narrow-library.sh"
6 6
7 7 #if tree
8 8 $ cat << EOF >> $HGRCPATH
9 9 > [experimental]
10 10 > treemanifest = 1
11 11 > EOF
12 12 #endif
13 13
14 14 #if flat-nofncache
15 15 $ cat << EOF >> $HGRCPATH
16 16 > [format]
17 17 > usefncache = 0
18 18 > EOF
19 19 #endif
20 20
21 21 Server setup
22 22
23 23 $ hg init master
24 24 $ cd master
25 25 $ mkdir dir
26 26 $ mkdir dir/src
27 27 $ cd dir/src
28 28 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "F$x"; hg add "F$x"; hg commit -m "Commit src $x"; done
29 29
30 30 $ cd ..
31 31 $ mkdir tests
32 32 $ cd tests
33 33 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "F$x"; hg add "F$x"; hg commit -m "Commit src $x"; done
34 34 $ cd ../../..
35 35
36 36 Trying to stream clone when the server does not support it
37 37
38 38 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/F10" --stream
39 39 streaming all changes
40 40 remote: abort: server does not support narrow stream clones
41 41 abort: pull failed on remote
42 42 [255]
43 43
44 44 Enable stream clone on the server
45 45
46 46 $ echo "[experimental]" >> master/.hg/hgrc
47 47 $ echo "server.stream-narrow-clones=True" >> master/.hg/hgrc
48 48
49 49 Cloning a specific file when stream clone is supported
50 50
51 51 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/F10" --stream
52 52 streaming all changes
53 53 * files to transfer, * KB of data (glob)
54 54 transferred * KB in * seconds (* */sec) (glob)
55 55
56 56 $ cd narrow
57 57 $ ls
58 58 $ hg tracked
59 59 I path:dir/src/F10
60 60
61 61 Making sure we have the correct set of requirements
62 62
63 63 $ cat .hg/requires
64 64 dotencode (tree flat-fncache !)
65 65 fncache (tree flat-fncache !)
66 66 generaldelta
67 67 narrowhg-experimental
68 68 revlogv1
69 69 store
70 70 treemanifest (tree !)
71 71
72 72 Making sure store has the required files
73 73
74 74 $ ls .hg/store/
75 75 00changelog.i
76 76 00manifest.i
77 77 data (tree flat-fncache !)
78 78 fncache (tree flat-fncache !)
79 79 meta (tree !)
80 80 narrowspec
81 81 undo
82 82 undo.backupfiles
83 83 undo.phaseroots
84 84
85 85 Checking that repository has all the required data and not broken
86 86
87 #if flat-nofncache
88 $ hg verify
89 checking changesets
90 checking manifests
91 crosschecking files in changesets and manifests
92 checking files
93 warning: revlog 'data/dir/src/F10.i' not in fncache!
94 9: empty or missing dir/src/F10
95 dir/src/F10@9: manifest refers to unknown revision 419ee72d626b
96 checked 40 changesets with 0 changes to 1 files
97 1 warnings encountered!
98 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
99 2 integrity errors encountered!
100 (first damaged changeset appears to be 9)
101 [1]
102 #else
103 87 $ hg verify
104 88 checking changesets
105 89 checking manifests
106 90 checking directory manifests (tree !)
107 91 crosschecking files in changesets and manifests
108 92 checking files
109 93 checked 40 changesets with 1 changes to 1 files
110 #endif
General Comments 0
You need to be logged in to leave comments. Login now