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