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