##// END OF EJS Templates
dirstate: move file type filtering to its source...
Bryan O'Sullivan -
r18017:74912fe3 default
parent child Browse files
Show More
@@ -1,798 +1,795 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 import errno
8 8
9 9 from node import nullid
10 10 from i18n import _
11 11 import scmutil, util, ignore, osutil, parsers, encoding
12 12 import struct, os, stat, errno
13 13 import cStringIO
14 14
15 15 _format = ">cllll"
16 16 propertycache = util.propertycache
17 17 filecache = scmutil.filecache
18 18 _rangemask = 0x7fffffff
19 19
20 20 class repocache(filecache):
21 21 """filecache for files in .hg/"""
22 22 def join(self, obj, fname):
23 23 return obj._opener.join(fname)
24 24
25 25 class rootcache(filecache):
26 26 """filecache for files in the repository root"""
27 27 def join(self, obj, fname):
28 28 return obj._join(fname)
29 29
30 30 def _finddirs(path):
31 31 pos = path.rfind('/')
32 32 while pos != -1:
33 33 yield path[:pos]
34 34 pos = path.rfind('/', 0, pos)
35 35
36 36 def _incdirs(dirs, path):
37 37 for base in _finddirs(path):
38 38 if base in dirs:
39 39 dirs[base] += 1
40 40 return
41 41 dirs[base] = 1
42 42
43 43 def _decdirs(dirs, path):
44 44 for base in _finddirs(path):
45 45 if dirs[base] > 1:
46 46 dirs[base] -= 1
47 47 return
48 48 del dirs[base]
49 49
50 50 class dirstate(object):
51 51
52 52 def __init__(self, opener, ui, root, validate):
53 53 '''Create a new dirstate object.
54 54
55 55 opener is an open()-like callable that can be used to open the
56 56 dirstate file; root is the root of the directory tracked by
57 57 the dirstate.
58 58 '''
59 59 self._opener = opener
60 60 self._validate = validate
61 61 self._root = root
62 62 self._rootdir = os.path.join(root, '')
63 63 self._dirty = False
64 64 self._dirtypl = False
65 65 self._lastnormaltime = 0
66 66 self._ui = ui
67 67 self._filecache = {}
68 68
69 69 @propertycache
70 70 def _map(self):
71 71 '''Return the dirstate contents as a map from filename to
72 72 (state, mode, size, time).'''
73 73 self._read()
74 74 return self._map
75 75
76 76 @propertycache
77 77 def _copymap(self):
78 78 self._read()
79 79 return self._copymap
80 80
81 81 @propertycache
82 82 def _foldmap(self):
83 83 f = {}
84 84 for name in self._map:
85 85 f[util.normcase(name)] = name
86 86 for name in self._dirs:
87 87 f[util.normcase(name)] = name
88 88 f['.'] = '.' # prevents useless util.fspath() invocation
89 89 return f
90 90
91 91 @repocache('branch')
92 92 def _branch(self):
93 93 try:
94 94 return self._opener.read("branch").strip() or "default"
95 95 except IOError, inst:
96 96 if inst.errno != errno.ENOENT:
97 97 raise
98 98 return "default"
99 99
100 100 @propertycache
101 101 def _pl(self):
102 102 try:
103 103 fp = self._opener("dirstate")
104 104 st = fp.read(40)
105 105 fp.close()
106 106 l = len(st)
107 107 if l == 40:
108 108 return st[:20], st[20:40]
109 109 elif l > 0 and l < 40:
110 110 raise util.Abort(_('working directory state appears damaged!'))
111 111 except IOError, err:
112 112 if err.errno != errno.ENOENT:
113 113 raise
114 114 return [nullid, nullid]
115 115
116 116 @propertycache
117 117 def _dirs(self):
118 118 dirs = {}
119 119 for f, s in self._map.iteritems():
120 120 if s[0] != 'r':
121 121 _incdirs(dirs, f)
122 122 return dirs
123 123
124 124 def dirs(self):
125 125 return self._dirs
126 126
127 127 @rootcache('.hgignore')
128 128 def _ignore(self):
129 129 files = [self._join('.hgignore')]
130 130 for name, path in self._ui.configitems("ui"):
131 131 if name == 'ignore' or name.startswith('ignore.'):
132 132 files.append(util.expandpath(path))
133 133 return ignore.ignore(self._root, files, self._ui.warn)
134 134
135 135 @propertycache
136 136 def _slash(self):
137 137 return self._ui.configbool('ui', 'slash') and os.sep != '/'
138 138
139 139 @propertycache
140 140 def _checklink(self):
141 141 return util.checklink(self._root)
142 142
143 143 @propertycache
144 144 def _checkexec(self):
145 145 return util.checkexec(self._root)
146 146
147 147 @propertycache
148 148 def _checkcase(self):
149 149 return not util.checkcase(self._join('.hg'))
150 150
151 151 def _join(self, f):
152 152 # much faster than os.path.join()
153 153 # it's safe because f is always a relative path
154 154 return self._rootdir + f
155 155
156 156 def flagfunc(self, buildfallback):
157 157 if self._checklink and self._checkexec:
158 158 def f(x):
159 159 p = self._join(x)
160 160 if os.path.islink(p):
161 161 return 'l'
162 162 if util.isexec(p):
163 163 return 'x'
164 164 return ''
165 165 return f
166 166
167 167 fallback = buildfallback()
168 168 if self._checklink:
169 169 def f(x):
170 170 if os.path.islink(self._join(x)):
171 171 return 'l'
172 172 if 'x' in fallback(x):
173 173 return 'x'
174 174 return ''
175 175 return f
176 176 if self._checkexec:
177 177 def f(x):
178 178 if 'l' in fallback(x):
179 179 return 'l'
180 180 if util.isexec(self._join(x)):
181 181 return 'x'
182 182 return ''
183 183 return f
184 184 else:
185 185 return fallback
186 186
187 187 def getcwd(self):
188 188 cwd = os.getcwd()
189 189 if cwd == self._root:
190 190 return ''
191 191 # self._root ends with a path separator if self._root is '/' or 'C:\'
192 192 rootsep = self._root
193 193 if not util.endswithsep(rootsep):
194 194 rootsep += os.sep
195 195 if cwd.startswith(rootsep):
196 196 return cwd[len(rootsep):]
197 197 else:
198 198 # we're outside the repo. return an absolute path.
199 199 return cwd
200 200
201 201 def pathto(self, f, cwd=None):
202 202 if cwd is None:
203 203 cwd = self.getcwd()
204 204 path = util.pathto(self._root, cwd, f)
205 205 if self._slash:
206 206 return util.normpath(path)
207 207 return path
208 208
209 209 def __getitem__(self, key):
210 210 '''Return the current state of key (a filename) in the dirstate.
211 211
212 212 States are:
213 213 n normal
214 214 m needs merging
215 215 r marked for removal
216 216 a marked for addition
217 217 ? not tracked
218 218 '''
219 219 return self._map.get(key, ("?",))[0]
220 220
221 221 def __contains__(self, key):
222 222 return key in self._map
223 223
224 224 def __iter__(self):
225 225 for x in sorted(self._map):
226 226 yield x
227 227
228 228 def parents(self):
229 229 return [self._validate(p) for p in self._pl]
230 230
231 231 def p1(self):
232 232 return self._validate(self._pl[0])
233 233
234 234 def p2(self):
235 235 return self._validate(self._pl[1])
236 236
237 237 def branch(self):
238 238 return encoding.tolocal(self._branch)
239 239
240 240 def setparents(self, p1, p2=nullid):
241 241 """Set dirstate parents to p1 and p2.
242 242
243 243 When moving from two parents to one, 'm' merged entries a
244 244 adjusted to normal and previous copy records discarded and
245 245 returned by the call.
246 246
247 247 See localrepo.setparents()
248 248 """
249 249 self._dirty = self._dirtypl = True
250 250 oldp2 = self._pl[1]
251 251 self._pl = p1, p2
252 252 copies = {}
253 253 if oldp2 != nullid and p2 == nullid:
254 254 # Discard 'm' markers when moving away from a merge state
255 255 for f, s in self._map.iteritems():
256 256 if s[0] == 'm':
257 257 if f in self._copymap:
258 258 copies[f] = self._copymap[f]
259 259 self.normallookup(f)
260 260 return copies
261 261
262 262 def setbranch(self, branch):
263 263 self._branch = encoding.fromlocal(branch)
264 264 f = self._opener('branch', 'w', atomictemp=True)
265 265 try:
266 266 f.write(self._branch + '\n')
267 267 finally:
268 268 f.close()
269 269
270 270 def _read(self):
271 271 self._map = {}
272 272 self._copymap = {}
273 273 try:
274 274 st = self._opener.read("dirstate")
275 275 except IOError, err:
276 276 if err.errno != errno.ENOENT:
277 277 raise
278 278 return
279 279 if not st:
280 280 return
281 281
282 282 p = parsers.parse_dirstate(self._map, self._copymap, st)
283 283 if not self._dirtypl:
284 284 self._pl = p
285 285
286 286 def invalidate(self):
287 287 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
288 288 "_ignore"):
289 289 if a in self.__dict__:
290 290 delattr(self, a)
291 291 self._lastnormaltime = 0
292 292 self._dirty = False
293 293
294 294 def copy(self, source, dest):
295 295 """Mark dest as a copy of source. Unmark dest if source is None."""
296 296 if source == dest:
297 297 return
298 298 self._dirty = True
299 299 if source is not None:
300 300 self._copymap[dest] = source
301 301 elif dest in self._copymap:
302 302 del self._copymap[dest]
303 303
304 304 def copied(self, file):
305 305 return self._copymap.get(file, None)
306 306
307 307 def copies(self):
308 308 return self._copymap
309 309
310 310 def _droppath(self, f):
311 311 if self[f] not in "?r" and "_dirs" in self.__dict__:
312 312 _decdirs(self._dirs, f)
313 313
314 314 def _addpath(self, f, state, mode, size, mtime):
315 315 oldstate = self[f]
316 316 if state == 'a' or oldstate == 'r':
317 317 scmutil.checkfilename(f)
318 318 if f in self._dirs:
319 319 raise util.Abort(_('directory %r already in dirstate') % f)
320 320 # shadows
321 321 for d in _finddirs(f):
322 322 if d in self._dirs:
323 323 break
324 324 if d in self._map and self[d] != 'r':
325 325 raise util.Abort(
326 326 _('file %r in dirstate clashes with %r') % (d, f))
327 327 if oldstate in "?r" and "_dirs" in self.__dict__:
328 328 _incdirs(self._dirs, f)
329 329 self._dirty = True
330 330 self._map[f] = (state, mode, size, mtime)
331 331
332 332 def normal(self, f):
333 333 '''Mark a file normal and clean.'''
334 334 s = os.lstat(self._join(f))
335 335 mtime = int(s.st_mtime)
336 336 self._addpath(f, 'n', s.st_mode,
337 337 s.st_size & _rangemask, mtime & _rangemask)
338 338 if f in self._copymap:
339 339 del self._copymap[f]
340 340 if mtime > self._lastnormaltime:
341 341 # Remember the most recent modification timeslot for status(),
342 342 # to make sure we won't miss future size-preserving file content
343 343 # modifications that happen within the same timeslot.
344 344 self._lastnormaltime = mtime
345 345
346 346 def normallookup(self, f):
347 347 '''Mark a file normal, but possibly dirty.'''
348 348 if self._pl[1] != nullid and f in self._map:
349 349 # if there is a merge going on and the file was either
350 350 # in state 'm' (-1) or coming from other parent (-2) before
351 351 # being removed, restore that state.
352 352 entry = self._map[f]
353 353 if entry[0] == 'r' and entry[2] in (-1, -2):
354 354 source = self._copymap.get(f)
355 355 if entry[2] == -1:
356 356 self.merge(f)
357 357 elif entry[2] == -2:
358 358 self.otherparent(f)
359 359 if source:
360 360 self.copy(source, f)
361 361 return
362 362 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
363 363 return
364 364 self._addpath(f, 'n', 0, -1, -1)
365 365 if f in self._copymap:
366 366 del self._copymap[f]
367 367
368 368 def otherparent(self, f):
369 369 '''Mark as coming from the other parent, always dirty.'''
370 370 if self._pl[1] == nullid:
371 371 raise util.Abort(_("setting %r to other parent "
372 372 "only allowed in merges") % f)
373 373 self._addpath(f, 'n', 0, -2, -1)
374 374 if f in self._copymap:
375 375 del self._copymap[f]
376 376
377 377 def add(self, f):
378 378 '''Mark a file added.'''
379 379 self._addpath(f, 'a', 0, -1, -1)
380 380 if f in self._copymap:
381 381 del self._copymap[f]
382 382
383 383 def remove(self, f):
384 384 '''Mark a file removed.'''
385 385 self._dirty = True
386 386 self._droppath(f)
387 387 size = 0
388 388 if self._pl[1] != nullid and f in self._map:
389 389 # backup the previous state
390 390 entry = self._map[f]
391 391 if entry[0] == 'm': # merge
392 392 size = -1
393 393 elif entry[0] == 'n' and entry[2] == -2: # other parent
394 394 size = -2
395 395 self._map[f] = ('r', 0, size, 0)
396 396 if size == 0 and f in self._copymap:
397 397 del self._copymap[f]
398 398
399 399 def merge(self, f):
400 400 '''Mark a file merged.'''
401 401 if self._pl[1] == nullid:
402 402 return self.normallookup(f)
403 403 s = os.lstat(self._join(f))
404 404 self._addpath(f, 'm', s.st_mode,
405 405 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
406 406 if f in self._copymap:
407 407 del self._copymap[f]
408 408
409 409 def drop(self, f):
410 410 '''Drop a file from the dirstate'''
411 411 if f in self._map:
412 412 self._dirty = True
413 413 self._droppath(f)
414 414 del self._map[f]
415 415
416 416 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
417 417 normed = util.normcase(path)
418 418 folded = self._foldmap.get(normed, None)
419 419 if folded is None:
420 420 if isknown:
421 421 folded = path
422 422 else:
423 423 if exists is None:
424 424 exists = os.path.lexists(os.path.join(self._root, path))
425 425 if not exists:
426 426 # Maybe a path component exists
427 427 if not ignoremissing and '/' in path:
428 428 d, f = path.rsplit('/', 1)
429 429 d = self._normalize(d, isknown, ignoremissing, None)
430 430 folded = d + "/" + f
431 431 else:
432 432 # No path components, preserve original case
433 433 folded = path
434 434 else:
435 435 # recursively normalize leading directory components
436 436 # against dirstate
437 437 if '/' in normed:
438 438 d, f = normed.rsplit('/', 1)
439 439 d = self._normalize(d, isknown, ignoremissing, True)
440 440 r = self._root + "/" + d
441 441 folded = d + "/" + util.fspath(f, r)
442 442 else:
443 443 folded = util.fspath(normed, self._root)
444 444 self._foldmap[normed] = folded
445 445
446 446 return folded
447 447
448 448 def normalize(self, path, isknown=False, ignoremissing=False):
449 449 '''
450 450 normalize the case of a pathname when on a casefolding filesystem
451 451
452 452 isknown specifies whether the filename came from walking the
453 453 disk, to avoid extra filesystem access.
454 454
455 455 If ignoremissing is True, missing path are returned
456 456 unchanged. Otherwise, we try harder to normalize possibly
457 457 existing path components.
458 458
459 459 The normalized case is determined based on the following precedence:
460 460
461 461 - version of name already stored in the dirstate
462 462 - version of name stored on disk
463 463 - version provided via command arguments
464 464 '''
465 465
466 466 if self._checkcase:
467 467 return self._normalize(path, isknown, ignoremissing)
468 468 return path
469 469
470 470 def clear(self):
471 471 self._map = {}
472 472 if "_dirs" in self.__dict__:
473 473 delattr(self, "_dirs")
474 474 self._copymap = {}
475 475 self._pl = [nullid, nullid]
476 476 self._lastnormaltime = 0
477 477 self._dirty = True
478 478
479 479 def rebuild(self, parent, files):
480 480 self.clear()
481 481 for f in files:
482 482 if 'x' in files.flags(f):
483 483 self._map[f] = ('n', 0777, -1, 0)
484 484 else:
485 485 self._map[f] = ('n', 0666, -1, 0)
486 486 self._pl = (parent, nullid)
487 487 self._dirty = True
488 488
489 489 def write(self):
490 490 if not self._dirty:
491 491 return
492 492 st = self._opener("dirstate", "w", atomictemp=True)
493 493
494 494 def finish(s):
495 495 st.write(s)
496 496 st.close()
497 497 self._lastnormaltime = 0
498 498 self._dirty = self._dirtypl = False
499 499
500 500 # use the modification time of the newly created temporary file as the
501 501 # filesystem's notion of 'now'
502 502 now = util.fstat(st).st_mtime
503 503 copymap = self._copymap
504 504 try:
505 505 finish(parsers.pack_dirstate(self._map, copymap, self._pl, now))
506 506 return
507 507 except AttributeError:
508 508 pass
509 509
510 510 now = int(now)
511 511 cs = cStringIO.StringIO()
512 512 pack = struct.pack
513 513 write = cs.write
514 514 write("".join(self._pl))
515 515 for f, e in self._map.iteritems():
516 516 if e[0] == 'n' and e[3] == now:
517 517 # The file was last modified "simultaneously" with the current
518 518 # write to dirstate (i.e. within the same second for file-
519 519 # systems with a granularity of 1 sec). This commonly happens
520 520 # for at least a couple of files on 'update'.
521 521 # The user could change the file without changing its size
522 522 # within the same second. Invalidate the file's stat data in
523 523 # dirstate, forcing future 'status' calls to compare the
524 524 # contents of the file. This prevents mistakenly treating such
525 525 # files as clean.
526 526 e = (e[0], 0, -1, -1) # mark entry as 'unset'
527 527 self._map[f] = e
528 528
529 529 if f in copymap:
530 530 f = "%s\0%s" % (f, copymap[f])
531 531 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
532 532 write(e)
533 533 write(f)
534 534 finish(cs.getvalue())
535 535
536 536 def _dirignore(self, f):
537 537 if f == '.':
538 538 return False
539 539 if self._ignore(f):
540 540 return True
541 541 for p in _finddirs(f):
542 542 if self._ignore(p):
543 543 return True
544 544 return False
545 545
546 546 def walk(self, match, subrepos, unknown, ignored):
547 547 '''
548 548 Walk recursively through the directory tree, finding all files
549 549 matched by match.
550 550
551 551 Return a dict mapping filename to stat-like object (either
552 552 mercurial.osutil.stat instance or return value of os.stat()).
553 553 '''
554 554
555 555 def fwarn(f, msg):
556 556 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
557 557 return False
558 558
559 559 def badtype(mode):
560 560 kind = _('unknown')
561 561 if stat.S_ISCHR(mode):
562 562 kind = _('character device')
563 563 elif stat.S_ISBLK(mode):
564 564 kind = _('block device')
565 565 elif stat.S_ISFIFO(mode):
566 566 kind = _('fifo')
567 567 elif stat.S_ISSOCK(mode):
568 568 kind = _('socket')
569 569 elif stat.S_ISDIR(mode):
570 570 kind = _('directory')
571 571 return _('unsupported file type (type is %s)') % kind
572 572
573 573 ignore = self._ignore
574 574 dirignore = self._dirignore
575 575 if ignored:
576 576 ignore = util.never
577 577 dirignore = util.never
578 578 elif not unknown:
579 579 # if unknown and ignored are False, skip step 2
580 580 ignore = util.always
581 581 dirignore = util.always
582 582
583 583 matchfn = match.matchfn
584 584 badfn = match.bad
585 585 dmap = self._map
586 586 normpath = util.normpath
587 587 listdir = osutil.listdir
588 588 lstat = os.lstat
589 589 getkind = stat.S_IFMT
590 590 dirkind = stat.S_IFDIR
591 591 regkind = stat.S_IFREG
592 592 lnkkind = stat.S_IFLNK
593 593 join = self._join
594 594 work = []
595 595 wadd = work.append
596 596
597 597 exact = skipstep3 = False
598 598 if matchfn == match.exact: # match.exact
599 599 exact = True
600 600 dirignore = util.always # skip step 2
601 601 elif match.files() and not match.anypats(): # match.match, no patterns
602 602 skipstep3 = True
603 603
604 604 if not exact and self._checkcase:
605 605 normalize = self._normalize
606 606 skipstep3 = False
607 607 else:
608 608 normalize = lambda x, y, z: x
609 609
610 610 files = sorted(match.files())
611 611 subrepos.sort()
612 612 i, j = 0, 0
613 613 while i < len(files) and j < len(subrepos):
614 614 subpath = subrepos[j] + "/"
615 615 if files[i] < subpath:
616 616 i += 1
617 617 continue
618 618 while i < len(files) and files[i].startswith(subpath):
619 619 del files[i]
620 620 j += 1
621 621
622 622 if not files or '.' in files:
623 623 files = ['']
624 624 results = dict.fromkeys(subrepos)
625 625 results['.hg'] = None
626 626
627 627 # step 1: find all explicit files
628 628 for ff in files:
629 629 nf = normalize(normpath(ff), False, True)
630 630 if nf in results:
631 631 continue
632 632
633 633 try:
634 634 st = lstat(join(nf))
635 635 kind = getkind(st.st_mode)
636 636 if kind == dirkind:
637 637 skipstep3 = False
638 638 if nf in dmap:
639 639 #file deleted on disk but still in dirstate
640 640 results[nf] = None
641 641 match.dir(nf)
642 642 if not dirignore(nf):
643 643 wadd(nf)
644 644 elif kind == regkind or kind == lnkkind:
645 645 results[nf] = st
646 646 else:
647 647 badfn(ff, badtype(kind))
648 648 if nf in dmap:
649 649 results[nf] = None
650 650 except OSError, inst:
651 651 if nf in dmap: # does it exactly match a file?
652 652 results[nf] = None
653 653 else: # does it match a directory?
654 654 prefix = nf + "/"
655 655 for fn in dmap:
656 656 if fn.startswith(prefix):
657 657 match.dir(nf)
658 658 skipstep3 = False
659 659 break
660 660 else:
661 661 badfn(ff, inst.strerror)
662 662
663 663 # step 2: visit subdirectories
664 664 while work:
665 665 nd = work.pop()
666 666 skip = None
667 667 if nd == '.':
668 668 nd = ''
669 669 else:
670 670 skip = '.hg'
671 671 try:
672 672 entries = listdir(join(nd), stat=True, skip=skip)
673 673 except OSError, inst:
674 674 if inst.errno in (errno.EACCES, errno.ENOENT):
675 675 fwarn(nd, inst.strerror)
676 676 continue
677 677 raise
678 678 for f, kind, st in entries:
679 679 nf = normalize(nd and (nd + "/" + f) or f, True, True)
680 680 if nf not in results:
681 681 if kind == dirkind:
682 682 if not ignore(nf):
683 683 match.dir(nf)
684 684 wadd(nf)
685 685 if nf in dmap and matchfn(nf):
686 686 results[nf] = None
687 687 elif kind == regkind or kind == lnkkind:
688 688 if nf in dmap:
689 689 if matchfn(nf):
690 690 results[nf] = st
691 691 elif matchfn(nf) and not ignore(nf):
692 692 results[nf] = st
693 693 elif nf in dmap and matchfn(nf):
694 694 results[nf] = None
695 695
696 696 # step 3: report unseen items in the dmap hash
697 697 if not skipstep3 and not exact:
698 698 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
699 699 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
700 if (not st is None and
701 getkind(st.st_mode) not in (regkind, lnkkind)):
702 st = None
703 700 results[nf] = st
704 701 for s in subrepos:
705 702 del results[s]
706 703 del results['.hg']
707 704 return results
708 705
709 706 def status(self, match, subrepos, ignored, clean, unknown):
710 707 '''Determine the status of the working copy relative to the
711 708 dirstate and return a tuple of lists (unsure, modified, added,
712 709 removed, deleted, unknown, ignored, clean), where:
713 710
714 711 unsure:
715 712 files that might have been modified since the dirstate was
716 713 written, but need to be read to be sure (size is the same
717 714 but mtime differs)
718 715 modified:
719 716 files that have definitely been modified since the dirstate
720 717 was written (different size or mode)
721 718 added:
722 719 files that have been explicitly added with hg add
723 720 removed:
724 721 files that have been explicitly removed with hg remove
725 722 deleted:
726 723 files that have been deleted through other means ("missing")
727 724 unknown:
728 725 files not in the dirstate that are not ignored
729 726 ignored:
730 727 files not in the dirstate that are ignored
731 728 (by _dirignore())
732 729 clean:
733 730 files that have definitely not been modified since the
734 731 dirstate was written
735 732 '''
736 733 listignored, listclean, listunknown = ignored, clean, unknown
737 734 lookup, modified, added, unknown, ignored = [], [], [], [], []
738 735 removed, deleted, clean = [], [], []
739 736
740 737 dmap = self._map
741 738 ladd = lookup.append # aka "unsure"
742 739 madd = modified.append
743 740 aadd = added.append
744 741 uadd = unknown.append
745 742 iadd = ignored.append
746 743 radd = removed.append
747 744 dadd = deleted.append
748 745 cadd = clean.append
749 746
750 747 lnkkind = stat.S_IFLNK
751 748
752 749 for fn, st in self.walk(match, subrepos, listunknown,
753 750 listignored).iteritems():
754 751 if fn not in dmap:
755 752 if (listignored or match.exact(fn)) and self._dirignore(fn):
756 753 if listignored:
757 754 iadd(fn)
758 755 elif listunknown:
759 756 uadd(fn)
760 757 continue
761 758
762 759 state, mode, size, time = dmap[fn]
763 760
764 761 if not st and state in "nma":
765 762 dadd(fn)
766 763 elif state == 'n':
767 764 # The "mode & lnkkind != lnkkind or self._checklink"
768 765 # lines are an expansion of "islink => checklink"
769 766 # where islink means "is this a link?" and checklink
770 767 # means "can we check links?".
771 768 mtime = int(st.st_mtime)
772 769 if (size >= 0 and
773 770 ((size != st.st_size and size != st.st_size & _rangemask)
774 771 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
775 772 and (mode & lnkkind != lnkkind or self._checklink)
776 773 or size == -2 # other parent
777 774 or fn in self._copymap):
778 775 madd(fn)
779 776 elif ((time != mtime and time != mtime & _rangemask)
780 777 and (mode & lnkkind != lnkkind or self._checklink)):
781 778 ladd(fn)
782 779 elif mtime == self._lastnormaltime:
783 780 # fn may have been changed in the same timeslot without
784 781 # changing its size. This can happen if we quickly do
785 782 # multiple commits in a single transaction.
786 783 # Force lookup, so we don't miss such a racy file change.
787 784 ladd(fn)
788 785 elif listclean:
789 786 cadd(fn)
790 787 elif state == 'm':
791 788 madd(fn)
792 789 elif state == 'a':
793 790 aadd(fn)
794 791 elif state == 'r':
795 792 radd(fn)
796 793
797 794 return (lookup, modified, added, removed, deleted, unknown, ignored,
798 795 clean)
@@ -1,479 +1,485 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 encoding
10 10 import os, sys, errno, stat, getpass, pwd, grp, tempfile, unicodedata
11 11
12 12 posixfile = open
13 13 normpath = os.path.normpath
14 14 samestat = os.path.samestat
15 15 oslink = os.link
16 16 unlink = os.unlink
17 17 rename = os.rename
18 18 expandglobs = False
19 19
20 20 umask = os.umask(0)
21 21 os.umask(umask)
22 22
23 23 def split(p):
24 24 '''Same as os.path.split, but faster'''
25 25 ht = p.rsplit('/', 1)
26 26 if len(ht) == 1:
27 27 return '', p
28 28 nh = ht[0].rstrip('/')
29 29 if nh:
30 30 return nh, ht[1]
31 31 return ht
32 32
33 33 def openhardlinks():
34 34 '''return true if it is safe to hold open file handles to hardlinks'''
35 35 return True
36 36
37 37 def nlinks(name):
38 38 '''return number of hardlinks for the given file'''
39 39 return os.lstat(name).st_nlink
40 40
41 41 def parsepatchoutput(output_line):
42 42 """parses the output produced by patch and returns the filename"""
43 43 pf = output_line[14:]
44 44 if os.sys.platform == 'OpenVMS':
45 45 if pf[0] == '`':
46 46 pf = pf[1:-1] # Remove the quotes
47 47 else:
48 48 if pf.startswith("'") and pf.endswith("'") and " " in pf:
49 49 pf = pf[1:-1] # Remove the quotes
50 50 return pf
51 51
52 52 def sshargs(sshcmd, host, user, port):
53 53 '''Build argument list for ssh'''
54 54 args = user and ("%s@%s" % (user, host)) or host
55 55 return port and ("%s -p %s" % (args, port)) or args
56 56
57 57 def isexec(f):
58 58 """check whether a file is executable"""
59 59 return (os.lstat(f).st_mode & 0100 != 0)
60 60
61 61 def setflags(f, l, x):
62 62 s = os.lstat(f).st_mode
63 63 if l:
64 64 if not stat.S_ISLNK(s):
65 65 # switch file to link
66 66 fp = open(f)
67 67 data = fp.read()
68 68 fp.close()
69 69 os.unlink(f)
70 70 try:
71 71 os.symlink(data, f)
72 72 except OSError:
73 73 # failed to make a link, rewrite file
74 74 fp = open(f, "w")
75 75 fp.write(data)
76 76 fp.close()
77 77 # no chmod needed at this point
78 78 return
79 79 if stat.S_ISLNK(s):
80 80 # switch link to file
81 81 data = os.readlink(f)
82 82 os.unlink(f)
83 83 fp = open(f, "w")
84 84 fp.write(data)
85 85 fp.close()
86 86 s = 0666 & ~umask # avoid restatting for chmod
87 87
88 88 sx = s & 0100
89 89 if x and not sx:
90 90 # Turn on +x for every +r bit when making a file executable
91 91 # and obey umask.
92 92 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
93 93 elif not x and sx:
94 94 # Turn off all +x bits
95 95 os.chmod(f, s & 0666)
96 96
97 97 def copymode(src, dst, mode=None):
98 98 '''Copy the file mode from the file at path src to dst.
99 99 If src doesn't exist, we're using mode instead. If mode is None, we're
100 100 using umask.'''
101 101 try:
102 102 st_mode = os.lstat(src).st_mode & 0777
103 103 except OSError, inst:
104 104 if inst.errno != errno.ENOENT:
105 105 raise
106 106 st_mode = mode
107 107 if st_mode is None:
108 108 st_mode = ~umask
109 109 st_mode &= 0666
110 110 os.chmod(dst, st_mode)
111 111
112 112 def checkexec(path):
113 113 """
114 114 Check whether the given path is on a filesystem with UNIX-like exec flags
115 115
116 116 Requires a directory (like /foo/.hg)
117 117 """
118 118
119 119 # VFAT on some Linux versions can flip mode but it doesn't persist
120 120 # a FS remount. Frequently we can detect it if files are created
121 121 # with exec bit on.
122 122
123 123 try:
124 124 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
125 125 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
126 126 try:
127 127 os.close(fh)
128 128 m = os.stat(fn).st_mode & 0777
129 129 new_file_has_exec = m & EXECFLAGS
130 130 os.chmod(fn, m ^ EXECFLAGS)
131 131 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
132 132 finally:
133 133 os.unlink(fn)
134 134 except (IOError, OSError):
135 135 # we don't care, the user probably won't be able to commit anyway
136 136 return False
137 137 return not (new_file_has_exec or exec_flags_cannot_flip)
138 138
139 139 def checklink(path):
140 140 """check whether the given path is on a symlink-capable filesystem"""
141 141 # mktemp is not racy because symlink creation will fail if the
142 142 # file already exists
143 143 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
144 144 try:
145 145 os.symlink(".", name)
146 146 os.unlink(name)
147 147 return True
148 148 except (OSError, AttributeError):
149 149 return False
150 150
151 151 def checkosfilename(path):
152 152 '''Check that the base-relative path is a valid filename on this platform.
153 153 Returns None if the path is ok, or a UI string describing the problem.'''
154 154 pass # on posix platforms, every path is ok
155 155
156 156 def setbinary(fd):
157 157 pass
158 158
159 159 def pconvert(path):
160 160 return path
161 161
162 162 def localpath(path):
163 163 return path
164 164
165 165 def samefile(fpath1, fpath2):
166 166 """Returns whether path1 and path2 refer to the same file. This is only
167 167 guaranteed to work for files, not directories."""
168 168 return os.path.samefile(fpath1, fpath2)
169 169
170 170 def samedevice(fpath1, fpath2):
171 171 """Returns whether fpath1 and fpath2 are on the same device. This is only
172 172 guaranteed to work for files, not directories."""
173 173 st1 = os.lstat(fpath1)
174 174 st2 = os.lstat(fpath2)
175 175 return st1.st_dev == st2.st_dev
176 176
177 177 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
178 178 def normcase(path):
179 179 return path.lower()
180 180
181 181 if sys.platform == 'darwin':
182 182 import fcntl # only needed on darwin, missing on jython
183 183
184 184 def normcase(path):
185 185 try:
186 186 u = path.decode('utf-8')
187 187 except UnicodeDecodeError:
188 188 # percent-encode any characters that don't round-trip
189 189 p2 = path.decode('utf-8', 'ignore').encode('utf-8')
190 190 s = ""
191 191 pos = 0
192 192 for c in path:
193 193 if p2[pos:pos + 1] == c:
194 194 s += c
195 195 pos += 1
196 196 else:
197 197 s += "%%%02X" % ord(c)
198 198 u = s.decode('utf-8')
199 199
200 200 # Decompose then lowercase (HFS+ technote specifies lower)
201 201 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
202 202
203 203 def realpath(path):
204 204 '''
205 205 Returns the true, canonical file system path equivalent to the given
206 206 path.
207 207
208 208 Equivalent means, in this case, resulting in the same, unique
209 209 file system link to the path. Every file system entry, whether a file,
210 210 directory, hard link or symbolic link or special, will have a single
211 211 path preferred by the system, but may allow multiple, differing path
212 212 lookups to point to it.
213 213
214 214 Most regular UNIX file systems only allow a file system entry to be
215 215 looked up by its distinct path. Obviously, this does not apply to case
216 216 insensitive file systems, whether case preserving or not. The most
217 217 complex issue to deal with is file systems transparently reencoding the
218 218 path, such as the non-standard Unicode normalisation required for HFS+
219 219 and HFSX.
220 220 '''
221 221 # Constants copied from /usr/include/sys/fcntl.h
222 222 F_GETPATH = 50
223 223 O_SYMLINK = 0x200000
224 224
225 225 try:
226 226 fd = os.open(path, O_SYMLINK)
227 227 except OSError, err:
228 228 if err.errno == errno.ENOENT:
229 229 return path
230 230 raise
231 231
232 232 try:
233 233 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
234 234 finally:
235 235 os.close(fd)
236 236 elif sys.version_info < (2, 4, 2, 'final'):
237 237 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
238 238 # didn't resolve symlinks that were the first component of the path.)
239 239 def realpath(path):
240 240 if os.path.isabs(path):
241 241 return os.path.realpath(path)
242 242 else:
243 243 return os.path.realpath('./' + path)
244 244 else:
245 245 # Fallback to the likely inadequate Python builtin function.
246 246 realpath = os.path.realpath
247 247
248 248 if sys.platform == 'cygwin':
249 249 # workaround for cygwin, in which mount point part of path is
250 250 # treated as case sensitive, even though underlying NTFS is case
251 251 # insensitive.
252 252
253 253 # default mount points
254 254 cygwinmountpoints = sorted([
255 255 "/usr/bin",
256 256 "/usr/lib",
257 257 "/cygdrive",
258 258 ], reverse=True)
259 259
260 260 # use upper-ing as normcase as same as NTFS workaround
261 261 def normcase(path):
262 262 pathlen = len(path)
263 263 if (pathlen == 0) or (path[0] != os.sep):
264 264 # treat as relative
265 265 return encoding.upper(path)
266 266
267 267 # to preserve case of mountpoint part
268 268 for mp in cygwinmountpoints:
269 269 if not path.startswith(mp):
270 270 continue
271 271
272 272 mplen = len(mp)
273 273 if mplen == pathlen: # mount point itself
274 274 return mp
275 275 if path[mplen] == os.sep:
276 276 return mp + encoding.upper(path[mplen:])
277 277
278 278 return encoding.upper(path)
279 279
280 280 # Cygwin translates native ACLs to POSIX permissions,
281 281 # but these translations are not supported by native
282 282 # tools, so the exec bit tends to be set erroneously.
283 283 # Therefore, disable executable bit access on Cygwin.
284 284 def checkexec(path):
285 285 return False
286 286
287 287 # Similarly, Cygwin's symlink emulation is likely to create
288 288 # problems when Mercurial is used from both Cygwin and native
289 289 # Windows, with other native tools, or on shared volumes
290 290 def checklink(path):
291 291 return False
292 292
293 293 def shellquote(s):
294 294 if os.sys.platform == 'OpenVMS':
295 295 return '"%s"' % s
296 296 else:
297 297 return "'%s'" % s.replace("'", "'\\''")
298 298
299 299 def quotecommand(cmd):
300 300 return cmd
301 301
302 302 def popen(command, mode='r'):
303 303 return os.popen(command, mode)
304 304
305 305 def testpid(pid):
306 306 '''return False if pid dead, True if running or not sure'''
307 307 if os.sys.platform == 'OpenVMS':
308 308 return True
309 309 try:
310 310 os.kill(pid, 0)
311 311 return True
312 312 except OSError, inst:
313 313 return inst.errno != errno.ESRCH
314 314
315 315 def explainexit(code):
316 316 """return a 2-tuple (desc, code) describing a subprocess status
317 317 (codes from kill are negative - not os.system/wait encoding)"""
318 318 if code >= 0:
319 319 return _("exited with status %d") % code, code
320 320 return _("killed by signal %d") % -code, -code
321 321
322 322 def isowner(st):
323 323 """Return True if the stat object st is from the current user."""
324 324 return st.st_uid == os.getuid()
325 325
326 326 def findexe(command):
327 327 '''Find executable for command searching like which does.
328 328 If command is a basename then PATH is searched for command.
329 329 PATH isn't searched if command is an absolute or relative path.
330 330 If command isn't found None is returned.'''
331 331 if sys.platform == 'OpenVMS':
332 332 return command
333 333
334 334 def findexisting(executable):
335 335 'Will return executable if existing file'
336 336 if os.path.isfile(executable) and os.access(executable, os.X_OK):
337 337 return executable
338 338 return None
339 339
340 340 if os.sep in command:
341 341 return findexisting(command)
342 342
343 343 if sys.platform == 'plan9':
344 344 return findexisting(os.path.join('/bin', command))
345 345
346 346 for path in os.environ.get('PATH', '').split(os.pathsep):
347 347 executable = findexisting(os.path.join(path, command))
348 348 if executable is not None:
349 349 return executable
350 350 return None
351 351
352 352 def setsignalhandler():
353 353 pass
354 354
355 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
356
355 357 def statfiles(files):
356 'Stat each file in files and yield stat or None if file does not exist.'
358 '''Stat each file in files. Yield each stat, or None if a file does not
359 exist or has a type we don't care about.'''
357 360 lstat = os.lstat
361 getkind = stat.S_IFMT
358 362 for nf in files:
359 363 try:
360 364 st = lstat(nf)
365 if getkind(st.st_mode) not in _wantedkinds:
366 st = None
361 367 except OSError, err:
362 368 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
363 369 raise
364 370 st = None
365 371 yield st
366 372
367 373 def getuser():
368 374 '''return name of current user'''
369 375 return getpass.getuser()
370 376
371 377 def username(uid=None):
372 378 """Return the name of the user with the given uid.
373 379
374 380 If uid is None, return the name of the current user."""
375 381
376 382 if uid is None:
377 383 uid = os.getuid()
378 384 try:
379 385 return pwd.getpwuid(uid)[0]
380 386 except KeyError:
381 387 return str(uid)
382 388
383 389 def groupname(gid=None):
384 390 """Return the name of the group with the given gid.
385 391
386 392 If gid is None, return the name of the current group."""
387 393
388 394 if gid is None:
389 395 gid = os.getgid()
390 396 try:
391 397 return grp.getgrgid(gid)[0]
392 398 except KeyError:
393 399 return str(gid)
394 400
395 401 def groupmembers(name):
396 402 """Return the list of members of the group with the given
397 403 name, KeyError if the group does not exist.
398 404 """
399 405 return list(grp.getgrnam(name).gr_mem)
400 406
401 407 def spawndetached(args):
402 408 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
403 409 args[0], args)
404 410
405 411 def gethgcmd():
406 412 return sys.argv[:1]
407 413
408 414 def termwidth():
409 415 try:
410 416 import termios, array, fcntl
411 417 for dev in (sys.stderr, sys.stdout, sys.stdin):
412 418 try:
413 419 try:
414 420 fd = dev.fileno()
415 421 except AttributeError:
416 422 continue
417 423 if not os.isatty(fd):
418 424 continue
419 425 try:
420 426 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
421 427 width = array.array('h', arri)[1]
422 428 if width > 0:
423 429 return width
424 430 except AttributeError:
425 431 pass
426 432 except ValueError:
427 433 pass
428 434 except IOError, e:
429 435 if e[0] == errno.EINVAL:
430 436 pass
431 437 else:
432 438 raise
433 439 except ImportError:
434 440 pass
435 441 return 80
436 442
437 443 def makedir(path, notindexed):
438 444 os.mkdir(path)
439 445
440 446 def unlinkpath(f):
441 447 """unlink and remove the directory if it is empty"""
442 448 os.unlink(f)
443 449 # try removing directories that might now be empty
444 450 try:
445 451 os.removedirs(os.path.dirname(f))
446 452 except OSError:
447 453 pass
448 454
449 455 def lookupreg(key, name=None, scope=None):
450 456 return None
451 457
452 458 def hidewindow():
453 459 """Hide current shell window.
454 460
455 461 Used to hide the window opened when starting asynchronous
456 462 child process under Windows, unneeded on other systems.
457 463 """
458 464 pass
459 465
460 466 class cachestat(object):
461 467 def __init__(self, path):
462 468 self.stat = os.stat(path)
463 469
464 470 def cacheable(self):
465 471 return bool(self.stat.st_ino)
466 472
467 473 __hash__ = object.__hash__
468 474
469 475 def __eq__(self, other):
470 476 try:
471 477 return self.stat == other.stat
472 478 except AttributeError:
473 479 return False
474 480
475 481 def __ne__(self, other):
476 482 return not self == other
477 483
478 484 def executablepath():
479 485 return None # available on Windows only
@@ -1,329 +1,335 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil, encoding
10 import errno, msvcrt, os, re, sys, _winreg
10 import errno, msvcrt, os, re, stat, sys, _winreg
11 11
12 12 import win32
13 13 executablepath = win32.executablepath
14 14 getuser = win32.getuser
15 15 hidewindow = win32.hidewindow
16 16 makedir = win32.makedir
17 17 nlinks = win32.nlinks
18 18 oslink = win32.oslink
19 19 samedevice = win32.samedevice
20 20 samefile = win32.samefile
21 21 setsignalhandler = win32.setsignalhandler
22 22 spawndetached = win32.spawndetached
23 23 split = os.path.split
24 24 termwidth = win32.termwidth
25 25 testpid = win32.testpid
26 26 unlink = win32.unlink
27 27
28 28 umask = 0022
29 29
30 30 # wrap osutil.posixfile to provide friendlier exceptions
31 31 def posixfile(name, mode='r', buffering=-1):
32 32 try:
33 33 return osutil.posixfile(name, mode, buffering)
34 34 except WindowsError, err:
35 35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
36 36 posixfile.__doc__ = osutil.posixfile.__doc__
37 37
38 38 class winstdout(object):
39 39 '''stdout on windows misbehaves if sent through a pipe'''
40 40
41 41 def __init__(self, fp):
42 42 self.fp = fp
43 43
44 44 def __getattr__(self, key):
45 45 return getattr(self.fp, key)
46 46
47 47 def close(self):
48 48 try:
49 49 self.fp.close()
50 50 except IOError:
51 51 pass
52 52
53 53 def write(self, s):
54 54 try:
55 55 # This is workaround for "Not enough space" error on
56 56 # writing large size of data to console.
57 57 limit = 16000
58 58 l = len(s)
59 59 start = 0
60 60 self.softspace = 0
61 61 while start < l:
62 62 end = start + limit
63 63 self.fp.write(s[start:end])
64 64 start = end
65 65 except IOError, inst:
66 66 if inst.errno != 0:
67 67 raise
68 68 self.close()
69 69 raise IOError(errno.EPIPE, 'Broken pipe')
70 70
71 71 def flush(self):
72 72 try:
73 73 return self.fp.flush()
74 74 except IOError, inst:
75 75 if inst.errno != errno.EINVAL:
76 76 raise
77 77 self.close()
78 78 raise IOError(errno.EPIPE, 'Broken pipe')
79 79
80 80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
81 81
82 82 def _is_win_9x():
83 83 '''return true if run on windows 95, 98 or me.'''
84 84 try:
85 85 return sys.getwindowsversion()[3] == 1
86 86 except AttributeError:
87 87 return 'command' in os.environ.get('comspec', '')
88 88
89 89 def openhardlinks():
90 90 return not _is_win_9x()
91 91
92 92 def parsepatchoutput(output_line):
93 93 """parses the output produced by patch and returns the filename"""
94 94 pf = output_line[14:]
95 95 if pf[0] == '`':
96 96 pf = pf[1:-1] # Remove the quotes
97 97 return pf
98 98
99 99 def sshargs(sshcmd, host, user, port):
100 100 '''Build argument list for ssh or Plink'''
101 101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
102 102 args = user and ("%s@%s" % (user, host)) or host
103 103 return port and ("%s %s %s" % (args, pflag, port)) or args
104 104
105 105 def setflags(f, l, x):
106 106 pass
107 107
108 108 def copymode(src, dst, mode=None):
109 109 pass
110 110
111 111 def checkexec(path):
112 112 return False
113 113
114 114 def checklink(path):
115 115 return False
116 116
117 117 def setbinary(fd):
118 118 # When run without console, pipes may expose invalid
119 119 # fileno(), usually set to -1.
120 120 fno = getattr(fd, 'fileno', None)
121 121 if fno is not None and fno() >= 0:
122 122 msvcrt.setmode(fno(), os.O_BINARY)
123 123
124 124 def pconvert(path):
125 125 return path.replace(os.sep, '/')
126 126
127 127 def localpath(path):
128 128 return path.replace('/', '\\')
129 129
130 130 def normpath(path):
131 131 return pconvert(os.path.normpath(path))
132 132
133 133 def normcase(path):
134 134 return encoding.upper(path)
135 135
136 136 def realpath(path):
137 137 '''
138 138 Returns the true, canonical file system path equivalent to the given
139 139 path.
140 140 '''
141 141 # TODO: There may be a more clever way to do this that also handles other,
142 142 # less common file systems.
143 143 return os.path.normpath(normcase(os.path.realpath(path)))
144 144
145 145 def samestat(s1, s2):
146 146 return False
147 147
148 148 # A sequence of backslashes is special iff it precedes a double quote:
149 149 # - if there's an even number of backslashes, the double quote is not
150 150 # quoted (i.e. it ends the quoted region)
151 151 # - if there's an odd number of backslashes, the double quote is quoted
152 152 # - in both cases, every pair of backslashes is unquoted into a single
153 153 # backslash
154 154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
155 155 # So, to quote a string, we must surround it in double quotes, double
156 156 # the number of backslashes that precede double quotes and add another
157 157 # backslash before every double quote (being careful with the double
158 158 # quote we've appended to the end)
159 159 _quotere = None
160 160 def shellquote(s):
161 161 global _quotere
162 162 if _quotere is None:
163 163 _quotere = re.compile(r'(\\*)("|\\$)')
164 164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
165 165
166 166 def quotecommand(cmd):
167 167 """Build a command string suitable for os.popen* calls."""
168 168 if sys.version_info < (2, 7, 1):
169 169 # Python versions since 2.7.1 do this extra quoting themselves
170 170 return '"' + cmd + '"'
171 171 return cmd
172 172
173 173 def popen(command, mode='r'):
174 174 # Work around "popen spawned process may not write to stdout
175 175 # under windows"
176 176 # http://bugs.python.org/issue1366
177 177 command += " 2> %s" % os.devnull
178 178 return os.popen(quotecommand(command), mode)
179 179
180 180 def explainexit(code):
181 181 return _("exited with status %d") % code, code
182 182
183 183 # if you change this stub into a real check, please try to implement the
184 184 # username and groupname functions above, too.
185 185 def isowner(st):
186 186 return True
187 187
188 188 def findexe(command):
189 189 '''Find executable for command searching like cmd.exe does.
190 190 If command is a basename then PATH is searched for command.
191 191 PATH isn't searched if command is an absolute or relative path.
192 192 An extension from PATHEXT is found and added if not present.
193 193 If command isn't found None is returned.'''
194 194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
195 195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
196 196 if os.path.splitext(command)[1].lower() in pathexts:
197 197 pathexts = ['']
198 198
199 199 def findexisting(pathcommand):
200 200 'Will append extension (if needed) and return existing file'
201 201 for ext in pathexts:
202 202 executable = pathcommand + ext
203 203 if os.path.exists(executable):
204 204 return executable
205 205 return None
206 206
207 207 if os.sep in command:
208 208 return findexisting(command)
209 209
210 210 for path in os.environ.get('PATH', '').split(os.pathsep):
211 211 executable = findexisting(os.path.join(path, command))
212 212 if executable is not None:
213 213 return executable
214 214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
215 215
216 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
217
216 218 def statfiles(files):
217 '''Stat each file in files and yield stat or None if file does not exist.
219 '''Stat each file in files. Yield each stat, or None if a file
220 does not exist or has a type we don't care about.
221
218 222 Cluster and cache stat per directory to minimize number of OS stat calls.'''
219 223 dircache = {} # dirname -> filename -> status | None if file does not exist
224 getkind = stat.S_IFMT
220 225 for nf in files:
221 226 nf = normcase(nf)
222 227 dir, base = os.path.split(nf)
223 228 if not dir:
224 229 dir = '.'
225 230 cache = dircache.get(dir, None)
226 231 if cache is None:
227 232 try:
228 233 dmap = dict([(normcase(n), s)
229 for n, k, s in osutil.listdir(dir, True)])
234 for n, k, s in osutil.listdir(dir, True)
235 if getkind(s) in _wantedkinds])
230 236 except OSError, err:
231 237 # handle directory not found in Python version prior to 2.5
232 238 # Python <= 2.4 returns native Windows code 3 in errno
233 239 # Python >= 2.5 returns ENOENT and adds winerror field
234 240 # EINVAL is raised if dir is not a directory.
235 241 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 242 errno.ENOTDIR):
237 243 raise
238 244 dmap = {}
239 245 cache = dircache.setdefault(dir, dmap)
240 246 yield cache.get(base, None)
241 247
242 248 def username(uid=None):
243 249 """Return the name of the user with the given uid.
244 250
245 251 If uid is None, return the name of the current user."""
246 252 return None
247 253
248 254 def groupname(gid=None):
249 255 """Return the name of the group with the given gid.
250 256
251 257 If gid is None, return the name of the current group."""
252 258 return None
253 259
254 260 def _removedirs(name):
255 261 """special version of os.removedirs that does not remove symlinked
256 262 directories or junction points if they actually contain files"""
257 263 if osutil.listdir(name):
258 264 return
259 265 os.rmdir(name)
260 266 head, tail = os.path.split(name)
261 267 if not tail:
262 268 head, tail = os.path.split(head)
263 269 while head and tail:
264 270 try:
265 271 if osutil.listdir(head):
266 272 return
267 273 os.rmdir(head)
268 274 except (ValueError, OSError):
269 275 break
270 276 head, tail = os.path.split(head)
271 277
272 278 def unlinkpath(f):
273 279 """unlink and remove the directory if it is empty"""
274 280 unlink(f)
275 281 # try removing directories that might now be empty
276 282 try:
277 283 _removedirs(os.path.dirname(f))
278 284 except OSError:
279 285 pass
280 286
281 287 def rename(src, dst):
282 288 '''atomically rename file src to dst, replacing dst if it exists'''
283 289 try:
284 290 os.rename(src, dst)
285 291 except OSError, e:
286 292 if e.errno != errno.EEXIST:
287 293 raise
288 294 unlink(dst)
289 295 os.rename(src, dst)
290 296
291 297 def gethgcmd():
292 298 return [sys.executable] + sys.argv[:1]
293 299
294 300 def groupmembers(name):
295 301 # Don't support groups on Windows for now
296 302 raise KeyError
297 303
298 304 def isexec(f):
299 305 return False
300 306
301 307 class cachestat(object):
302 308 def __init__(self, path):
303 309 pass
304 310
305 311 def cacheable(self):
306 312 return False
307 313
308 314 def lookupreg(key, valname=None, scope=None):
309 315 ''' Look up a key/value name in the Windows registry.
310 316
311 317 valname: value name. If unspecified, the default value for the key
312 318 is used.
313 319 scope: optionally specify scope for registry lookup, this can be
314 320 a sequence of scopes to look up in order. Default (CURRENT_USER,
315 321 LOCAL_MACHINE).
316 322 '''
317 323 if scope is None:
318 324 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
319 325 elif not isinstance(scope, (list, tuple)):
320 326 scope = (scope,)
321 327 for s in scope:
322 328 try:
323 329 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
324 330 # never let a Unicode string escape into the wild
325 331 return encoding.tolocal(val.encode('UTF-8'))
326 332 except EnvironmentError:
327 333 pass
328 334
329 335 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now