##// END OF EJS Templates
pathauditor: preserve case in abort messages...
FUJIWARA Katsunori -
r15665:081e795c stable
parent child Browse files
Show More
@@ -1,803 +1,809 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
10 10 import match as matchmod
11 11 import os, errno, re, stat, sys, glob
12 12
13 13 def checkfilename(f):
14 14 '''Check that the filename f is an acceptable filename for a tracked file'''
15 15 if '\r' in f or '\n' in f:
16 16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
17 17
18 18 def checkportable(ui, f):
19 19 '''Check if filename f is portable and warn or abort depending on config'''
20 20 checkfilename(f)
21 21 abort, warn = checkportabilityalert(ui)
22 22 if abort or warn:
23 23 msg = util.checkwinfilename(f)
24 24 if msg:
25 25 msg = "%s: %r" % (msg, f)
26 26 if abort:
27 27 raise util.Abort(msg)
28 28 ui.warn(_("warning: %s\n") % msg)
29 29
30 30 def checkportabilityalert(ui):
31 31 '''check if the user's config requests nothing, a warning, or abort for
32 32 non-portable filenames'''
33 33 val = ui.config('ui', 'portablefilenames', 'warn')
34 34 lval = val.lower()
35 35 bval = util.parsebool(val)
36 36 abort = os.name == 'nt' or lval == 'abort'
37 37 warn = bval or lval == 'warn'
38 38 if bval is None and not (warn or abort or lval == 'ignore'):
39 39 raise error.ConfigError(
40 40 _("ui.portablefilenames value is invalid ('%s')") % val)
41 41 return abort, warn
42 42
43 43 class casecollisionauditor(object):
44 44 def __init__(self, ui, abort, existingiter):
45 45 self._ui = ui
46 46 self._abort = abort
47 47 self._map = {}
48 48 for f in existingiter:
49 49 self._map[encoding.lower(f)] = f
50 50
51 51 def __call__(self, f):
52 52 fl = encoding.lower(f)
53 53 map = self._map
54 54 if fl in map and map[fl] != f:
55 55 msg = _('possible case-folding collision for %s') % f
56 56 if self._abort:
57 57 raise util.Abort(msg)
58 58 self._ui.warn(_("warning: %s\n") % msg)
59 59 map[fl] = f
60 60
61 61 class pathauditor(object):
62 62 '''ensure that a filesystem path contains no banned components.
63 63 the following properties of a path are checked:
64 64
65 65 - ends with a directory separator
66 66 - under top-level .hg
67 67 - starts at the root of a windows drive
68 68 - contains ".."
69 69 - traverses a symlink (e.g. a/symlink_here/b)
70 70 - inside a nested repository (a callback can be used to approve
71 71 some nested repositories, e.g., subrepositories)
72 72 '''
73 73
74 74 def __init__(self, root, callback=None):
75 75 self.audited = set()
76 76 self.auditeddir = set()
77 77 self.root = root
78 78 self.callback = callback
79 79
80 80 def __call__(self, path):
81 81 '''Check the relative path.
82 82 path may contain a pattern (e.g. foodir/**.txt)'''
83 83
84 84 normpath = os.path.normcase(path)
85 85 if normpath in self.audited:
86 86 return
87 87 # AIX ignores "/" at end of path, others raise EISDIR.
88 88 if util.endswithsep(path):
89 89 raise util.Abort(_("path ends in directory separator: %s") % path)
90 parts = util.splitpath(normpath)
90 parts = util.splitpath(path)
91 91 if (os.path.splitdrive(path)[0]
92 92 or parts[0].lower() in ('.hg', '.hg.', '')
93 93 or os.pardir in parts):
94 94 raise util.Abort(_("path contains illegal component: %s") % path)
95 95 if '.hg' in path.lower():
96 96 lparts = [p.lower() for p in parts]
97 97 for p in '.hg', '.hg.':
98 98 if p in lparts[1:]:
99 99 pos = lparts.index(p)
100 100 base = os.path.join(*parts[:pos])
101 101 raise util.Abort(_('path %r is inside nested repo %r')
102 102 % (path, base))
103 103
104 normparts = util.splitpath(normpath)
105 assert len(parts) == len(normparts)
106
104 107 parts.pop()
108 normparts.pop()
105 109 prefixes = []
106 110 while parts:
107 111 prefix = os.sep.join(parts)
108 if prefix in self.auditeddir:
112 normprefix = os.sep.join(normparts)
113 if normprefix in self.auditeddir:
109 114 break
110 115 curpath = os.path.join(self.root, prefix)
111 116 try:
112 117 st = os.lstat(curpath)
113 118 except OSError, err:
114 119 # EINVAL can be raised as invalid path syntax under win32.
115 120 # They must be ignored for patterns can be checked too.
116 121 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
117 122 raise
118 123 else:
119 124 if stat.S_ISLNK(st.st_mode):
120 125 raise util.Abort(
121 126 _('path %r traverses symbolic link %r')
122 127 % (path, prefix))
123 128 elif (stat.S_ISDIR(st.st_mode) and
124 129 os.path.isdir(os.path.join(curpath, '.hg'))):
125 130 if not self.callback or not self.callback(curpath):
126 131 raise util.Abort(_('path %r is inside nested repo %r') %
127 132 (path, prefix))
128 prefixes.append(prefix)
133 prefixes.append(normprefix)
129 134 parts.pop()
135 normparts.pop()
130 136
131 137 self.audited.add(normpath)
132 138 # only add prefixes to the cache after checking everything: we don't
133 139 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 140 self.auditeddir.update(prefixes)
135 141
136 142 class abstractopener(object):
137 143 """Abstract base class; cannot be instantiated"""
138 144
139 145 def __init__(self, *args, **kwargs):
140 146 '''Prevent instantiation; don't call this from subclasses.'''
141 147 raise NotImplementedError('attempted instantiating ' + str(type(self)))
142 148
143 149 def read(self, path):
144 150 fp = self(path, 'rb')
145 151 try:
146 152 return fp.read()
147 153 finally:
148 154 fp.close()
149 155
150 156 def write(self, path, data):
151 157 fp = self(path, 'wb')
152 158 try:
153 159 return fp.write(data)
154 160 finally:
155 161 fp.close()
156 162
157 163 def append(self, path, data):
158 164 fp = self(path, 'ab')
159 165 try:
160 166 return fp.write(data)
161 167 finally:
162 168 fp.close()
163 169
164 170 class opener(abstractopener):
165 171 '''Open files relative to a base directory
166 172
167 173 This class is used to hide the details of COW semantics and
168 174 remote file access from higher level code.
169 175 '''
170 176 def __init__(self, base, audit=True):
171 177 self.base = base
172 178 self._audit = audit
173 179 if audit:
174 180 self.auditor = pathauditor(base)
175 181 else:
176 182 self.auditor = util.always
177 183 self.createmode = None
178 184 self._trustnlink = None
179 185
180 186 @util.propertycache
181 187 def _cansymlink(self):
182 188 return util.checklink(self.base)
183 189
184 190 def _fixfilemode(self, name):
185 191 if self.createmode is None:
186 192 return
187 193 os.chmod(name, self.createmode & 0666)
188 194
189 195 def __call__(self, path, mode="r", text=False, atomictemp=False):
190 196 if self._audit:
191 197 r = util.checkosfilename(path)
192 198 if r:
193 199 raise util.Abort("%s: %r" % (r, path))
194 200 self.auditor(path)
195 201 f = os.path.join(self.base, path)
196 202
197 203 if not text and "b" not in mode:
198 204 mode += "b" # for that other OS
199 205
200 206 nlink = -1
201 207 dirname, basename = os.path.split(f)
202 208 # If basename is empty, then the path is malformed because it points
203 209 # to a directory. Let the posixfile() call below raise IOError.
204 210 if basename and mode not in ('r', 'rb'):
205 211 if atomictemp:
206 212 if not os.path.isdir(dirname):
207 213 util.makedirs(dirname, self.createmode)
208 214 return util.atomictempfile(f, mode, self.createmode)
209 215 try:
210 216 if 'w' in mode:
211 217 util.unlink(f)
212 218 nlink = 0
213 219 else:
214 220 # nlinks() may behave differently for files on Windows
215 221 # shares if the file is open.
216 222 fd = util.posixfile(f)
217 223 nlink = util.nlinks(f)
218 224 if nlink < 1:
219 225 nlink = 2 # force mktempcopy (issue1922)
220 226 fd.close()
221 227 except (OSError, IOError), e:
222 228 if e.errno != errno.ENOENT:
223 229 raise
224 230 nlink = 0
225 231 if not os.path.isdir(dirname):
226 232 util.makedirs(dirname, self.createmode)
227 233 if nlink > 0:
228 234 if self._trustnlink is None:
229 235 self._trustnlink = nlink > 1 or util.checknlink(f)
230 236 if nlink > 1 or not self._trustnlink:
231 237 util.rename(util.mktempcopy(f), f)
232 238 fp = util.posixfile(f, mode)
233 239 if nlink == 0:
234 240 self._fixfilemode(f)
235 241 return fp
236 242
237 243 def symlink(self, src, dst):
238 244 self.auditor(dst)
239 245 linkname = os.path.join(self.base, dst)
240 246 try:
241 247 os.unlink(linkname)
242 248 except OSError:
243 249 pass
244 250
245 251 dirname = os.path.dirname(linkname)
246 252 if not os.path.exists(dirname):
247 253 util.makedirs(dirname, self.createmode)
248 254
249 255 if self._cansymlink:
250 256 try:
251 257 os.symlink(src, linkname)
252 258 except OSError, err:
253 259 raise OSError(err.errno, _('could not symlink to %r: %s') %
254 260 (src, err.strerror), linkname)
255 261 else:
256 262 f = self(dst, "w")
257 263 f.write(src)
258 264 f.close()
259 265 self._fixfilemode(dst)
260 266
261 267 def audit(self, path):
262 268 self.auditor(path)
263 269
264 270 class filteropener(abstractopener):
265 271 '''Wrapper opener for filtering filenames with a function.'''
266 272
267 273 def __init__(self, opener, filter):
268 274 self._filter = filter
269 275 self._orig = opener
270 276
271 277 def __call__(self, path, *args, **kwargs):
272 278 return self._orig(self._filter(path), *args, **kwargs)
273 279
274 280 def canonpath(root, cwd, myname, auditor=None):
275 281 '''return the canonical path of myname, given cwd and root'''
276 282 if util.endswithsep(root):
277 283 rootsep = root
278 284 else:
279 285 rootsep = root + os.sep
280 286 name = myname
281 287 if not os.path.isabs(name):
282 288 name = os.path.join(root, cwd, name)
283 289 name = os.path.normpath(name)
284 290 if auditor is None:
285 291 auditor = pathauditor(root)
286 292 if name != rootsep and name.startswith(rootsep):
287 293 name = name[len(rootsep):]
288 294 auditor(name)
289 295 return util.pconvert(name)
290 296 elif name == root:
291 297 return ''
292 298 else:
293 299 # Determine whether `name' is in the hierarchy at or beneath `root',
294 300 # by iterating name=dirname(name) until that causes no change (can't
295 301 # check name == '/', because that doesn't work on windows). For each
296 302 # `name', compare dev/inode numbers. If they match, the list `rel'
297 303 # holds the reversed list of components making up the relative file
298 304 # name we want.
299 305 root_st = os.stat(root)
300 306 rel = []
301 307 while True:
302 308 try:
303 309 name_st = os.stat(name)
304 310 except OSError:
305 311 break
306 312 if util.samestat(name_st, root_st):
307 313 if not rel:
308 314 # name was actually the same as root (maybe a symlink)
309 315 return ''
310 316 rel.reverse()
311 317 name = os.path.join(*rel)
312 318 auditor(name)
313 319 return util.pconvert(name)
314 320 dirname, basename = os.path.split(name)
315 321 rel.append(basename)
316 322 if dirname == name:
317 323 break
318 324 name = dirname
319 325
320 326 raise util.Abort('%s not under root' % myname)
321 327
322 328 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
323 329 '''yield every hg repository under path, recursively.'''
324 330 def errhandler(err):
325 331 if err.filename == path:
326 332 raise err
327 333 samestat = getattr(os.path, 'samestat', None)
328 334 if followsym and samestat is not None:
329 335 def adddir(dirlst, dirname):
330 336 match = False
331 337 dirstat = os.stat(dirname)
332 338 for lstdirstat in dirlst:
333 339 if samestat(dirstat, lstdirstat):
334 340 match = True
335 341 break
336 342 if not match:
337 343 dirlst.append(dirstat)
338 344 return not match
339 345 else:
340 346 followsym = False
341 347
342 348 if (seen_dirs is None) and followsym:
343 349 seen_dirs = []
344 350 adddir(seen_dirs, path)
345 351 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
346 352 dirs.sort()
347 353 if '.hg' in dirs:
348 354 yield root # found a repository
349 355 qroot = os.path.join(root, '.hg', 'patches')
350 356 if os.path.isdir(os.path.join(qroot, '.hg')):
351 357 yield qroot # we have a patch queue repo here
352 358 if recurse:
353 359 # avoid recursing inside the .hg directory
354 360 dirs.remove('.hg')
355 361 else:
356 362 dirs[:] = [] # don't descend further
357 363 elif followsym:
358 364 newdirs = []
359 365 for d in dirs:
360 366 fname = os.path.join(root, d)
361 367 if adddir(seen_dirs, fname):
362 368 if os.path.islink(fname):
363 369 for hgname in walkrepos(fname, True, seen_dirs):
364 370 yield hgname
365 371 else:
366 372 newdirs.append(d)
367 373 dirs[:] = newdirs
368 374
369 375 def osrcpath():
370 376 '''return default os-specific hgrc search path'''
371 377 path = systemrcpath()
372 378 path.extend(userrcpath())
373 379 path = [os.path.normpath(f) for f in path]
374 380 return path
375 381
376 382 _rcpath = None
377 383
378 384 def rcpath():
379 385 '''return hgrc search path. if env var HGRCPATH is set, use it.
380 386 for each item in path, if directory, use files ending in .rc,
381 387 else use item.
382 388 make HGRCPATH empty to only look in .hg/hgrc of current repo.
383 389 if no HGRCPATH, use default os-specific path.'''
384 390 global _rcpath
385 391 if _rcpath is None:
386 392 if 'HGRCPATH' in os.environ:
387 393 _rcpath = []
388 394 for p in os.environ['HGRCPATH'].split(os.pathsep):
389 395 if not p:
390 396 continue
391 397 p = util.expandpath(p)
392 398 if os.path.isdir(p):
393 399 for f, kind in osutil.listdir(p):
394 400 if f.endswith('.rc'):
395 401 _rcpath.append(os.path.join(p, f))
396 402 else:
397 403 _rcpath.append(p)
398 404 else:
399 405 _rcpath = osrcpath()
400 406 return _rcpath
401 407
402 408 if os.name != 'nt':
403 409
404 410 def rcfiles(path):
405 411 rcs = [os.path.join(path, 'hgrc')]
406 412 rcdir = os.path.join(path, 'hgrc.d')
407 413 try:
408 414 rcs.extend([os.path.join(rcdir, f)
409 415 for f, kind in osutil.listdir(rcdir)
410 416 if f.endswith(".rc")])
411 417 except OSError:
412 418 pass
413 419 return rcs
414 420
415 421 def systemrcpath():
416 422 path = []
417 423 # old mod_python does not set sys.argv
418 424 if len(getattr(sys, 'argv', [])) > 0:
419 425 p = os.path.dirname(os.path.dirname(sys.argv[0]))
420 426 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
421 427 path.extend(rcfiles('/etc/mercurial'))
422 428 return path
423 429
424 430 def userrcpath():
425 431 return [os.path.expanduser('~/.hgrc')]
426 432
427 433 else:
428 434
429 435 _HKEY_LOCAL_MACHINE = 0x80000002L
430 436
431 437 def systemrcpath():
432 438 '''return default os-specific hgrc search path'''
433 439 rcpath = []
434 440 filename = util.executablepath()
435 441 # Use mercurial.ini found in directory with hg.exe
436 442 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
437 443 if os.path.isfile(progrc):
438 444 rcpath.append(progrc)
439 445 return rcpath
440 446 # Use hgrc.d found in directory with hg.exe
441 447 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
442 448 if os.path.isdir(progrcd):
443 449 for f, kind in osutil.listdir(progrcd):
444 450 if f.endswith('.rc'):
445 451 rcpath.append(os.path.join(progrcd, f))
446 452 return rcpath
447 453 # else look for a system rcpath in the registry
448 454 value = util.lookupreg('SOFTWARE\\Mercurial', None,
449 455 _HKEY_LOCAL_MACHINE)
450 456 if not isinstance(value, str) or not value:
451 457 return rcpath
452 458 value = value.replace('/', os.sep)
453 459 for p in value.split(os.pathsep):
454 460 if p.lower().endswith('mercurial.ini'):
455 461 rcpath.append(p)
456 462 elif os.path.isdir(p):
457 463 for f, kind in osutil.listdir(p):
458 464 if f.endswith('.rc'):
459 465 rcpath.append(os.path.join(p, f))
460 466 return rcpath
461 467
462 468 def userrcpath():
463 469 '''return os-specific hgrc search path to the user dir'''
464 470 home = os.path.expanduser('~')
465 471 path = [os.path.join(home, 'mercurial.ini'),
466 472 os.path.join(home, '.hgrc')]
467 473 userprofile = os.environ.get('USERPROFILE')
468 474 if userprofile:
469 475 path.append(os.path.join(userprofile, 'mercurial.ini'))
470 476 path.append(os.path.join(userprofile, '.hgrc'))
471 477 return path
472 478
473 479 def revsingle(repo, revspec, default='.'):
474 480 if not revspec:
475 481 return repo[default]
476 482
477 483 l = revrange(repo, [revspec])
478 484 if len(l) < 1:
479 485 raise util.Abort(_('empty revision set'))
480 486 return repo[l[-1]]
481 487
482 488 def revpair(repo, revs):
483 489 if not revs:
484 490 return repo.dirstate.p1(), None
485 491
486 492 l = revrange(repo, revs)
487 493
488 494 if len(l) == 0:
489 495 return repo.dirstate.p1(), None
490 496
491 497 if len(l) == 1:
492 498 return repo.lookup(l[0]), None
493 499
494 500 return repo.lookup(l[0]), repo.lookup(l[-1])
495 501
496 502 _revrangesep = ':'
497 503
498 504 def revrange(repo, revs):
499 505 """Yield revision as strings from a list of revision specifications."""
500 506
501 507 def revfix(repo, val, defval):
502 508 if not val and val != 0 and defval is not None:
503 509 return defval
504 510 return repo.changelog.rev(repo.lookup(val))
505 511
506 512 seen, l = set(), []
507 513 for spec in revs:
508 514 # attempt to parse old-style ranges first to deal with
509 515 # things like old-tag which contain query metacharacters
510 516 try:
511 517 if isinstance(spec, int):
512 518 seen.add(spec)
513 519 l.append(spec)
514 520 continue
515 521
516 522 if _revrangesep in spec:
517 523 start, end = spec.split(_revrangesep, 1)
518 524 start = revfix(repo, start, 0)
519 525 end = revfix(repo, end, len(repo) - 1)
520 526 step = start > end and -1 or 1
521 527 for rev in xrange(start, end + step, step):
522 528 if rev in seen:
523 529 continue
524 530 seen.add(rev)
525 531 l.append(rev)
526 532 continue
527 533 elif spec and spec in repo: # single unquoted rev
528 534 rev = revfix(repo, spec, None)
529 535 if rev in seen:
530 536 continue
531 537 seen.add(rev)
532 538 l.append(rev)
533 539 continue
534 540 except error.RepoLookupError:
535 541 pass
536 542
537 543 # fall through to new-style queries if old-style fails
538 544 m = revset.match(repo.ui, spec)
539 545 for r in m(repo, range(len(repo))):
540 546 if r not in seen:
541 547 l.append(r)
542 548 seen.update(l)
543 549
544 550 return l
545 551
546 552 def expandpats(pats):
547 553 if not util.expandglobs:
548 554 return list(pats)
549 555 ret = []
550 556 for p in pats:
551 557 kind, name = matchmod._patsplit(p, None)
552 558 if kind is None:
553 559 try:
554 560 globbed = glob.glob(name)
555 561 except re.error:
556 562 globbed = [name]
557 563 if globbed:
558 564 ret.extend(globbed)
559 565 continue
560 566 ret.append(p)
561 567 return ret
562 568
563 569 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
564 570 if pats == ("",):
565 571 pats = []
566 572 if not globbed and default == 'relpath':
567 573 pats = expandpats(pats or [])
568 574
569 575 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
570 576 default)
571 577 def badfn(f, msg):
572 578 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
573 579 m.bad = badfn
574 580 return m
575 581
576 582 def matchall(repo):
577 583 return matchmod.always(repo.root, repo.getcwd())
578 584
579 585 def matchfiles(repo, files):
580 586 return matchmod.exact(repo.root, repo.getcwd(), files)
581 587
582 588 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
583 589 if dry_run is None:
584 590 dry_run = opts.get('dry_run')
585 591 if similarity is None:
586 592 similarity = float(opts.get('similarity') or 0)
587 593 # we'd use status here, except handling of symlinks and ignore is tricky
588 594 added, unknown, deleted, removed = [], [], [], []
589 595 audit_path = pathauditor(repo.root)
590 596 m = match(repo[None], pats, opts)
591 597 for abs in repo.walk(m):
592 598 target = repo.wjoin(abs)
593 599 good = True
594 600 try:
595 601 audit_path(abs)
596 602 except (OSError, util.Abort):
597 603 good = False
598 604 rel = m.rel(abs)
599 605 exact = m.exact(abs)
600 606 if good and abs not in repo.dirstate:
601 607 unknown.append(abs)
602 608 if repo.ui.verbose or not exact:
603 609 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
604 610 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
605 611 or (os.path.isdir(target) and not os.path.islink(target))):
606 612 deleted.append(abs)
607 613 if repo.ui.verbose or not exact:
608 614 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
609 615 # for finding renames
610 616 elif repo.dirstate[abs] == 'r':
611 617 removed.append(abs)
612 618 elif repo.dirstate[abs] == 'a':
613 619 added.append(abs)
614 620 copies = {}
615 621 if similarity > 0:
616 622 for old, new, score in similar.findrenames(repo,
617 623 added + unknown, removed + deleted, similarity):
618 624 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
619 625 repo.ui.status(_('recording removal of %s as rename to %s '
620 626 '(%d%% similar)\n') %
621 627 (m.rel(old), m.rel(new), score * 100))
622 628 copies[new] = old
623 629
624 630 if not dry_run:
625 631 wctx = repo[None]
626 632 wlock = repo.wlock()
627 633 try:
628 634 wctx.forget(deleted)
629 635 wctx.add(unknown)
630 636 for new, old in copies.iteritems():
631 637 wctx.copy(old, new)
632 638 finally:
633 639 wlock.release()
634 640
635 641 def updatedir(ui, repo, patches, similarity=0):
636 642 '''Update dirstate after patch application according to metadata'''
637 643 if not patches:
638 644 return []
639 645 copies = []
640 646 removes = set()
641 647 cfiles = patches.keys()
642 648 cwd = repo.getcwd()
643 649 if cwd:
644 650 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
645 651 for f in patches:
646 652 gp = patches[f]
647 653 if not gp:
648 654 continue
649 655 if gp.op == 'RENAME':
650 656 copies.append((gp.oldpath, gp.path))
651 657 removes.add(gp.oldpath)
652 658 elif gp.op == 'COPY':
653 659 copies.append((gp.oldpath, gp.path))
654 660 elif gp.op == 'DELETE':
655 661 removes.add(gp.path)
656 662
657 663 wctx = repo[None]
658 664 for src, dst in copies:
659 665 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
660 666 if (not similarity) and removes:
661 667 wctx.remove(sorted(removes), True)
662 668
663 669 for f in patches:
664 670 gp = patches[f]
665 671 if gp and gp.mode:
666 672 islink, isexec = gp.mode
667 673 dst = repo.wjoin(gp.path)
668 674 # patch won't create empty files
669 675 if gp.op == 'ADD' and not os.path.lexists(dst):
670 676 flags = (isexec and 'x' or '') + (islink and 'l' or '')
671 677 repo.wwrite(gp.path, '', flags)
672 678 util.setflags(dst, islink, isexec)
673 679 addremove(repo, cfiles, similarity=similarity)
674 680 files = patches.keys()
675 681 files.extend([r for r in removes if r not in files])
676 682 return sorted(files)
677 683
678 684 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
679 685 """Update the dirstate to reflect the intent of copying src to dst. For
680 686 different reasons it might not end with dst being marked as copied from src.
681 687 """
682 688 origsrc = repo.dirstate.copied(src) or src
683 689 if dst == origsrc: # copying back a copy?
684 690 if repo.dirstate[dst] not in 'mn' and not dryrun:
685 691 repo.dirstate.normallookup(dst)
686 692 else:
687 693 if repo.dirstate[origsrc] == 'a' and origsrc == src:
688 694 if not ui.quiet:
689 695 ui.warn(_("%s has not been committed yet, so no copy "
690 696 "data will be stored for %s.\n")
691 697 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
692 698 if repo.dirstate[dst] in '?r' and not dryrun:
693 699 wctx.add([dst])
694 700 elif not dryrun:
695 701 wctx.copy(origsrc, dst)
696 702
697 703 def readrequires(opener, supported):
698 704 '''Reads and parses .hg/requires and checks if all entries found
699 705 are in the list of supported features.'''
700 706 requirements = set(opener.read("requires").splitlines())
701 707 missings = []
702 708 for r in requirements:
703 709 if r not in supported:
704 710 if not r or not r[0].isalnum():
705 711 raise error.RequirementError(_(".hg/requires file is corrupt"))
706 712 missings.append(r)
707 713 missings.sort()
708 714 if missings:
709 715 raise error.RequirementError(_("unknown repository format: "
710 716 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
711 717 return requirements
712 718
713 719 class filecacheentry(object):
714 720 def __init__(self, path):
715 721 self.path = path
716 722 self.cachestat = filecacheentry.stat(self.path)
717 723
718 724 if self.cachestat:
719 725 self._cacheable = self.cachestat.cacheable()
720 726 else:
721 727 # None means we don't know yet
722 728 self._cacheable = None
723 729
724 730 def refresh(self):
725 731 if self.cacheable():
726 732 self.cachestat = filecacheentry.stat(self.path)
727 733
728 734 def cacheable(self):
729 735 if self._cacheable is not None:
730 736 return self._cacheable
731 737
732 738 # we don't know yet, assume it is for now
733 739 return True
734 740
735 741 def changed(self):
736 742 # no point in going further if we can't cache it
737 743 if not self.cacheable():
738 744 return True
739 745
740 746 newstat = filecacheentry.stat(self.path)
741 747
742 748 # we may not know if it's cacheable yet, check again now
743 749 if newstat and self._cacheable is None:
744 750 self._cacheable = newstat.cacheable()
745 751
746 752 # check again
747 753 if not self._cacheable:
748 754 return True
749 755
750 756 if self.cachestat != newstat:
751 757 self.cachestat = newstat
752 758 return True
753 759 else:
754 760 return False
755 761
756 762 @staticmethod
757 763 def stat(path):
758 764 try:
759 765 return util.cachestat(path)
760 766 except OSError, e:
761 767 if e.errno != errno.ENOENT:
762 768 raise
763 769
764 770 class filecache(object):
765 771 '''A property like decorator that tracks a file under .hg/ for updates.
766 772
767 773 Records stat info when called in _filecache.
768 774
769 775 On subsequent calls, compares old stat info with new info, and recreates
770 776 the object when needed, updating the new stat info in _filecache.
771 777
772 778 Mercurial either atomic renames or appends for files under .hg,
773 779 so to ensure the cache is reliable we need the filesystem to be able
774 780 to tell us if a file has been replaced. If it can't, we fallback to
775 781 recreating the object on every call (essentially the same behaviour as
776 782 propertycache).'''
777 783 def __init__(self, path, instore=False):
778 784 self.path = path
779 785 self.instore = instore
780 786
781 787 def __call__(self, func):
782 788 self.func = func
783 789 self.name = func.__name__
784 790 return self
785 791
786 792 def __get__(self, obj, type=None):
787 793 entry = obj._filecache.get(self.name)
788 794
789 795 if entry:
790 796 if entry.changed():
791 797 entry.obj = self.func(obj)
792 798 else:
793 799 path = self.instore and obj.sjoin(self.path) or obj.join(self.path)
794 800
795 801 # We stat -before- creating the object so our cache doesn't lie if
796 802 # a writer modified between the time we read and stat
797 803 entry = filecacheentry(path)
798 804 entry.obj = self.func(obj)
799 805
800 806 obj._filecache[self.name] = entry
801 807
802 808 setattr(obj, self.name, entry.obj)
803 809 return entry.obj
General Comments 0
You need to be logged in to leave comments. Login now