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