##// END OF EJS Templates
store: invoke "os.stat()" for "createmode" initialization via vfs...
FUJIWARA Katsunori -
r17726:7cb7e17c default
parent child Browse files
Show More
@@ -1,939 +1,942 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 i18n import _
9 9 import util, error, osutil, revset, similar, encoding, phases
10 10 import match as matchmod
11 11 import os, errno, re, stat, sys, glob
12 12
13 13 def nochangesfound(ui, repo, excluded=None):
14 14 '''Report no changes for push/pull, excluded is None or a list of
15 15 nodes excluded from the push/pull.
16 16 '''
17 17 secretlist = []
18 18 if excluded:
19 19 for n in excluded:
20 20 ctx = repo[n]
21 21 if ctx.phase() >= phases.secret and not ctx.extinct():
22 22 secretlist.append(n)
23 23
24 24 if secretlist:
25 25 ui.status(_("no changes found (ignored %d secret changesets)\n")
26 26 % len(secretlist))
27 27 else:
28 28 ui.status(_("no changes found\n"))
29 29
30 30 def checkfilename(f):
31 31 '''Check that the filename f is an acceptable filename for a tracked file'''
32 32 if '\r' in f or '\n' in f:
33 33 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
34 34
35 35 def checkportable(ui, f):
36 36 '''Check if filename f is portable and warn or abort depending on config'''
37 37 checkfilename(f)
38 38 abort, warn = checkportabilityalert(ui)
39 39 if abort or warn:
40 40 msg = util.checkwinfilename(f)
41 41 if msg:
42 42 msg = "%s: %r" % (msg, f)
43 43 if abort:
44 44 raise util.Abort(msg)
45 45 ui.warn(_("warning: %s\n") % msg)
46 46
47 47 def checkportabilityalert(ui):
48 48 '''check if the user's config requests nothing, a warning, or abort for
49 49 non-portable filenames'''
50 50 val = ui.config('ui', 'portablefilenames', 'warn')
51 51 lval = val.lower()
52 52 bval = util.parsebool(val)
53 53 abort = os.name == 'nt' or lval == 'abort'
54 54 warn = bval or lval == 'warn'
55 55 if bval is None and not (warn or abort or lval == 'ignore'):
56 56 raise error.ConfigError(
57 57 _("ui.portablefilenames value is invalid ('%s')") % val)
58 58 return abort, warn
59 59
60 60 class casecollisionauditor(object):
61 61 def __init__(self, ui, abort, dirstate):
62 62 self._ui = ui
63 63 self._abort = abort
64 64 allfiles = '\0'.join(dirstate._map)
65 65 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
66 66 self._dirstate = dirstate
67 67 # The purpose of _newfiles is so that we don't complain about
68 68 # case collisions if someone were to call this object with the
69 69 # same filename twice.
70 70 self._newfiles = set()
71 71
72 72 def __call__(self, f):
73 73 fl = encoding.lower(f)
74 74 if (fl in self._loweredfiles and f not in self._dirstate and
75 75 f not in self._newfiles):
76 76 msg = _('possible case-folding collision for %s') % f
77 77 if self._abort:
78 78 raise util.Abort(msg)
79 79 self._ui.warn(_("warning: %s\n") % msg)
80 80 self._loweredfiles.add(fl)
81 81 self._newfiles.add(f)
82 82
83 83 class pathauditor(object):
84 84 '''ensure that a filesystem path contains no banned components.
85 85 the following properties of a path are checked:
86 86
87 87 - ends with a directory separator
88 88 - under top-level .hg
89 89 - starts at the root of a windows drive
90 90 - contains ".."
91 91 - traverses a symlink (e.g. a/symlink_here/b)
92 92 - inside a nested repository (a callback can be used to approve
93 93 some nested repositories, e.g., subrepositories)
94 94 '''
95 95
96 96 def __init__(self, root, callback=None):
97 97 self.audited = set()
98 98 self.auditeddir = set()
99 99 self.root = root
100 100 self.callback = callback
101 101 if os.path.lexists(root) and not util.checkcase(root):
102 102 self.normcase = util.normcase
103 103 else:
104 104 self.normcase = lambda x: x
105 105
106 106 def __call__(self, path):
107 107 '''Check the relative path.
108 108 path may contain a pattern (e.g. foodir/**.txt)'''
109 109
110 110 path = util.localpath(path)
111 111 normpath = self.normcase(path)
112 112 if normpath in self.audited:
113 113 return
114 114 # AIX ignores "/" at end of path, others raise EISDIR.
115 115 if util.endswithsep(path):
116 116 raise util.Abort(_("path ends in directory separator: %s") % path)
117 117 parts = util.splitpath(path)
118 118 if (os.path.splitdrive(path)[0]
119 119 or parts[0].lower() in ('.hg', '.hg.', '')
120 120 or os.pardir in parts):
121 121 raise util.Abort(_("path contains illegal component: %s") % path)
122 122 if '.hg' in path.lower():
123 123 lparts = [p.lower() for p in parts]
124 124 for p in '.hg', '.hg.':
125 125 if p in lparts[1:]:
126 126 pos = lparts.index(p)
127 127 base = os.path.join(*parts[:pos])
128 128 raise util.Abort(_("path '%s' is inside nested repo %r")
129 129 % (path, base))
130 130
131 131 normparts = util.splitpath(normpath)
132 132 assert len(parts) == len(normparts)
133 133
134 134 parts.pop()
135 135 normparts.pop()
136 136 prefixes = []
137 137 while parts:
138 138 prefix = os.sep.join(parts)
139 139 normprefix = os.sep.join(normparts)
140 140 if normprefix in self.auditeddir:
141 141 break
142 142 curpath = os.path.join(self.root, prefix)
143 143 try:
144 144 st = os.lstat(curpath)
145 145 except OSError, err:
146 146 # EINVAL can be raised as invalid path syntax under win32.
147 147 # They must be ignored for patterns can be checked too.
148 148 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
149 149 raise
150 150 else:
151 151 if stat.S_ISLNK(st.st_mode):
152 152 raise util.Abort(
153 153 _('path %r traverses symbolic link %r')
154 154 % (path, prefix))
155 155 elif (stat.S_ISDIR(st.st_mode) and
156 156 os.path.isdir(os.path.join(curpath, '.hg'))):
157 157 if not self.callback or not self.callback(curpath):
158 158 raise util.Abort(_("path '%s' is inside nested "
159 159 "repo %r")
160 160 % (path, prefix))
161 161 prefixes.append(normprefix)
162 162 parts.pop()
163 163 normparts.pop()
164 164
165 165 self.audited.add(normpath)
166 166 # only add prefixes to the cache after checking everything: we don't
167 167 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
168 168 self.auditeddir.update(prefixes)
169 169
170 170 class abstractvfs(object):
171 171 """Abstract base class; cannot be instantiated"""
172 172
173 173 def __init__(self, *args, **kwargs):
174 174 '''Prevent instantiation; don't call this from subclasses.'''
175 175 raise NotImplementedError('attempted instantiating ' + str(type(self)))
176 176
177 177 def tryread(self, path):
178 178 '''gracefully return an empty string for missing files'''
179 179 try:
180 180 return self.read(path)
181 181 except IOError, inst:
182 182 if inst.errno != errno.ENOENT:
183 183 raise
184 184 return ""
185 185
186 186 def read(self, path):
187 187 fp = self(path, 'rb')
188 188 try:
189 189 return fp.read()
190 190 finally:
191 191 fp.close()
192 192
193 193 def write(self, path, data):
194 194 fp = self(path, 'wb')
195 195 try:
196 196 return fp.write(data)
197 197 finally:
198 198 fp.close()
199 199
200 200 def append(self, path, data):
201 201 fp = self(path, 'ab')
202 202 try:
203 203 return fp.write(data)
204 204 finally:
205 205 fp.close()
206 206
207 207 def exists(self, path=None):
208 208 return os.path.exists(self.join(path))
209 209
210 210 def isdir(self, path=None):
211 211 return os.path.isdir(self.join(path))
212 212
213 213 def makedir(self, path=None, notindexed=True):
214 214 return util.makedir(self.join(path), notindexed)
215 215
216 216 def makedirs(self, path=None, mode=None):
217 217 return util.makedirs(self.join(path), mode)
218 218
219 219 def mkdir(self, path=None):
220 220 return os.mkdir(self.join(path))
221 221
222 def stat(self, path=None):
223 return os.stat(self.join(path))
224
222 225 class vfs(abstractvfs):
223 226 '''Operate files relative to a base directory
224 227
225 228 This class is used to hide the details of COW semantics and
226 229 remote file access from higher level code.
227 230 '''
228 231 def __init__(self, base, audit=True, expand=False):
229 232 if expand:
230 233 base = os.path.realpath(util.expandpath(base))
231 234 self.base = base
232 235 self._setmustaudit(audit)
233 236 self.createmode = None
234 237 self._trustnlink = None
235 238
236 239 def _getmustaudit(self):
237 240 return self._audit
238 241
239 242 def _setmustaudit(self, onoff):
240 243 self._audit = onoff
241 244 if onoff:
242 245 self.auditor = pathauditor(self.base)
243 246 else:
244 247 self.auditor = util.always
245 248
246 249 mustaudit = property(_getmustaudit, _setmustaudit)
247 250
248 251 @util.propertycache
249 252 def _cansymlink(self):
250 253 return util.checklink(self.base)
251 254
252 255 def _fixfilemode(self, name):
253 256 if self.createmode is None:
254 257 return
255 258 os.chmod(name, self.createmode & 0666)
256 259
257 260 def __call__(self, path, mode="r", text=False, atomictemp=False):
258 261 if self._audit:
259 262 r = util.checkosfilename(path)
260 263 if r:
261 264 raise util.Abort("%s: %r" % (r, path))
262 265 self.auditor(path)
263 266 f = self.join(path)
264 267
265 268 if not text and "b" not in mode:
266 269 mode += "b" # for that other OS
267 270
268 271 nlink = -1
269 272 dirname, basename = util.split(f)
270 273 # If basename is empty, then the path is malformed because it points
271 274 # to a directory. Let the posixfile() call below raise IOError.
272 275 if basename and mode not in ('r', 'rb'):
273 276 if atomictemp:
274 277 if not os.path.isdir(dirname):
275 278 util.makedirs(dirname, self.createmode)
276 279 return util.atomictempfile(f, mode, self.createmode)
277 280 try:
278 281 if 'w' in mode:
279 282 util.unlink(f)
280 283 nlink = 0
281 284 else:
282 285 # nlinks() may behave differently for files on Windows
283 286 # shares if the file is open.
284 287 fd = util.posixfile(f)
285 288 nlink = util.nlinks(f)
286 289 if nlink < 1:
287 290 nlink = 2 # force mktempcopy (issue1922)
288 291 fd.close()
289 292 except (OSError, IOError), e:
290 293 if e.errno != errno.ENOENT:
291 294 raise
292 295 nlink = 0
293 296 if not os.path.isdir(dirname):
294 297 util.makedirs(dirname, self.createmode)
295 298 if nlink > 0:
296 299 if self._trustnlink is None:
297 300 self._trustnlink = nlink > 1 or util.checknlink(f)
298 301 if nlink > 1 or not self._trustnlink:
299 302 util.rename(util.mktempcopy(f), f)
300 303 fp = util.posixfile(f, mode)
301 304 if nlink == 0:
302 305 self._fixfilemode(f)
303 306 return fp
304 307
305 308 def symlink(self, src, dst):
306 309 self.auditor(dst)
307 310 linkname = self.join(dst)
308 311 try:
309 312 os.unlink(linkname)
310 313 except OSError:
311 314 pass
312 315
313 316 dirname = os.path.dirname(linkname)
314 317 if not os.path.exists(dirname):
315 318 util.makedirs(dirname, self.createmode)
316 319
317 320 if self._cansymlink:
318 321 try:
319 322 os.symlink(src, linkname)
320 323 except OSError, err:
321 324 raise OSError(err.errno, _('could not symlink to %r: %s') %
322 325 (src, err.strerror), linkname)
323 326 else:
324 327 f = self(dst, "w")
325 328 f.write(src)
326 329 f.close()
327 330 self._fixfilemode(dst)
328 331
329 332 def audit(self, path):
330 333 self.auditor(path)
331 334
332 335 def join(self, path):
333 336 if path:
334 337 return os.path.join(self.base, path)
335 338 else:
336 339 return self.base
337 340
338 341 opener = vfs
339 342
340 343 class filtervfs(abstractvfs):
341 344 '''Wrapper vfs for filtering filenames with a function.'''
342 345
343 346 def __init__(self, opener, filter):
344 347 self._filter = filter
345 348 self._orig = opener
346 349
347 350 def __call__(self, path, *args, **kwargs):
348 351 return self._orig(self._filter(path), *args, **kwargs)
349 352
350 353 def join(self, path):
351 354 if path:
352 355 return self._orig.join(self._filter(path))
353 356 else:
354 357 return self._orig.join(path)
355 358
356 359 filteropener = filtervfs
357 360
358 361 def canonpath(root, cwd, myname, auditor=None):
359 362 '''return the canonical path of myname, given cwd and root'''
360 363 if util.endswithsep(root):
361 364 rootsep = root
362 365 else:
363 366 rootsep = root + os.sep
364 367 name = myname
365 368 if not os.path.isabs(name):
366 369 name = os.path.join(root, cwd, name)
367 370 name = os.path.normpath(name)
368 371 if auditor is None:
369 372 auditor = pathauditor(root)
370 373 if name != rootsep and name.startswith(rootsep):
371 374 name = name[len(rootsep):]
372 375 auditor(name)
373 376 return util.pconvert(name)
374 377 elif name == root:
375 378 return ''
376 379 else:
377 380 # Determine whether `name' is in the hierarchy at or beneath `root',
378 381 # by iterating name=dirname(name) until that causes no change (can't
379 382 # check name == '/', because that doesn't work on windows). The list
380 383 # `rel' holds the reversed list of components making up the relative
381 384 # file name we want.
382 385 rel = []
383 386 while True:
384 387 try:
385 388 s = util.samefile(name, root)
386 389 except OSError:
387 390 s = False
388 391 if s:
389 392 if not rel:
390 393 # name was actually the same as root (maybe a symlink)
391 394 return ''
392 395 rel.reverse()
393 396 name = os.path.join(*rel)
394 397 auditor(name)
395 398 return util.pconvert(name)
396 399 dirname, basename = util.split(name)
397 400 rel.append(basename)
398 401 if dirname == name:
399 402 break
400 403 name = dirname
401 404
402 405 raise util.Abort('%s not under root' % myname)
403 406
404 407 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
405 408 '''yield every hg repository under path, always recursively.
406 409 The recurse flag will only control recursion into repo working dirs'''
407 410 def errhandler(err):
408 411 if err.filename == path:
409 412 raise err
410 413 samestat = getattr(os.path, 'samestat', None)
411 414 if followsym and samestat is not None:
412 415 def adddir(dirlst, dirname):
413 416 match = False
414 417 dirstat = os.stat(dirname)
415 418 for lstdirstat in dirlst:
416 419 if samestat(dirstat, lstdirstat):
417 420 match = True
418 421 break
419 422 if not match:
420 423 dirlst.append(dirstat)
421 424 return not match
422 425 else:
423 426 followsym = False
424 427
425 428 if (seen_dirs is None) and followsym:
426 429 seen_dirs = []
427 430 adddir(seen_dirs, path)
428 431 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
429 432 dirs.sort()
430 433 if '.hg' in dirs:
431 434 yield root # found a repository
432 435 qroot = os.path.join(root, '.hg', 'patches')
433 436 if os.path.isdir(os.path.join(qroot, '.hg')):
434 437 yield qroot # we have a patch queue repo here
435 438 if recurse:
436 439 # avoid recursing inside the .hg directory
437 440 dirs.remove('.hg')
438 441 else:
439 442 dirs[:] = [] # don't descend further
440 443 elif followsym:
441 444 newdirs = []
442 445 for d in dirs:
443 446 fname = os.path.join(root, d)
444 447 if adddir(seen_dirs, fname):
445 448 if os.path.islink(fname):
446 449 for hgname in walkrepos(fname, True, seen_dirs):
447 450 yield hgname
448 451 else:
449 452 newdirs.append(d)
450 453 dirs[:] = newdirs
451 454
452 455 def osrcpath():
453 456 '''return default os-specific hgrc search path'''
454 457 path = systemrcpath()
455 458 path.extend(userrcpath())
456 459 path = [os.path.normpath(f) for f in path]
457 460 return path
458 461
459 462 _rcpath = None
460 463
461 464 def rcpath():
462 465 '''return hgrc search path. if env var HGRCPATH is set, use it.
463 466 for each item in path, if directory, use files ending in .rc,
464 467 else use item.
465 468 make HGRCPATH empty to only look in .hg/hgrc of current repo.
466 469 if no HGRCPATH, use default os-specific path.'''
467 470 global _rcpath
468 471 if _rcpath is None:
469 472 if 'HGRCPATH' in os.environ:
470 473 _rcpath = []
471 474 for p in os.environ['HGRCPATH'].split(os.pathsep):
472 475 if not p:
473 476 continue
474 477 p = util.expandpath(p)
475 478 if os.path.isdir(p):
476 479 for f, kind in osutil.listdir(p):
477 480 if f.endswith('.rc'):
478 481 _rcpath.append(os.path.join(p, f))
479 482 else:
480 483 _rcpath.append(p)
481 484 else:
482 485 _rcpath = osrcpath()
483 486 return _rcpath
484 487
485 488 if os.name != 'nt':
486 489
487 490 def rcfiles(path):
488 491 rcs = [os.path.join(path, 'hgrc')]
489 492 rcdir = os.path.join(path, 'hgrc.d')
490 493 try:
491 494 rcs.extend([os.path.join(rcdir, f)
492 495 for f, kind in osutil.listdir(rcdir)
493 496 if f.endswith(".rc")])
494 497 except OSError:
495 498 pass
496 499 return rcs
497 500
498 501 def systemrcpath():
499 502 path = []
500 503 if sys.platform == 'plan9':
501 504 root = 'lib/mercurial'
502 505 else:
503 506 root = 'etc/mercurial'
504 507 # old mod_python does not set sys.argv
505 508 if len(getattr(sys, 'argv', [])) > 0:
506 509 p = os.path.dirname(os.path.dirname(sys.argv[0]))
507 510 path.extend(rcfiles(os.path.join(p, root)))
508 511 path.extend(rcfiles('/' + root))
509 512 return path
510 513
511 514 def userrcpath():
512 515 if sys.platform == 'plan9':
513 516 return [os.environ['home'] + '/lib/hgrc']
514 517 else:
515 518 return [os.path.expanduser('~/.hgrc')]
516 519
517 520 else:
518 521
519 522 import _winreg
520 523
521 524 def systemrcpath():
522 525 '''return default os-specific hgrc search path'''
523 526 rcpath = []
524 527 filename = util.executablepath()
525 528 # Use mercurial.ini found in directory with hg.exe
526 529 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
527 530 if os.path.isfile(progrc):
528 531 rcpath.append(progrc)
529 532 return rcpath
530 533 # Use hgrc.d found in directory with hg.exe
531 534 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
532 535 if os.path.isdir(progrcd):
533 536 for f, kind in osutil.listdir(progrcd):
534 537 if f.endswith('.rc'):
535 538 rcpath.append(os.path.join(progrcd, f))
536 539 return rcpath
537 540 # else look for a system rcpath in the registry
538 541 value = util.lookupreg('SOFTWARE\\Mercurial', None,
539 542 _winreg.HKEY_LOCAL_MACHINE)
540 543 if not isinstance(value, str) or not value:
541 544 return rcpath
542 545 value = util.localpath(value)
543 546 for p in value.split(os.pathsep):
544 547 if p.lower().endswith('mercurial.ini'):
545 548 rcpath.append(p)
546 549 elif os.path.isdir(p):
547 550 for f, kind in osutil.listdir(p):
548 551 if f.endswith('.rc'):
549 552 rcpath.append(os.path.join(p, f))
550 553 return rcpath
551 554
552 555 def userrcpath():
553 556 '''return os-specific hgrc search path to the user dir'''
554 557 home = os.path.expanduser('~')
555 558 path = [os.path.join(home, 'mercurial.ini'),
556 559 os.path.join(home, '.hgrc')]
557 560 userprofile = os.environ.get('USERPROFILE')
558 561 if userprofile:
559 562 path.append(os.path.join(userprofile, 'mercurial.ini'))
560 563 path.append(os.path.join(userprofile, '.hgrc'))
561 564 return path
562 565
563 566 def revsingle(repo, revspec, default='.'):
564 567 if not revspec:
565 568 return repo[default]
566 569
567 570 l = revrange(repo, [revspec])
568 571 if len(l) < 1:
569 572 raise util.Abort(_('empty revision set'))
570 573 return repo[l[-1]]
571 574
572 575 def revpair(repo, revs):
573 576 if not revs:
574 577 return repo.dirstate.p1(), None
575 578
576 579 l = revrange(repo, revs)
577 580
578 581 if len(l) == 0:
579 582 if revs:
580 583 raise util.Abort(_('empty revision range'))
581 584 return repo.dirstate.p1(), None
582 585
583 586 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
584 587 return repo.lookup(l[0]), None
585 588
586 589 return repo.lookup(l[0]), repo.lookup(l[-1])
587 590
588 591 _revrangesep = ':'
589 592
590 593 def revrange(repo, revs):
591 594 """Yield revision as strings from a list of revision specifications."""
592 595
593 596 def revfix(repo, val, defval):
594 597 if not val and val != 0 and defval is not None:
595 598 return defval
596 599 return repo[val].rev()
597 600
598 601 seen, l = set(), []
599 602 for spec in revs:
600 603 if l and not seen:
601 604 seen = set(l)
602 605 # attempt to parse old-style ranges first to deal with
603 606 # things like old-tag which contain query metacharacters
604 607 try:
605 608 if isinstance(spec, int):
606 609 seen.add(spec)
607 610 l.append(spec)
608 611 continue
609 612
610 613 if _revrangesep in spec:
611 614 start, end = spec.split(_revrangesep, 1)
612 615 start = revfix(repo, start, 0)
613 616 end = revfix(repo, end, len(repo) - 1)
614 617 step = start > end and -1 or 1
615 618 if not seen and not l:
616 619 # by far the most common case: revs = ["-1:0"]
617 620 l = range(start, end + step, step)
618 621 # defer syncing seen until next iteration
619 622 continue
620 623 newrevs = set(xrange(start, end + step, step))
621 624 if seen:
622 625 newrevs.difference_update(seen)
623 626 seen.update(newrevs)
624 627 else:
625 628 seen = newrevs
626 629 l.extend(sorted(newrevs, reverse=start > end))
627 630 continue
628 631 elif spec and spec in repo: # single unquoted rev
629 632 rev = revfix(repo, spec, None)
630 633 if rev in seen:
631 634 continue
632 635 seen.add(rev)
633 636 l.append(rev)
634 637 continue
635 638 except error.RepoLookupError:
636 639 pass
637 640
638 641 # fall through to new-style queries if old-style fails
639 642 m = revset.match(repo.ui, spec)
640 643 dl = [r for r in m(repo, list(repo)) if r not in seen]
641 644 l.extend(dl)
642 645 seen.update(dl)
643 646
644 647 return l
645 648
646 649 def expandpats(pats):
647 650 if not util.expandglobs:
648 651 return list(pats)
649 652 ret = []
650 653 for p in pats:
651 654 kind, name = matchmod._patsplit(p, None)
652 655 if kind is None:
653 656 try:
654 657 globbed = glob.glob(name)
655 658 except re.error:
656 659 globbed = [name]
657 660 if globbed:
658 661 ret.extend(globbed)
659 662 continue
660 663 ret.append(p)
661 664 return ret
662 665
663 666 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
664 667 if pats == ("",):
665 668 pats = []
666 669 if not globbed and default == 'relpath':
667 670 pats = expandpats(pats or [])
668 671
669 672 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
670 673 default)
671 674 def badfn(f, msg):
672 675 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
673 676 m.bad = badfn
674 677 return m, pats
675 678
676 679 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
677 680 return matchandpats(ctx, pats, opts, globbed, default)[0]
678 681
679 682 def matchall(repo):
680 683 return matchmod.always(repo.root, repo.getcwd())
681 684
682 685 def matchfiles(repo, files):
683 686 return matchmod.exact(repo.root, repo.getcwd(), files)
684 687
685 688 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
686 689 if dry_run is None:
687 690 dry_run = opts.get('dry_run')
688 691 if similarity is None:
689 692 similarity = float(opts.get('similarity') or 0)
690 693 # we'd use status here, except handling of symlinks and ignore is tricky
691 694 added, unknown, deleted, removed = [], [], [], []
692 695 audit_path = pathauditor(repo.root)
693 696 m = match(repo[None], pats, opts)
694 697 rejected = []
695 698 m.bad = lambda x, y: rejected.append(x)
696 699
697 700 for abs in repo.walk(m):
698 701 target = repo.wjoin(abs)
699 702 good = True
700 703 try:
701 704 audit_path(abs)
702 705 except (OSError, util.Abort):
703 706 good = False
704 707 rel = m.rel(abs)
705 708 exact = m.exact(abs)
706 709 if good and abs not in repo.dirstate:
707 710 unknown.append(abs)
708 711 if repo.ui.verbose or not exact:
709 712 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
710 713 elif (repo.dirstate[abs] != 'r' and
711 714 (not good or not os.path.lexists(target) or
712 715 (os.path.isdir(target) and not os.path.islink(target)))):
713 716 deleted.append(abs)
714 717 if repo.ui.verbose or not exact:
715 718 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
716 719 # for finding renames
717 720 elif repo.dirstate[abs] == 'r':
718 721 removed.append(abs)
719 722 elif repo.dirstate[abs] == 'a':
720 723 added.append(abs)
721 724 copies = {}
722 725 if similarity > 0:
723 726 for old, new, score in similar.findrenames(repo,
724 727 added + unknown, removed + deleted, similarity):
725 728 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
726 729 repo.ui.status(_('recording removal of %s as rename to %s '
727 730 '(%d%% similar)\n') %
728 731 (m.rel(old), m.rel(new), score * 100))
729 732 copies[new] = old
730 733
731 734 if not dry_run:
732 735 wctx = repo[None]
733 736 wlock = repo.wlock()
734 737 try:
735 738 wctx.forget(deleted)
736 739 wctx.add(unknown)
737 740 for new, old in copies.iteritems():
738 741 wctx.copy(old, new)
739 742 finally:
740 743 wlock.release()
741 744
742 745 for f in rejected:
743 746 if f in m.files():
744 747 return 1
745 748 return 0
746 749
747 750 def updatedir(ui, repo, patches, similarity=0):
748 751 '''Update dirstate after patch application according to metadata'''
749 752 if not patches:
750 753 return []
751 754 copies = []
752 755 removes = set()
753 756 cfiles = patches.keys()
754 757 cwd = repo.getcwd()
755 758 if cwd:
756 759 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
757 760 for f in patches:
758 761 gp = patches[f]
759 762 if not gp:
760 763 continue
761 764 if gp.op == 'RENAME':
762 765 copies.append((gp.oldpath, gp.path))
763 766 removes.add(gp.oldpath)
764 767 elif gp.op == 'COPY':
765 768 copies.append((gp.oldpath, gp.path))
766 769 elif gp.op == 'DELETE':
767 770 removes.add(gp.path)
768 771
769 772 wctx = repo[None]
770 773 for src, dst in copies:
771 774 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
772 775 if (not similarity) and removes:
773 776 wctx.remove(sorted(removes), True)
774 777
775 778 for f in patches:
776 779 gp = patches[f]
777 780 if gp and gp.mode:
778 781 islink, isexec = gp.mode
779 782 dst = repo.wjoin(gp.path)
780 783 # patch won't create empty files
781 784 if gp.op == 'ADD' and not os.path.lexists(dst):
782 785 flags = (isexec and 'x' or '') + (islink and 'l' or '')
783 786 repo.wwrite(gp.path, '', flags)
784 787 util.setflags(dst, islink, isexec)
785 788 addremove(repo, cfiles, similarity=similarity)
786 789 files = patches.keys()
787 790 files.extend([r for r in removes if r not in files])
788 791 return sorted(files)
789 792
790 793 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
791 794 """Update the dirstate to reflect the intent of copying src to dst. For
792 795 different reasons it might not end with dst being marked as copied from src.
793 796 """
794 797 origsrc = repo.dirstate.copied(src) or src
795 798 if dst == origsrc: # copying back a copy?
796 799 if repo.dirstate[dst] not in 'mn' and not dryrun:
797 800 repo.dirstate.normallookup(dst)
798 801 else:
799 802 if repo.dirstate[origsrc] == 'a' and origsrc == src:
800 803 if not ui.quiet:
801 804 ui.warn(_("%s has not been committed yet, so no copy "
802 805 "data will be stored for %s.\n")
803 806 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
804 807 if repo.dirstate[dst] in '?r' and not dryrun:
805 808 wctx.add([dst])
806 809 elif not dryrun:
807 810 wctx.copy(origsrc, dst)
808 811
809 812 def readrequires(opener, supported):
810 813 '''Reads and parses .hg/requires and checks if all entries found
811 814 are in the list of supported features.'''
812 815 requirements = set(opener.read("requires").splitlines())
813 816 missings = []
814 817 for r in requirements:
815 818 if r not in supported:
816 819 if not r or not r[0].isalnum():
817 820 raise error.RequirementError(_(".hg/requires file is corrupt"))
818 821 missings.append(r)
819 822 missings.sort()
820 823 if missings:
821 824 raise error.RequirementError(
822 825 _("unknown repository format: requires features '%s' (upgrade "
823 826 "Mercurial)") % "', '".join(missings))
824 827 return requirements
825 828
826 829 class filecacheentry(object):
827 830 def __init__(self, path):
828 831 self.path = path
829 832 self.cachestat = filecacheentry.stat(self.path)
830 833
831 834 if self.cachestat:
832 835 self._cacheable = self.cachestat.cacheable()
833 836 else:
834 837 # None means we don't know yet
835 838 self._cacheable = None
836 839
837 840 def refresh(self):
838 841 if self.cacheable():
839 842 self.cachestat = filecacheentry.stat(self.path)
840 843
841 844 def cacheable(self):
842 845 if self._cacheable is not None:
843 846 return self._cacheable
844 847
845 848 # we don't know yet, assume it is for now
846 849 return True
847 850
848 851 def changed(self):
849 852 # no point in going further if we can't cache it
850 853 if not self.cacheable():
851 854 return True
852 855
853 856 newstat = filecacheentry.stat(self.path)
854 857
855 858 # we may not know if it's cacheable yet, check again now
856 859 if newstat and self._cacheable is None:
857 860 self._cacheable = newstat.cacheable()
858 861
859 862 # check again
860 863 if not self._cacheable:
861 864 return True
862 865
863 866 if self.cachestat != newstat:
864 867 self.cachestat = newstat
865 868 return True
866 869 else:
867 870 return False
868 871
869 872 @staticmethod
870 873 def stat(path):
871 874 try:
872 875 return util.cachestat(path)
873 876 except OSError, e:
874 877 if e.errno != errno.ENOENT:
875 878 raise
876 879
877 880 class filecache(object):
878 881 '''A property like decorator that tracks a file under .hg/ for updates.
879 882
880 883 Records stat info when called in _filecache.
881 884
882 885 On subsequent calls, compares old stat info with new info, and recreates
883 886 the object when needed, updating the new stat info in _filecache.
884 887
885 888 Mercurial either atomic renames or appends for files under .hg,
886 889 so to ensure the cache is reliable we need the filesystem to be able
887 890 to tell us if a file has been replaced. If it can't, we fallback to
888 891 recreating the object on every call (essentially the same behaviour as
889 892 propertycache).'''
890 893 def __init__(self, path):
891 894 self.path = path
892 895
893 896 def join(self, obj, fname):
894 897 """Used to compute the runtime path of the cached file.
895 898
896 899 Users should subclass filecache and provide their own version of this
897 900 function to call the appropriate join function on 'obj' (an instance
898 901 of the class that its member function was decorated).
899 902 """
900 903 return obj.join(fname)
901 904
902 905 def __call__(self, func):
903 906 self.func = func
904 907 self.name = func.__name__
905 908 return self
906 909
907 910 def __get__(self, obj, type=None):
908 911 # do we need to check if the file changed?
909 912 if self.name in obj.__dict__:
910 913 return obj.__dict__[self.name]
911 914
912 915 entry = obj._filecache.get(self.name)
913 916
914 917 if entry:
915 918 if entry.changed():
916 919 entry.obj = self.func(obj)
917 920 else:
918 921 path = self.join(obj, self.path)
919 922
920 923 # We stat -before- creating the object so our cache doesn't lie if
921 924 # a writer modified between the time we read and stat
922 925 entry = filecacheentry(path)
923 926 entry.obj = self.func(obj)
924 927
925 928 obj._filecache[self.name] = entry
926 929
927 930 obj.__dict__[self.name] = entry.obj
928 931 return entry.obj
929 932
930 933 def __set__(self, obj, value):
931 934 if self.name in obj._filecache:
932 935 obj._filecache[self.name].obj = value # update cached copy
933 936 obj.__dict__[self.name] = value # update copy returned by obj.x
934 937
935 938 def __delete__(self, obj):
936 939 try:
937 940 del obj.__dict__[self.name]
938 941 except KeyError:
939 942 raise AttributeError, self.name
@@ -1,505 +1,505 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 i18n import _
9 9 import osutil, scmutil, util, parsers
10 10 import os, stat, errno
11 11
12 12 _sha = util.sha1
13 13
14 14 # This avoids a collision between a file named foo and a dir named
15 15 # foo.i or foo.d
16 16 def _encodedir(path):
17 17 '''
18 18 >>> _encodedir('data/foo.i')
19 19 'data/foo.i'
20 20 >>> _encodedir('data/foo.i/bla.i')
21 21 'data/foo.i.hg/bla.i'
22 22 >>> _encodedir('data/foo.i.hg/bla.i')
23 23 'data/foo.i.hg.hg/bla.i'
24 24 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
25 25 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
26 26 '''
27 27 return (path
28 28 .replace(".hg/", ".hg.hg/")
29 29 .replace(".i/", ".i.hg/")
30 30 .replace(".d/", ".d.hg/"))
31 31
32 32 encodedir = getattr(parsers, 'encodedir', _encodedir)
33 33
34 34 def decodedir(path):
35 35 '''
36 36 >>> decodedir('data/foo.i')
37 37 'data/foo.i'
38 38 >>> decodedir('data/foo.i.hg/bla.i')
39 39 'data/foo.i/bla.i'
40 40 >>> decodedir('data/foo.i.hg.hg/bla.i')
41 41 'data/foo.i.hg/bla.i'
42 42 '''
43 43 if ".hg/" not in path:
44 44 return path
45 45 return (path
46 46 .replace(".d.hg/", ".d/")
47 47 .replace(".i.hg/", ".i/")
48 48 .replace(".hg.hg/", ".hg/"))
49 49
50 50 def _buildencodefun():
51 51 '''
52 52 >>> enc, dec = _buildencodefun()
53 53
54 54 >>> enc('nothing/special.txt')
55 55 'nothing/special.txt'
56 56 >>> dec('nothing/special.txt')
57 57 'nothing/special.txt'
58 58
59 59 >>> enc('HELLO')
60 60 '_h_e_l_l_o'
61 61 >>> dec('_h_e_l_l_o')
62 62 'HELLO'
63 63
64 64 >>> enc('hello:world?')
65 65 'hello~3aworld~3f'
66 66 >>> dec('hello~3aworld~3f')
67 67 'hello:world?'
68 68
69 69 >>> enc('the\x07quick\xADshot')
70 70 'the~07quick~adshot'
71 71 >>> dec('the~07quick~adshot')
72 72 'the\\x07quick\\xadshot'
73 73 '''
74 74 e = '_'
75 75 winreserved = [ord(x) for x in '\\:*?"<>|']
76 76 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
77 77 for x in (range(32) + range(126, 256) + winreserved):
78 78 cmap[chr(x)] = "~%02x" % x
79 79 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
80 80 cmap[chr(x)] = e + chr(x).lower()
81 81 dmap = {}
82 82 for k, v in cmap.iteritems():
83 83 dmap[v] = k
84 84 def decode(s):
85 85 i = 0
86 86 while i < len(s):
87 87 for l in xrange(1, 4):
88 88 try:
89 89 yield dmap[s[i:i + l]]
90 90 i += l
91 91 break
92 92 except KeyError:
93 93 pass
94 94 else:
95 95 raise KeyError
96 96 return (lambda s: ''.join([cmap[c] for c in s]),
97 97 lambda s: ''.join(list(decode(s))))
98 98
99 99 _encodefname, _decodefname = _buildencodefun()
100 100
101 101 def encodefilename(s):
102 102 '''
103 103 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
104 104 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
105 105 '''
106 106 return _encodefname(encodedir(s))
107 107
108 108 def decodefilename(s):
109 109 '''
110 110 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
111 111 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
112 112 '''
113 113 return decodedir(_decodefname(s))
114 114
115 115 def _buildlowerencodefun():
116 116 '''
117 117 >>> f = _buildlowerencodefun()
118 118 >>> f('nothing/special.txt')
119 119 'nothing/special.txt'
120 120 >>> f('HELLO')
121 121 'hello'
122 122 >>> f('hello:world?')
123 123 'hello~3aworld~3f'
124 124 >>> f('the\x07quick\xADshot')
125 125 'the~07quick~adshot'
126 126 '''
127 127 winreserved = [ord(x) for x in '\\:*?"<>|']
128 128 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
129 129 for x in (range(32) + range(126, 256) + winreserved):
130 130 cmap[chr(x)] = "~%02x" % x
131 131 for x in range(ord("A"), ord("Z")+1):
132 132 cmap[chr(x)] = chr(x).lower()
133 133 return lambda s: "".join([cmap[c] for c in s])
134 134
135 135 lowerencode = _buildlowerencodefun()
136 136
137 137 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
138 138 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
139 139 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
140 140 def _auxencode(path, dotencode):
141 141 '''
142 142 Encodes filenames containing names reserved by Windows or which end in
143 143 period or space. Does not touch other single reserved characters c.
144 144 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
145 145 Additionally encodes space or period at the beginning, if dotencode is
146 146 True. Parameter path is assumed to be all lowercase.
147 147 A segment only needs encoding if a reserved name appears as a
148 148 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
149 149 doesn't need encoding.
150 150
151 151 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
152 152 >>> _auxencode(s.split('/'), True)
153 153 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
154 154 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
155 155 >>> _auxencode(s.split('/'), False)
156 156 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
157 157 >>> _auxencode(['foo. '], True)
158 158 ['foo.~20']
159 159 >>> _auxencode([' .foo'], True)
160 160 ['~20.foo']
161 161 '''
162 162 for i, n in enumerate(path):
163 163 if not n:
164 164 continue
165 165 if dotencode and n[0] in '. ':
166 166 n = "~%02x" % ord(n[0]) + n[1:]
167 167 path[i] = n
168 168 else:
169 169 l = n.find('.')
170 170 if l == -1:
171 171 l = len(n)
172 172 if ((l == 3 and n[:3] in _winres3) or
173 173 (l == 4 and n[3] <= '9' and n[3] >= '1'
174 174 and n[:3] in _winres4)):
175 175 # encode third letter ('aux' -> 'au~78')
176 176 ec = "~%02x" % ord(n[2])
177 177 n = n[0:2] + ec + n[3:]
178 178 path[i] = n
179 179 if n[-1] in '. ':
180 180 # encode last period or space ('foo...' -> 'foo..~2e')
181 181 path[i] = n[:-1] + "~%02x" % ord(n[-1])
182 182 return path
183 183
184 184 _maxstorepathlen = 120
185 185 _dirprefixlen = 8
186 186 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
187 187
188 188 def _hashencode(path, dotencode):
189 189 digest = _sha(path).hexdigest()
190 190 le = lowerencode(path).split('/')[1:]
191 191 parts = _auxencode(le, dotencode)
192 192 basename = parts[-1]
193 193 _root, ext = os.path.splitext(basename)
194 194 sdirs = []
195 195 sdirslen = 0
196 196 for p in parts[:-1]:
197 197 d = p[:_dirprefixlen]
198 198 if d[-1] in '. ':
199 199 # Windows can't access dirs ending in period or space
200 200 d = d[:-1] + '_'
201 201 if sdirslen == 0:
202 202 t = len(d)
203 203 else:
204 204 t = sdirslen + 1 + len(d)
205 205 if t > _maxshortdirslen:
206 206 break
207 207 sdirs.append(d)
208 208 sdirslen = t
209 209 dirs = '/'.join(sdirs)
210 210 if len(dirs) > 0:
211 211 dirs += '/'
212 212 res = 'dh/' + dirs + digest + ext
213 213 spaceleft = _maxstorepathlen - len(res)
214 214 if spaceleft > 0:
215 215 filler = basename[:spaceleft]
216 216 res = 'dh/' + dirs + filler + digest + ext
217 217 return res
218 218
219 219 def _hybridencode(path, dotencode):
220 220 '''encodes path with a length limit
221 221
222 222 Encodes all paths that begin with 'data/', according to the following.
223 223
224 224 Default encoding (reversible):
225 225
226 226 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
227 227 characters are encoded as '~xx', where xx is the two digit hex code
228 228 of the character (see encodefilename).
229 229 Relevant path components consisting of Windows reserved filenames are
230 230 masked by encoding the third character ('aux' -> 'au~78', see auxencode).
231 231
232 232 Hashed encoding (not reversible):
233 233
234 234 If the default-encoded path is longer than _maxstorepathlen, a
235 235 non-reversible hybrid hashing of the path is done instead.
236 236 This encoding uses up to _dirprefixlen characters of all directory
237 237 levels of the lowerencoded path, but not more levels than can fit into
238 238 _maxshortdirslen.
239 239 Then follows the filler followed by the sha digest of the full path.
240 240 The filler is the beginning of the basename of the lowerencoded path
241 241 (the basename is everything after the last path separator). The filler
242 242 is as long as possible, filling in characters from the basename until
243 243 the encoded path has _maxstorepathlen characters (or all chars of the
244 244 basename have been taken).
245 245 The extension (e.g. '.i' or '.d') is preserved.
246 246
247 247 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
248 248 encoding was used.
249 249 '''
250 250 path = encodedir(path)
251 251 ef = _encodefname(path).split('/')
252 252 res = '/'.join(_auxencode(ef, dotencode))
253 253 if len(res) > _maxstorepathlen:
254 254 res = _hashencode(path, dotencode)
255 255 return res
256 256
257 257 def _pathencode(path):
258 258 if len(path) > _maxstorepathlen:
259 259 return None
260 260 ef = _encodefname(encodedir(path)).split('/')
261 261 res = '/'.join(_auxencode(ef, True))
262 262 if len(res) > _maxstorepathlen:
263 263 return None
264 264 return res
265 265
266 266 _pathencode = getattr(parsers, 'pathencode', _pathencode)
267 267
268 268 def _dothybridencode(f):
269 269 ef = _pathencode(f)
270 270 if ef is None:
271 271 return _hashencode(encodedir(f), True)
272 272 return ef
273 273
274 274 def _plainhybridencode(f):
275 275 return _hybridencode(f, False)
276 276
277 def _calcmode(path):
277 def _calcmode(vfs):
278 278 try:
279 279 # files in .hg/ will be created using this mode
280 mode = os.stat(path).st_mode
280 mode = vfs.stat().st_mode
281 281 # avoid some useless chmods
282 282 if (0777 & ~util.umask) == (0777 & mode):
283 283 mode = None
284 284 except OSError:
285 285 mode = None
286 286 return mode
287 287
288 288 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
289 289 ' phaseroots obsstore')
290 290
291 291 class basicstore(object):
292 292 '''base class for local repository stores'''
293 293 def __init__(self, path, vfstype):
294 294 vfs = vfstype(path)
295 295 self.path = vfs.base
296 self.createmode = _calcmode(path)
296 self.createmode = _calcmode(vfs)
297 297 vfs.createmode = self.createmode
298 298 self.vfs = scmutil.filtervfs(vfs, encodedir)
299 299 self.opener = self.vfs
300 300
301 301 def join(self, f):
302 302 return self.path + '/' + encodedir(f)
303 303
304 304 def _walk(self, relpath, recurse):
305 305 '''yields (unencoded, encoded, size)'''
306 306 path = self.path
307 307 if relpath:
308 308 path += '/' + relpath
309 309 striplen = len(self.path) + 1
310 310 l = []
311 311 if os.path.isdir(path):
312 312 visit = [path]
313 313 while visit:
314 314 p = visit.pop()
315 315 for f, kind, st in osutil.listdir(p, stat=True):
316 316 fp = p + '/' + f
317 317 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
318 318 n = util.pconvert(fp[striplen:])
319 319 l.append((decodedir(n), n, st.st_size))
320 320 elif kind == stat.S_IFDIR and recurse:
321 321 visit.append(fp)
322 322 l.sort()
323 323 return l
324 324
325 325 def datafiles(self):
326 326 return self._walk('data', True)
327 327
328 328 def walk(self):
329 329 '''yields (unencoded, encoded, size)'''
330 330 # yield data files first
331 331 for x in self.datafiles():
332 332 yield x
333 333 # yield manifest before changelog
334 334 for x in reversed(self._walk('', False)):
335 335 yield x
336 336
337 337 def copylist(self):
338 338 return ['requires'] + _data.split()
339 339
340 340 def write(self):
341 341 pass
342 342
343 343 class encodedstore(basicstore):
344 344 def __init__(self, path, vfstype):
345 345 vfs = vfstype(path + '/store')
346 346 self.path = vfs.base
347 self.createmode = _calcmode(self.path)
347 self.createmode = _calcmode(vfs)
348 348 vfs.createmode = self.createmode
349 349 self.vfs = scmutil.filtervfs(vfs, encodefilename)
350 350 self.opener = self.vfs
351 351
352 352 def datafiles(self):
353 353 for a, b, size in self._walk('data', True):
354 354 try:
355 355 a = decodefilename(a)
356 356 except KeyError:
357 357 a = None
358 358 yield a, b, size
359 359
360 360 def join(self, f):
361 361 return self.path + '/' + encodefilename(f)
362 362
363 363 def copylist(self):
364 364 return (['requires', '00changelog.i'] +
365 365 ['store/' + f for f in _data.split()])
366 366
367 367 class fncache(object):
368 368 # the filename used to be partially encoded
369 369 # hence the encodedir/decodedir dance
370 370 def __init__(self, vfs):
371 371 self.vfs = vfs
372 372 self.entries = None
373 373 self._dirty = False
374 374
375 375 def _load(self):
376 376 '''fill the entries from the fncache file'''
377 377 self._dirty = False
378 378 try:
379 379 fp = self.vfs('fncache', mode='rb')
380 380 except IOError:
381 381 # skip nonexistent file
382 382 self.entries = set()
383 383 return
384 384 self.entries = set(decodedir(fp.read()).splitlines())
385 385 if '' in self.entries:
386 386 fp.seek(0)
387 387 for n, line in enumerate(fp):
388 388 if not line.rstrip('\n'):
389 389 t = _('invalid entry in fncache, line %s') % (n + 1)
390 390 raise util.Abort(t)
391 391 fp.close()
392 392
393 393 def _write(self, files, atomictemp):
394 394 fp = self.vfs('fncache', mode='wb', atomictemp=atomictemp)
395 395 if files:
396 396 fp.write(encodedir('\n'.join(files) + '\n'))
397 397 fp.close()
398 398 self._dirty = False
399 399
400 400 def rewrite(self, files):
401 401 self._write(files, False)
402 402 self.entries = set(files)
403 403
404 404 def write(self):
405 405 if self._dirty:
406 406 self._write(self.entries, True)
407 407
408 408 def add(self, fn):
409 409 if self.entries is None:
410 410 self._load()
411 411 if fn not in self.entries:
412 412 self._dirty = True
413 413 self.entries.add(fn)
414 414
415 415 def __contains__(self, fn):
416 416 if self.entries is None:
417 417 self._load()
418 418 return fn in self.entries
419 419
420 420 def __iter__(self):
421 421 if self.entries is None:
422 422 self._load()
423 423 return iter(self.entries)
424 424
425 425 class _fncachevfs(scmutil.abstractvfs):
426 426 def __init__(self, vfs, fnc, encode):
427 427 self.vfs = vfs
428 428 self.fncache = fnc
429 429 self.encode = encode
430 430
431 431 def _getmustaudit(self):
432 432 return self.vfs.mustaudit
433 433
434 434 def _setmustaudit(self, onoff):
435 435 self.vfs.mustaudit = onoff
436 436
437 437 mustaudit = property(_getmustaudit, _setmustaudit)
438 438
439 439 def __call__(self, path, mode='r', *args, **kw):
440 440 if mode not in ('r', 'rb') and path.startswith('data/'):
441 441 self.fncache.add(path)
442 442 return self.vfs(self.encode(path), mode, *args, **kw)
443 443
444 444 def join(self, path):
445 445 if path:
446 446 return self.vfs.join(self.encode(path))
447 447 else:
448 448 return self.vfs.join(path)
449 449
450 450 class fncachestore(basicstore):
451 451 def __init__(self, path, vfstype, dotencode):
452 452 if dotencode:
453 453 encode = _dothybridencode
454 454 else:
455 455 encode = _plainhybridencode
456 456 self.encode = encode
457 457 vfs = vfstype(path + '/store')
458 458 self.path = vfs.base
459 459 self.pathsep = self.path + '/'
460 self.createmode = _calcmode(self.path)
460 self.createmode = _calcmode(vfs)
461 461 vfs.createmode = self.createmode
462 462 fnc = fncache(vfs)
463 463 self.fncache = fnc
464 464 self.vfs = _fncachevfs(vfs, fnc, encode)
465 465 self.opener = self.vfs
466 466
467 467 def join(self, f):
468 468 return self.pathsep + self.encode(f)
469 469
470 470 def getsize(self, path):
471 471 return os.stat(self.pathsep + path).st_size
472 472
473 473 def datafiles(self):
474 474 rewrite = False
475 475 existing = []
476 476 for f in sorted(self.fncache):
477 477 ef = self.encode(f)
478 478 try:
479 479 yield f, ef, self.getsize(ef)
480 480 existing.append(f)
481 481 except OSError, err:
482 482 if err.errno != errno.ENOENT:
483 483 raise
484 484 # nonexistent entry
485 485 rewrite = True
486 486 if rewrite:
487 487 # rewrite fncache to remove nonexistent entries
488 488 # (may be caused by rollback / strip)
489 489 self.fncache.rewrite(existing)
490 490
491 491 def copylist(self):
492 492 d = ('data dh fncache phaseroots obsstore'
493 493 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
494 494 return (['requires', '00changelog.i'] +
495 495 ['store/' + f for f in d.split()])
496 496
497 497 def write(self):
498 498 self.fncache.write()
499 499
500 500 def store(requirements, path, vfstype):
501 501 if 'store' in requirements:
502 502 if 'fncache' in requirements:
503 503 return fncachestore(path, vfstype, 'dotencode' in requirements)
504 504 return encodedstore(path, vfstype)
505 505 return basicstore(path, vfstype)
General Comments 0
You need to be logged in to leave comments. Login now