##// END OF EJS Templates
move checkfilename from util to scmutil...
Adrian Buehlmann -
r13974:23f2736a default
parent child Browse files
Show More
@@ -1,724 +1,724 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
8 8 from node import nullid
9 9 from i18n import _
10 import util, ignore, osutil, parsers, encoding
10 import scmutil, util, ignore, osutil, parsers, encoding
11 11 import struct, os, stat, errno
12 12 import cStringIO
13 13
14 14 _format = ">cllll"
15 15 propertycache = util.propertycache
16 16
17 17 def _finddirs(path):
18 18 pos = path.rfind('/')
19 19 while pos != -1:
20 20 yield path[:pos]
21 21 pos = path.rfind('/', 0, pos)
22 22
23 23 def _incdirs(dirs, path):
24 24 for base in _finddirs(path):
25 25 if base in dirs:
26 26 dirs[base] += 1
27 27 return
28 28 dirs[base] = 1
29 29
30 30 def _decdirs(dirs, path):
31 31 for base in _finddirs(path):
32 32 if dirs[base] > 1:
33 33 dirs[base] -= 1
34 34 return
35 35 del dirs[base]
36 36
37 37 class dirstate(object):
38 38
39 39 def __init__(self, opener, ui, root, validate):
40 40 '''Create a new dirstate object.
41 41
42 42 opener is an open()-like callable that can be used to open the
43 43 dirstate file; root is the root of the directory tracked by
44 44 the dirstate.
45 45 '''
46 46 self._opener = opener
47 47 self._validate = validate
48 48 self._root = root
49 49 self._rootdir = os.path.join(root, '')
50 50 self._dirty = False
51 51 self._dirtypl = False
52 52 self._lastnormaltime = None
53 53 self._ui = ui
54 54
55 55 @propertycache
56 56 def _map(self):
57 57 '''Return the dirstate contents as a map from filename to
58 58 (state, mode, size, time).'''
59 59 self._read()
60 60 return self._map
61 61
62 62 @propertycache
63 63 def _copymap(self):
64 64 self._read()
65 65 return self._copymap
66 66
67 67 @propertycache
68 68 def _foldmap(self):
69 69 f = {}
70 70 for name in self._map:
71 71 f[os.path.normcase(name)] = name
72 72 return f
73 73
74 74 @propertycache
75 75 def _branch(self):
76 76 try:
77 77 return self._opener("branch").read().strip() or "default"
78 78 except IOError:
79 79 return "default"
80 80
81 81 @propertycache
82 82 def _pl(self):
83 83 try:
84 84 fp = self._opener("dirstate")
85 85 st = fp.read(40)
86 86 fp.close()
87 87 l = len(st)
88 88 if l == 40:
89 89 return st[:20], st[20:40]
90 90 elif l > 0 and l < 40:
91 91 raise util.Abort(_('working directory state appears damaged!'))
92 92 except IOError, err:
93 93 if err.errno != errno.ENOENT:
94 94 raise
95 95 return [nullid, nullid]
96 96
97 97 @propertycache
98 98 def _dirs(self):
99 99 dirs = {}
100 100 for f, s in self._map.iteritems():
101 101 if s[0] != 'r':
102 102 _incdirs(dirs, f)
103 103 return dirs
104 104
105 105 @propertycache
106 106 def _ignore(self):
107 107 files = [self._join('.hgignore')]
108 108 for name, path in self._ui.configitems("ui"):
109 109 if name == 'ignore' or name.startswith('ignore.'):
110 110 files.append(util.expandpath(path))
111 111 return ignore.ignore(self._root, files, self._ui.warn)
112 112
113 113 @propertycache
114 114 def _slash(self):
115 115 return self._ui.configbool('ui', 'slash') and os.sep != '/'
116 116
117 117 @propertycache
118 118 def _checklink(self):
119 119 return util.checklink(self._root)
120 120
121 121 @propertycache
122 122 def _checkexec(self):
123 123 return util.checkexec(self._root)
124 124
125 125 @propertycache
126 126 def _checkcase(self):
127 127 return not util.checkcase(self._join('.hg'))
128 128
129 129 def _join(self, f):
130 130 # much faster than os.path.join()
131 131 # it's safe because f is always a relative path
132 132 return self._rootdir + f
133 133
134 134 def flagfunc(self, fallback):
135 135 if self._checklink:
136 136 if self._checkexec:
137 137 def f(x):
138 138 p = self._join(x)
139 139 if os.path.islink(p):
140 140 return 'l'
141 141 if util.is_exec(p):
142 142 return 'x'
143 143 return ''
144 144 return f
145 145 def f(x):
146 146 if os.path.islink(self._join(x)):
147 147 return 'l'
148 148 if 'x' in fallback(x):
149 149 return 'x'
150 150 return ''
151 151 return f
152 152 if self._checkexec:
153 153 def f(x):
154 154 if 'l' in fallback(x):
155 155 return 'l'
156 156 if util.is_exec(self._join(x)):
157 157 return 'x'
158 158 return ''
159 159 return f
160 160 return fallback
161 161
162 162 def getcwd(self):
163 163 cwd = os.getcwd()
164 164 if cwd == self._root:
165 165 return ''
166 166 # self._root ends with a path separator if self._root is '/' or 'C:\'
167 167 rootsep = self._root
168 168 if not util.endswithsep(rootsep):
169 169 rootsep += os.sep
170 170 if cwd.startswith(rootsep):
171 171 return cwd[len(rootsep):]
172 172 else:
173 173 # we're outside the repo. return an absolute path.
174 174 return cwd
175 175
176 176 def pathto(self, f, cwd=None):
177 177 if cwd is None:
178 178 cwd = self.getcwd()
179 179 path = util.pathto(self._root, cwd, f)
180 180 if self._slash:
181 181 return util.normpath(path)
182 182 return path
183 183
184 184 def __getitem__(self, key):
185 185 '''Return the current state of key (a filename) in the dirstate.
186 186
187 187 States are:
188 188 n normal
189 189 m needs merging
190 190 r marked for removal
191 191 a marked for addition
192 192 ? not tracked
193 193 '''
194 194 return self._map.get(key, ("?",))[0]
195 195
196 196 def __contains__(self, key):
197 197 return key in self._map
198 198
199 199 def __iter__(self):
200 200 for x in sorted(self._map):
201 201 yield x
202 202
203 203 def parents(self):
204 204 return [self._validate(p) for p in self._pl]
205 205
206 206 def p1(self):
207 207 return self._validate(self._pl[0])
208 208
209 209 def p2(self):
210 210 return self._validate(self._pl[1])
211 211
212 212 def branch(self):
213 213 return encoding.tolocal(self._branch)
214 214
215 215 def setparents(self, p1, p2=nullid):
216 216 self._dirty = self._dirtypl = True
217 217 self._pl = p1, p2
218 218
219 219 def setbranch(self, branch):
220 220 if branch in ['tip', '.', 'null']:
221 221 raise util.Abort(_('the name \'%s\' is reserved') % branch)
222 222 self._branch = encoding.fromlocal(branch)
223 223 self._opener("branch", "w").write(self._branch + '\n')
224 224
225 225 def _read(self):
226 226 self._map = {}
227 227 self._copymap = {}
228 228 try:
229 229 st = self._opener("dirstate").read()
230 230 except IOError, err:
231 231 if err.errno != errno.ENOENT:
232 232 raise
233 233 return
234 234 if not st:
235 235 return
236 236
237 237 p = parsers.parse_dirstate(self._map, self._copymap, st)
238 238 if not self._dirtypl:
239 239 self._pl = p
240 240
241 241 def invalidate(self):
242 242 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
243 243 "_ignore"):
244 244 if a in self.__dict__:
245 245 delattr(self, a)
246 246 self._lastnormaltime = None
247 247 self._dirty = False
248 248
249 249 def copy(self, source, dest):
250 250 """Mark dest as a copy of source. Unmark dest if source is None."""
251 251 if source == dest:
252 252 return
253 253 self._dirty = True
254 254 if source is not None:
255 255 self._copymap[dest] = source
256 256 elif dest in self._copymap:
257 257 del self._copymap[dest]
258 258
259 259 def copied(self, file):
260 260 return self._copymap.get(file, None)
261 261
262 262 def copies(self):
263 263 return self._copymap
264 264
265 265 def _droppath(self, f):
266 266 if self[f] not in "?r" and "_dirs" in self.__dict__:
267 267 _decdirs(self._dirs, f)
268 268
269 269 def _addpath(self, f, check=False):
270 270 oldstate = self[f]
271 271 if check or oldstate == "r":
272 util.checkfilename(f)
272 scmutil.checkfilename(f)
273 273 if f in self._dirs:
274 274 raise util.Abort(_('directory %r already in dirstate') % f)
275 275 # shadows
276 276 for d in _finddirs(f):
277 277 if d in self._dirs:
278 278 break
279 279 if d in self._map and self[d] != 'r':
280 280 raise util.Abort(
281 281 _('file %r in dirstate clashes with %r') % (d, f))
282 282 if oldstate in "?r" and "_dirs" in self.__dict__:
283 283 _incdirs(self._dirs, f)
284 284
285 285 def normal(self, f):
286 286 '''Mark a file normal and clean.'''
287 287 self._dirty = True
288 288 self._addpath(f)
289 289 s = os.lstat(self._join(f))
290 290 mtime = int(s.st_mtime)
291 291 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
292 292 if f in self._copymap:
293 293 del self._copymap[f]
294 294 if mtime > self._lastnormaltime:
295 295 # Remember the most recent modification timeslot for status(),
296 296 # to make sure we won't miss future size-preserving file content
297 297 # modifications that happen within the same timeslot.
298 298 self._lastnormaltime = mtime
299 299
300 300 def normallookup(self, f):
301 301 '''Mark a file normal, but possibly dirty.'''
302 302 if self._pl[1] != nullid and f in self._map:
303 303 # if there is a merge going on and the file was either
304 304 # in state 'm' (-1) or coming from other parent (-2) before
305 305 # being removed, restore that state.
306 306 entry = self._map[f]
307 307 if entry[0] == 'r' and entry[2] in (-1, -2):
308 308 source = self._copymap.get(f)
309 309 if entry[2] == -1:
310 310 self.merge(f)
311 311 elif entry[2] == -2:
312 312 self.otherparent(f)
313 313 if source:
314 314 self.copy(source, f)
315 315 return
316 316 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
317 317 return
318 318 self._dirty = True
319 319 self._addpath(f)
320 320 self._map[f] = ('n', 0, -1, -1)
321 321 if f in self._copymap:
322 322 del self._copymap[f]
323 323
324 324 def otherparent(self, f):
325 325 '''Mark as coming from the other parent, always dirty.'''
326 326 if self._pl[1] == nullid:
327 327 raise util.Abort(_("setting %r to other parent "
328 328 "only allowed in merges") % f)
329 329 self._dirty = True
330 330 self._addpath(f)
331 331 self._map[f] = ('n', 0, -2, -1)
332 332 if f in self._copymap:
333 333 del self._copymap[f]
334 334
335 335 def add(self, f):
336 336 '''Mark a file added.'''
337 337 self._dirty = True
338 338 self._addpath(f, True)
339 339 self._map[f] = ('a', 0, -1, -1)
340 340 if f in self._copymap:
341 341 del self._copymap[f]
342 342
343 343 def remove(self, f):
344 344 '''Mark a file removed.'''
345 345 self._dirty = True
346 346 self._droppath(f)
347 347 size = 0
348 348 if self._pl[1] != nullid and f in self._map:
349 349 # backup the previous state
350 350 entry = self._map[f]
351 351 if entry[0] == 'm': # merge
352 352 size = -1
353 353 elif entry[0] == 'n' and entry[2] == -2: # other parent
354 354 size = -2
355 355 self._map[f] = ('r', 0, size, 0)
356 356 if size == 0 and f in self._copymap:
357 357 del self._copymap[f]
358 358
359 359 def merge(self, f):
360 360 '''Mark a file merged.'''
361 361 self._dirty = True
362 362 s = os.lstat(self._join(f))
363 363 self._addpath(f)
364 364 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
365 365 if f in self._copymap:
366 366 del self._copymap[f]
367 367
368 368 def forget(self, f):
369 369 '''Forget a file.'''
370 370 self._dirty = True
371 371 try:
372 372 self._droppath(f)
373 373 del self._map[f]
374 374 except KeyError:
375 375 self._ui.warn(_("not in dirstate: %s\n") % f)
376 376
377 377 def _normalize(self, path, isknown):
378 378 normed = os.path.normcase(path)
379 379 folded = self._foldmap.get(normed, None)
380 380 if folded is None:
381 381 if isknown or not os.path.lexists(os.path.join(self._root, path)):
382 382 folded = path
383 383 else:
384 384 folded = self._foldmap.setdefault(normed,
385 385 util.fspath(path, self._root))
386 386 return folded
387 387
388 388 def normalize(self, path, isknown=False):
389 389 '''
390 390 normalize the case of a pathname when on a casefolding filesystem
391 391
392 392 isknown specifies whether the filename came from walking the
393 393 disk, to avoid extra filesystem access
394 394
395 395 The normalized case is determined based on the following precedence:
396 396
397 397 - version of name already stored in the dirstate
398 398 - version of name stored on disk
399 399 - version provided via command arguments
400 400 '''
401 401
402 402 if self._checkcase:
403 403 return self._normalize(path, isknown)
404 404 return path
405 405
406 406 def clear(self):
407 407 self._map = {}
408 408 if "_dirs" in self.__dict__:
409 409 delattr(self, "_dirs")
410 410 self._copymap = {}
411 411 self._pl = [nullid, nullid]
412 412 self._lastnormaltime = None
413 413 self._dirty = True
414 414
415 415 def rebuild(self, parent, files):
416 416 self.clear()
417 417 for f in files:
418 418 if 'x' in files.flags(f):
419 419 self._map[f] = ('n', 0777, -1, 0)
420 420 else:
421 421 self._map[f] = ('n', 0666, -1, 0)
422 422 self._pl = (parent, nullid)
423 423 self._dirty = True
424 424
425 425 def write(self):
426 426 if not self._dirty:
427 427 return
428 428 st = self._opener("dirstate", "w", atomictemp=True)
429 429
430 430 # use the modification time of the newly created temporary file as the
431 431 # filesystem's notion of 'now'
432 432 now = int(util.fstat(st).st_mtime)
433 433
434 434 cs = cStringIO.StringIO()
435 435 copymap = self._copymap
436 436 pack = struct.pack
437 437 write = cs.write
438 438 write("".join(self._pl))
439 439 for f, e in self._map.iteritems():
440 440 if e[0] == 'n' and e[3] == now:
441 441 # The file was last modified "simultaneously" with the current
442 442 # write to dirstate (i.e. within the same second for file-
443 443 # systems with a granularity of 1 sec). This commonly happens
444 444 # for at least a couple of files on 'update'.
445 445 # The user could change the file without changing its size
446 446 # within the same second. Invalidate the file's stat data in
447 447 # dirstate, forcing future 'status' calls to compare the
448 448 # contents of the file. This prevents mistakenly treating such
449 449 # files as clean.
450 450 e = (e[0], 0, -1, -1) # mark entry as 'unset'
451 451 self._map[f] = e
452 452
453 453 if f in copymap:
454 454 f = "%s\0%s" % (f, copymap[f])
455 455 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
456 456 write(e)
457 457 write(f)
458 458 st.write(cs.getvalue())
459 459 st.rename()
460 460 self._lastnormaltime = None
461 461 self._dirty = self._dirtypl = False
462 462
463 463 def _dirignore(self, f):
464 464 if f == '.':
465 465 return False
466 466 if self._ignore(f):
467 467 return True
468 468 for p in _finddirs(f):
469 469 if self._ignore(p):
470 470 return True
471 471 return False
472 472
473 473 def walk(self, match, subrepos, unknown, ignored):
474 474 '''
475 475 Walk recursively through the directory tree, finding all files
476 476 matched by match.
477 477
478 478 Return a dict mapping filename to stat-like object (either
479 479 mercurial.osutil.stat instance or return value of os.stat()).
480 480 '''
481 481
482 482 def fwarn(f, msg):
483 483 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
484 484 return False
485 485
486 486 def badtype(mode):
487 487 kind = _('unknown')
488 488 if stat.S_ISCHR(mode):
489 489 kind = _('character device')
490 490 elif stat.S_ISBLK(mode):
491 491 kind = _('block device')
492 492 elif stat.S_ISFIFO(mode):
493 493 kind = _('fifo')
494 494 elif stat.S_ISSOCK(mode):
495 495 kind = _('socket')
496 496 elif stat.S_ISDIR(mode):
497 497 kind = _('directory')
498 498 return _('unsupported file type (type is %s)') % kind
499 499
500 500 ignore = self._ignore
501 501 dirignore = self._dirignore
502 502 if ignored:
503 503 ignore = util.never
504 504 dirignore = util.never
505 505 elif not unknown:
506 506 # if unknown and ignored are False, skip step 2
507 507 ignore = util.always
508 508 dirignore = util.always
509 509
510 510 matchfn = match.matchfn
511 511 badfn = match.bad
512 512 dmap = self._map
513 513 normpath = util.normpath
514 514 listdir = osutil.listdir
515 515 lstat = os.lstat
516 516 getkind = stat.S_IFMT
517 517 dirkind = stat.S_IFDIR
518 518 regkind = stat.S_IFREG
519 519 lnkkind = stat.S_IFLNK
520 520 join = self._join
521 521 work = []
522 522 wadd = work.append
523 523
524 524 exact = skipstep3 = False
525 525 if matchfn == match.exact: # match.exact
526 526 exact = True
527 527 dirignore = util.always # skip step 2
528 528 elif match.files() and not match.anypats(): # match.match, no patterns
529 529 skipstep3 = True
530 530
531 531 if self._checkcase:
532 532 normalize = self._normalize
533 533 skipstep3 = False
534 534 else:
535 535 normalize = lambda x, y: x
536 536
537 537 files = sorted(match.files())
538 538 subrepos.sort()
539 539 i, j = 0, 0
540 540 while i < len(files) and j < len(subrepos):
541 541 subpath = subrepos[j] + "/"
542 542 if files[i] < subpath:
543 543 i += 1
544 544 continue
545 545 while i < len(files) and files[i].startswith(subpath):
546 546 del files[i]
547 547 j += 1
548 548
549 549 if not files or '.' in files:
550 550 files = ['']
551 551 results = dict.fromkeys(subrepos)
552 552 results['.hg'] = None
553 553
554 554 # step 1: find all explicit files
555 555 for ff in files:
556 556 nf = normalize(normpath(ff), False)
557 557 if nf in results:
558 558 continue
559 559
560 560 try:
561 561 st = lstat(join(nf))
562 562 kind = getkind(st.st_mode)
563 563 if kind == dirkind:
564 564 skipstep3 = False
565 565 if nf in dmap:
566 566 #file deleted on disk but still in dirstate
567 567 results[nf] = None
568 568 match.dir(nf)
569 569 if not dirignore(nf):
570 570 wadd(nf)
571 571 elif kind == regkind or kind == lnkkind:
572 572 results[nf] = st
573 573 else:
574 574 badfn(ff, badtype(kind))
575 575 if nf in dmap:
576 576 results[nf] = None
577 577 except OSError, inst:
578 578 if nf in dmap: # does it exactly match a file?
579 579 results[nf] = None
580 580 else: # does it match a directory?
581 581 prefix = nf + "/"
582 582 for fn in dmap:
583 583 if fn.startswith(prefix):
584 584 match.dir(nf)
585 585 skipstep3 = False
586 586 break
587 587 else:
588 588 badfn(ff, inst.strerror)
589 589
590 590 # step 2: visit subdirectories
591 591 while work:
592 592 nd = work.pop()
593 593 skip = None
594 594 if nd == '.':
595 595 nd = ''
596 596 else:
597 597 skip = '.hg'
598 598 try:
599 599 entries = listdir(join(nd), stat=True, skip=skip)
600 600 except OSError, inst:
601 601 if inst.errno == errno.EACCES:
602 602 fwarn(nd, inst.strerror)
603 603 continue
604 604 raise
605 605 for f, kind, st in entries:
606 606 nf = normalize(nd and (nd + "/" + f) or f, True)
607 607 if nf not in results:
608 608 if kind == dirkind:
609 609 if not ignore(nf):
610 610 match.dir(nf)
611 611 wadd(nf)
612 612 if nf in dmap and matchfn(nf):
613 613 results[nf] = None
614 614 elif kind == regkind or kind == lnkkind:
615 615 if nf in dmap:
616 616 if matchfn(nf):
617 617 results[nf] = st
618 618 elif matchfn(nf) and not ignore(nf):
619 619 results[nf] = st
620 620 elif nf in dmap and matchfn(nf):
621 621 results[nf] = None
622 622
623 623 # step 3: report unseen items in the dmap hash
624 624 if not skipstep3 and not exact:
625 625 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
626 626 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
627 627 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
628 628 st = None
629 629 results[nf] = st
630 630 for s in subrepos:
631 631 del results[s]
632 632 del results['.hg']
633 633 return results
634 634
635 635 def status(self, match, subrepos, ignored, clean, unknown):
636 636 '''Determine the status of the working copy relative to the
637 637 dirstate and return a tuple of lists (unsure, modified, added,
638 638 removed, deleted, unknown, ignored, clean), where:
639 639
640 640 unsure:
641 641 files that might have been modified since the dirstate was
642 642 written, but need to be read to be sure (size is the same
643 643 but mtime differs)
644 644 modified:
645 645 files that have definitely been modified since the dirstate
646 646 was written (different size or mode)
647 647 added:
648 648 files that have been explicitly added with hg add
649 649 removed:
650 650 files that have been explicitly removed with hg remove
651 651 deleted:
652 652 files that have been deleted through other means ("missing")
653 653 unknown:
654 654 files not in the dirstate that are not ignored
655 655 ignored:
656 656 files not in the dirstate that are ignored
657 657 (by _dirignore())
658 658 clean:
659 659 files that have definitely not been modified since the
660 660 dirstate was written
661 661 '''
662 662 listignored, listclean, listunknown = ignored, clean, unknown
663 663 lookup, modified, added, unknown, ignored = [], [], [], [], []
664 664 removed, deleted, clean = [], [], []
665 665
666 666 dmap = self._map
667 667 ladd = lookup.append # aka "unsure"
668 668 madd = modified.append
669 669 aadd = added.append
670 670 uadd = unknown.append
671 671 iadd = ignored.append
672 672 radd = removed.append
673 673 dadd = deleted.append
674 674 cadd = clean.append
675 675
676 676 lnkkind = stat.S_IFLNK
677 677
678 678 for fn, st in self.walk(match, subrepos, listunknown,
679 679 listignored).iteritems():
680 680 if fn not in dmap:
681 681 if (listignored or match.exact(fn)) and self._dirignore(fn):
682 682 if listignored:
683 683 iadd(fn)
684 684 elif listunknown:
685 685 uadd(fn)
686 686 continue
687 687
688 688 state, mode, size, time = dmap[fn]
689 689
690 690 if not st and state in "nma":
691 691 dadd(fn)
692 692 elif state == 'n':
693 693 # The "mode & lnkkind != lnkkind or self._checklink"
694 694 # lines are an expansion of "islink => checklink"
695 695 # where islink means "is this a link?" and checklink
696 696 # means "can we check links?".
697 697 mtime = int(st.st_mtime)
698 698 if (size >= 0 and
699 699 (size != st.st_size
700 700 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
701 701 and (mode & lnkkind != lnkkind or self._checklink)
702 702 or size == -2 # other parent
703 703 or fn in self._copymap):
704 704 madd(fn)
705 705 elif (mtime != time
706 706 and (mode & lnkkind != lnkkind or self._checklink)):
707 707 ladd(fn)
708 708 elif mtime == self._lastnormaltime:
709 709 # fn may have been changed in the same timeslot without
710 710 # changing its size. This can happen if we quickly do
711 711 # multiple commits in a single transaction.
712 712 # Force lookup, so we don't miss such a racy file change.
713 713 ladd(fn)
714 714 elif listclean:
715 715 cadd(fn)
716 716 elif state == 'm':
717 717 madd(fn)
718 718 elif state == 'a':
719 719 aadd(fn)
720 720 elif state == 'r':
721 721 radd(fn)
722 722
723 723 return (lookup, modified, added, removed, deleted, unknown, ignored,
724 724 clean)
@@ -1,245 +1,250 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
10 10 import os, errno, stat
11 11
12 def checkfilename(f):
13 '''Check that the filename f is an acceptable filename for a tracked file'''
14 if '\r' in f or '\n' in f:
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16
12 17 def checkportable(ui, f):
13 18 '''Check if filename f is portable and warn or abort depending on config'''
14 util.checkfilename(f)
19 checkfilename(f)
15 20 val = ui.config('ui', 'portablefilenames', 'warn')
16 21 lval = val.lower()
17 22 abort = os.name == 'nt' or lval == 'abort'
18 23 bval = util.parsebool(val)
19 24 if abort or lval == 'warn' or bval:
20 25 msg = util.checkwinfilename(f)
21 26 if msg:
22 27 if abort:
23 28 raise util.Abort("%s: %r" % (msg, f))
24 29 ui.warn(_("warning: %s: %r\n") % (msg, f))
25 30 elif bval is None and lval != 'ignore':
26 31 raise error.ConfigError(
27 32 _("ui.portablefilenames value is invalid ('%s')") % val)
28 33
29 34 class path_auditor(object):
30 35 '''ensure that a filesystem path contains no banned components.
31 36 the following properties of a path are checked:
32 37
33 38 - ends with a directory separator
34 39 - under top-level .hg
35 40 - starts at the root of a windows drive
36 41 - contains ".."
37 42 - traverses a symlink (e.g. a/symlink_here/b)
38 43 - inside a nested repository (a callback can be used to approve
39 44 some nested repositories, e.g., subrepositories)
40 45 '''
41 46
42 47 def __init__(self, root, callback=None):
43 48 self.audited = set()
44 49 self.auditeddir = set()
45 50 self.root = root
46 51 self.callback = callback
47 52
48 53 def __call__(self, path):
49 54 '''Check the relative path.
50 55 path may contain a pattern (e.g. foodir/**.txt)'''
51 56
52 57 if path in self.audited:
53 58 return
54 59 # AIX ignores "/" at end of path, others raise EISDIR.
55 60 if util.endswithsep(path):
56 61 raise util.Abort(_("path ends in directory separator: %s") % path)
57 62 normpath = os.path.normcase(path)
58 63 parts = util.splitpath(normpath)
59 64 if (os.path.splitdrive(path)[0]
60 65 or parts[0].lower() in ('.hg', '.hg.', '')
61 66 or os.pardir in parts):
62 67 raise util.Abort(_("path contains illegal component: %s") % path)
63 68 if '.hg' in path.lower():
64 69 lparts = [p.lower() for p in parts]
65 70 for p in '.hg', '.hg.':
66 71 if p in lparts[1:]:
67 72 pos = lparts.index(p)
68 73 base = os.path.join(*parts[:pos])
69 74 raise util.Abort(_('path %r is inside nested repo %r')
70 75 % (path, base))
71 76
72 77 parts.pop()
73 78 prefixes = []
74 79 while parts:
75 80 prefix = os.sep.join(parts)
76 81 if prefix in self.auditeddir:
77 82 break
78 83 curpath = os.path.join(self.root, prefix)
79 84 try:
80 85 st = os.lstat(curpath)
81 86 except OSError, err:
82 87 # EINVAL can be raised as invalid path syntax under win32.
83 88 # They must be ignored for patterns can be checked too.
84 89 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
85 90 raise
86 91 else:
87 92 if stat.S_ISLNK(st.st_mode):
88 93 raise util.Abort(
89 94 _('path %r traverses symbolic link %r')
90 95 % (path, prefix))
91 96 elif (stat.S_ISDIR(st.st_mode) and
92 97 os.path.isdir(os.path.join(curpath, '.hg'))):
93 98 if not self.callback or not self.callback(curpath):
94 99 raise util.Abort(_('path %r is inside nested repo %r') %
95 100 (path, prefix))
96 101 prefixes.append(prefix)
97 102 parts.pop()
98 103
99 104 self.audited.add(path)
100 105 # only add prefixes to the cache after checking everything: we don't
101 106 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
102 107 self.auditeddir.update(prefixes)
103 108
104 109 class opener(object):
105 110 '''Open files relative to a base directory
106 111
107 112 This class is used to hide the details of COW semantics and
108 113 remote file access from higher level code.
109 114 '''
110 115 def __init__(self, base, audit=True):
111 116 self.base = base
112 117 if audit:
113 118 self.auditor = path_auditor(base)
114 119 else:
115 120 self.auditor = util.always
116 121 self.createmode = None
117 122 self._trustnlink = None
118 123
119 124 @util.propertycache
120 125 def _can_symlink(self):
121 126 return util.checklink(self.base)
122 127
123 128 def _fixfilemode(self, name):
124 129 if self.createmode is None:
125 130 return
126 131 os.chmod(name, self.createmode & 0666)
127 132
128 133 def __call__(self, path, mode="r", text=False, atomictemp=False):
129 134 r = util.checkosfilename(path)
130 135 if r:
131 136 raise util.Abort("%s: %r" % (r, path))
132 137 self.auditor(path)
133 138 f = os.path.join(self.base, path)
134 139
135 140 if not text and "b" not in mode:
136 141 mode += "b" # for that other OS
137 142
138 143 nlink = -1
139 144 dirname, basename = os.path.split(f)
140 145 # If basename is empty, then the path is malformed because it points
141 146 # to a directory. Let the posixfile() call below raise IOError.
142 147 if basename and mode not in ('r', 'rb'):
143 148 if atomictemp:
144 149 if not os.path.isdir(dirname):
145 150 util.makedirs(dirname, self.createmode)
146 151 return util.atomictempfile(f, mode, self.createmode)
147 152 try:
148 153 if 'w' in mode:
149 154 util.unlink(f)
150 155 nlink = 0
151 156 else:
152 157 # nlinks() may behave differently for files on Windows
153 158 # shares if the file is open.
154 159 fd = util.posixfile(f)
155 160 nlink = util.nlinks(f)
156 161 if nlink < 1:
157 162 nlink = 2 # force mktempcopy (issue1922)
158 163 fd.close()
159 164 except (OSError, IOError), e:
160 165 if e.errno != errno.ENOENT:
161 166 raise
162 167 nlink = 0
163 168 if not os.path.isdir(dirname):
164 169 util.makedirs(dirname, self.createmode)
165 170 if nlink > 0:
166 171 if self._trustnlink is None:
167 172 self._trustnlink = nlink > 1 or util.checknlink(f)
168 173 if nlink > 1 or not self._trustnlink:
169 174 util.rename(util.mktempcopy(f), f)
170 175 fp = util.posixfile(f, mode)
171 176 if nlink == 0:
172 177 self._fixfilemode(f)
173 178 return fp
174 179
175 180 def symlink(self, src, dst):
176 181 self.auditor(dst)
177 182 linkname = os.path.join(self.base, dst)
178 183 try:
179 184 os.unlink(linkname)
180 185 except OSError:
181 186 pass
182 187
183 188 dirname = os.path.dirname(linkname)
184 189 if not os.path.exists(dirname):
185 190 util.makedirs(dirname, self.createmode)
186 191
187 192 if self._can_symlink:
188 193 try:
189 194 os.symlink(src, linkname)
190 195 except OSError, err:
191 196 raise OSError(err.errno, _('could not symlink to %r: %s') %
192 197 (src, err.strerror), linkname)
193 198 else:
194 199 f = self(dst, "w")
195 200 f.write(src)
196 201 f.close()
197 202 self._fixfilemode(dst)
198 203
199 204 def canonpath(root, cwd, myname, auditor=None):
200 205 '''return the canonical path of myname, given cwd and root'''
201 206 if util.endswithsep(root):
202 207 rootsep = root
203 208 else:
204 209 rootsep = root + os.sep
205 210 name = myname
206 211 if not os.path.isabs(name):
207 212 name = os.path.join(root, cwd, name)
208 213 name = os.path.normpath(name)
209 214 if auditor is None:
210 215 auditor = path_auditor(root)
211 216 if name != rootsep and name.startswith(rootsep):
212 217 name = name[len(rootsep):]
213 218 auditor(name)
214 219 return util.pconvert(name)
215 220 elif name == root:
216 221 return ''
217 222 else:
218 223 # Determine whether `name' is in the hierarchy at or beneath `root',
219 224 # by iterating name=dirname(name) until that causes no change (can't
220 225 # check name == '/', because that doesn't work on windows). For each
221 226 # `name', compare dev/inode numbers. If they match, the list `rel'
222 227 # holds the reversed list of components making up the relative file
223 228 # name we want.
224 229 root_st = os.stat(root)
225 230 rel = []
226 231 while True:
227 232 try:
228 233 name_st = os.stat(name)
229 234 except OSError:
230 235 break
231 236 if util.samestat(name_st, root_st):
232 237 if not rel:
233 238 # name was actually the same as root (maybe a symlink)
234 239 return ''
235 240 rel.reverse()
236 241 name = os.path.join(*rel)
237 242 auditor(name)
238 243 return util.pconvert(name)
239 244 dirname, basename = os.path.split(name)
240 245 rel.append(basename)
241 246 if dirname == name:
242 247 break
243 248 name = dirname
244 249
245 250 raise util.Abort('%s not under root' % myname)
@@ -1,1368 +1,1363 b''
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 if sys.version_info >= (2, 5):
32 32 from hashlib import sha1 as _sha1
33 33 else:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import __builtin__
40 40
41 41 if sys.version_info[0] < 3:
42 42 def fakebuffer(sliceable, offset=0):
43 43 return sliceable[offset:]
44 44 else:
45 45 def fakebuffer(sliceable, offset=0):
46 46 return memoryview(sliceable)[offset:]
47 47 try:
48 48 buffer
49 49 except NameError:
50 50 __builtin__.buffer = fakebuffer
51 51
52 52 import subprocess
53 53 closefds = os.name == 'posix'
54 54
55 55 def popen2(cmd, env=None, newlines=False):
56 56 # Setting bufsize to -1 lets the system decide the buffer size.
57 57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 universal_newlines=newlines,
63 63 env=env)
64 64 return p.stdin, p.stdout
65 65
66 66 def popen3(cmd, env=None, newlines=False):
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.PIPE,
71 71 universal_newlines=newlines,
72 72 env=env)
73 73 return p.stdin, p.stdout, p.stderr
74 74
75 75 def version():
76 76 """Return version information if available."""
77 77 try:
78 78 import __version__
79 79 return __version__.version
80 80 except ImportError:
81 81 return 'unknown'
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 97 '%b %d %H:%M:%S %Y',
98 98 '%b %d %I:%M:%S%p %Y',
99 99 '%b %d %H:%M:%S',
100 100 '%b %d %I:%M:%S%p',
101 101 '%b %d %H:%M',
102 102 '%b %d %I:%M%p',
103 103 '%b %d %Y',
104 104 '%b %d',
105 105 '%H:%M:%S',
106 106 '%I:%M:%S%p',
107 107 '%H:%M',
108 108 '%I:%M%p',
109 109 )
110 110
111 111 extendeddateformats = defaultdateformats + (
112 112 "%Y",
113 113 "%Y-%m",
114 114 "%b",
115 115 "%b %Y",
116 116 )
117 117
118 118 def cachefunc(func):
119 119 '''cache the result of function calls'''
120 120 # XXX doesn't handle keywords args
121 121 cache = {}
122 122 if func.func_code.co_argcount == 1:
123 123 # we gain a small amount of time because
124 124 # we don't need to pack/unpack the list
125 125 def f(arg):
126 126 if arg not in cache:
127 127 cache[arg] = func(arg)
128 128 return cache[arg]
129 129 else:
130 130 def f(*args):
131 131 if args not in cache:
132 132 cache[args] = func(*args)
133 133 return cache[args]
134 134
135 135 return f
136 136
137 137 def lrucachefunc(func):
138 138 '''cache most recent results of function calls'''
139 139 cache = {}
140 140 order = []
141 141 if func.func_code.co_argcount == 1:
142 142 def f(arg):
143 143 if arg not in cache:
144 144 if len(cache) > 20:
145 145 del cache[order.pop(0)]
146 146 cache[arg] = func(arg)
147 147 else:
148 148 order.remove(arg)
149 149 order.append(arg)
150 150 return cache[arg]
151 151 else:
152 152 def f(*args):
153 153 if args not in cache:
154 154 if len(cache) > 20:
155 155 del cache[order.pop(0)]
156 156 cache[args] = func(*args)
157 157 else:
158 158 order.remove(args)
159 159 order.append(args)
160 160 return cache[args]
161 161
162 162 return f
163 163
164 164 class propertycache(object):
165 165 def __init__(self, func):
166 166 self.func = func
167 167 self.name = func.__name__
168 168 def __get__(self, obj, type=None):
169 169 result = self.func(obj)
170 170 setattr(obj, self.name, result)
171 171 return result
172 172
173 173 def pipefilter(s, cmd):
174 174 '''filter string S through command CMD, returning its output'''
175 175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 177 pout, perr = p.communicate(s)
178 178 return pout
179 179
180 180 def tempfilter(s, cmd):
181 181 '''filter string S through a pair of temporary files with CMD.
182 182 CMD is used as a template to create the real command to be run,
183 183 with the strings INFILE and OUTFILE replaced by the real names of
184 184 the temporary files generated.'''
185 185 inname, outname = None, None
186 186 try:
187 187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 188 fp = os.fdopen(infd, 'wb')
189 189 fp.write(s)
190 190 fp.close()
191 191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 192 os.close(outfd)
193 193 cmd = cmd.replace('INFILE', inname)
194 194 cmd = cmd.replace('OUTFILE', outname)
195 195 code = os.system(cmd)
196 196 if sys.platform == 'OpenVMS' and code & 1:
197 197 code = 0
198 198 if code:
199 199 raise Abort(_("command '%s' failed: %s") %
200 200 (cmd, explain_exit(code)))
201 201 fp = open(outname, 'rb')
202 202 r = fp.read()
203 203 fp.close()
204 204 return r
205 205 finally:
206 206 try:
207 207 if inname:
208 208 os.unlink(inname)
209 209 except:
210 210 pass
211 211 try:
212 212 if outname:
213 213 os.unlink(outname)
214 214 except:
215 215 pass
216 216
217 217 filtertable = {
218 218 'tempfile:': tempfilter,
219 219 'pipe:': pipefilter,
220 220 }
221 221
222 222 def filter(s, cmd):
223 223 "filter a string through a command that transforms its input to its output"
224 224 for name, fn in filtertable.iteritems():
225 225 if cmd.startswith(name):
226 226 return fn(s, cmd[len(name):].lstrip())
227 227 return pipefilter(s, cmd)
228 228
229 229 def binary(s):
230 230 """return true if a string is binary data"""
231 231 return bool(s and '\0' in s)
232 232
233 233 def increasingchunks(source, min=1024, max=65536):
234 234 '''return no less than min bytes per chunk while data remains,
235 235 doubling min after each chunk until it reaches max'''
236 236 def log2(x):
237 237 if not x:
238 238 return 0
239 239 i = 0
240 240 while x:
241 241 x >>= 1
242 242 i += 1
243 243 return i - 1
244 244
245 245 buf = []
246 246 blen = 0
247 247 for chunk in source:
248 248 buf.append(chunk)
249 249 blen += len(chunk)
250 250 if blen >= min:
251 251 if min < max:
252 252 min = min << 1
253 253 nmin = 1 << log2(blen)
254 254 if nmin > min:
255 255 min = nmin
256 256 if min > max:
257 257 min = max
258 258 yield ''.join(buf)
259 259 blen = 0
260 260 buf = []
261 261 if buf:
262 262 yield ''.join(buf)
263 263
264 264 Abort = error.Abort
265 265
266 266 def always(fn):
267 267 return True
268 268
269 269 def never(fn):
270 270 return False
271 271
272 272 def pathto(root, n1, n2):
273 273 '''return the relative path from one place to another.
274 274 root should use os.sep to separate directories
275 275 n1 should use os.sep to separate directories
276 276 n2 should use "/" to separate directories
277 277 returns an os.sep-separated path.
278 278
279 279 If n1 is a relative path, it's assumed it's
280 280 relative to root.
281 281 n2 should always be relative to root.
282 282 '''
283 283 if not n1:
284 284 return localpath(n2)
285 285 if os.path.isabs(n1):
286 286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 287 return os.path.join(root, localpath(n2))
288 288 n2 = '/'.join((pconvert(root), n2))
289 289 a, b = splitpath(n1), n2.split('/')
290 290 a.reverse()
291 291 b.reverse()
292 292 while a and b and a[-1] == b[-1]:
293 293 a.pop()
294 294 b.pop()
295 295 b.reverse()
296 296 return os.sep.join((['..'] * len(a)) + b) or '.'
297 297
298 298 _hgexecutable = None
299 299
300 300 def main_is_frozen():
301 301 """return True if we are a frozen executable.
302 302
303 303 The code supports py2exe (most common, Windows only) and tools/freeze
304 304 (portable, not much used).
305 305 """
306 306 return (hasattr(sys, "frozen") or # new py2exe
307 307 hasattr(sys, "importers") or # old py2exe
308 308 imp.is_frozen("__main__")) # tools/freeze
309 309
310 310 def hgexecutable():
311 311 """return location of the 'hg' executable.
312 312
313 313 Defaults to $HG or 'hg' in the search path.
314 314 """
315 315 if _hgexecutable is None:
316 316 hg = os.environ.get('HG')
317 317 if hg:
318 318 set_hgexecutable(hg)
319 319 elif main_is_frozen():
320 320 set_hgexecutable(sys.executable)
321 321 else:
322 322 exe = find_exe('hg') or os.path.basename(sys.argv[0])
323 323 set_hgexecutable(exe)
324 324 return _hgexecutable
325 325
326 326 def set_hgexecutable(path):
327 327 """set location of the 'hg' executable"""
328 328 global _hgexecutable
329 329 _hgexecutable = path
330 330
331 331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 332 '''enhanced shell command execution.
333 333 run with environment maybe modified, maybe in different dir.
334 334
335 335 if command fails and onerr is None, return status. if ui object,
336 336 print error message and return status, else raise onerr object as
337 337 exception.
338 338
339 339 if out is specified, it is assumed to be a file-like object that has a
340 340 write() method. stdout and stderr will be redirected to out.'''
341 341 try:
342 342 sys.stdout.flush()
343 343 except Exception:
344 344 pass
345 345 def py2shell(val):
346 346 'convert python object into string that is useful to shell'
347 347 if val is None or val is False:
348 348 return '0'
349 349 if val is True:
350 350 return '1'
351 351 return str(val)
352 352 origcmd = cmd
353 353 cmd = quotecommand(cmd)
354 354 env = dict(os.environ)
355 355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 356 env['HG'] = hgexecutable()
357 357 if out is None:
358 358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 359 env=env, cwd=cwd)
360 360 else:
361 361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 362 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 363 stderr=subprocess.STDOUT)
364 364 for line in proc.stdout:
365 365 out.write(line)
366 366 proc.wait()
367 367 rc = proc.returncode
368 368 if sys.platform == 'OpenVMS' and rc & 1:
369 369 rc = 0
370 370 if rc and onerr:
371 371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 372 explain_exit(rc)[0])
373 373 if errprefix:
374 374 errmsg = '%s: %s' % (errprefix, errmsg)
375 375 try:
376 376 onerr.warn(errmsg + '\n')
377 377 except AttributeError:
378 378 raise onerr(errmsg)
379 379 return rc
380 380
381 381 def checksignature(func):
382 382 '''wrap a function with code to check for calling errors'''
383 383 def check(*args, **kwargs):
384 384 try:
385 385 return func(*args, **kwargs)
386 386 except TypeError:
387 387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 388 raise error.SignatureError
389 389 raise
390 390
391 391 return check
392 392
393 393 def makedir(path, notindexed):
394 394 os.mkdir(path)
395 395
396 396 def unlinkpath(f):
397 397 """unlink and remove the directory if it is empty"""
398 398 os.unlink(f)
399 399 # try removing directories that might now be empty
400 400 try:
401 401 os.removedirs(os.path.dirname(f))
402 402 except OSError:
403 403 pass
404 404
405 405 def copyfile(src, dest):
406 406 "copy a file, preserving mode and atime/mtime"
407 407 if os.path.islink(src):
408 408 try:
409 409 os.unlink(dest)
410 410 except:
411 411 pass
412 412 os.symlink(os.readlink(src), dest)
413 413 else:
414 414 try:
415 415 shutil.copyfile(src, dest)
416 416 shutil.copymode(src, dest)
417 417 except shutil.Error, inst:
418 418 raise Abort(str(inst))
419 419
420 420 def copyfiles(src, dst, hardlink=None):
421 421 """Copy a directory tree using hardlinks if possible"""
422 422
423 423 if hardlink is None:
424 424 hardlink = (os.stat(src).st_dev ==
425 425 os.stat(os.path.dirname(dst)).st_dev)
426 426
427 427 num = 0
428 428 if os.path.isdir(src):
429 429 os.mkdir(dst)
430 430 for name, kind in osutil.listdir(src):
431 431 srcname = os.path.join(src, name)
432 432 dstname = os.path.join(dst, name)
433 433 hardlink, n = copyfiles(srcname, dstname, hardlink)
434 434 num += n
435 435 else:
436 436 if hardlink:
437 437 try:
438 438 os_link(src, dst)
439 439 except (IOError, OSError):
440 440 hardlink = False
441 441 shutil.copy(src, dst)
442 442 else:
443 443 shutil.copy(src, dst)
444 444 num += 1
445 445
446 446 return hardlink, num
447 447
448 def checkfilename(f):
449 '''Check that the filename f is an acceptable filename for a tracked file'''
450 if '\r' in f or '\n' in f:
451 raise Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
452
453 448 _windows_reserved_filenames = '''con prn aux nul
454 449 com1 com2 com3 com4 com5 com6 com7 com8 com9
455 450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
456 451 _windows_reserved_chars = ':*?"<>|'
457 452 def checkwinfilename(path):
458 453 '''Check that the base-relative path is a valid filename on Windows.
459 454 Returns None if the path is ok, or a UI string describing the problem.
460 455
461 456 >>> checkwinfilename("just/a/normal/path")
462 457 >>> checkwinfilename("foo/bar/con.xml")
463 458 "filename contains 'con', which is reserved on Windows"
464 459 >>> checkwinfilename("foo/con.xml/bar")
465 460 "filename contains 'con', which is reserved on Windows"
466 461 >>> checkwinfilename("foo/bar/xml.con")
467 462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
468 463 "filename contains 'AUX', which is reserved on Windows"
469 464 >>> checkwinfilename("foo/bar/bla:.txt")
470 465 "filename contains ':', which is reserved on Windows"
471 466 >>> checkwinfilename("foo/bar/b\07la.txt")
472 467 "filename contains '\\\\x07', which is invalid on Windows"
473 468 >>> checkwinfilename("foo/bar/bla ")
474 469 "filename ends with ' ', which is not allowed on Windows"
475 470 '''
476 471 for n in path.replace('\\', '/').split('/'):
477 472 if not n:
478 473 continue
479 474 for c in n:
480 475 if c in _windows_reserved_chars:
481 476 return _("filename contains '%s', which is reserved "
482 477 "on Windows") % c
483 478 if ord(c) <= 31:
484 479 return _("filename contains %r, which is invalid "
485 480 "on Windows") % c
486 481 base = n.split('.')[0]
487 482 if base and base.lower() in _windows_reserved_filenames:
488 483 return _("filename contains '%s', which is reserved "
489 484 "on Windows") % base
490 485 t = n[-1]
491 486 if t in '. ':
492 487 return _("filename ends with '%s', which is not allowed "
493 488 "on Windows") % t
494 489
495 490 def lookup_reg(key, name=None, scope=None):
496 491 return None
497 492
498 493 def hidewindow():
499 494 """Hide current shell window.
500 495
501 496 Used to hide the window opened when starting asynchronous
502 497 child process under Windows, unneeded on other systems.
503 498 """
504 499 pass
505 500
506 501 if os.name == 'nt':
507 502 checkosfilename = checkwinfilename
508 503 from windows import *
509 504 else:
510 505 from posix import *
511 506
512 507 def makelock(info, pathname):
513 508 try:
514 509 return os.symlink(info, pathname)
515 510 except OSError, why:
516 511 if why.errno == errno.EEXIST:
517 512 raise
518 513 except AttributeError: # no symlink in os
519 514 pass
520 515
521 516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
522 517 os.write(ld, info)
523 518 os.close(ld)
524 519
525 520 def readlock(pathname):
526 521 try:
527 522 return os.readlink(pathname)
528 523 except OSError, why:
529 524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
530 525 raise
531 526 except AttributeError: # no symlink in os
532 527 pass
533 528 fp = posixfile(pathname)
534 529 r = fp.read()
535 530 fp.close()
536 531 return r
537 532
538 533 def fstat(fp):
539 534 '''stat file object that may not have fileno method.'''
540 535 try:
541 536 return os.fstat(fp.fileno())
542 537 except AttributeError:
543 538 return os.stat(fp.name)
544 539
545 540 # File system features
546 541
547 542 def checkcase(path):
548 543 """
549 544 Check whether the given path is on a case-sensitive filesystem
550 545
551 546 Requires a path (like /foo/.hg) ending with a foldable final
552 547 directory component.
553 548 """
554 549 s1 = os.stat(path)
555 550 d, b = os.path.split(path)
556 551 p2 = os.path.join(d, b.upper())
557 552 if path == p2:
558 553 p2 = os.path.join(d, b.lower())
559 554 try:
560 555 s2 = os.stat(p2)
561 556 if s2 == s1:
562 557 return False
563 558 return True
564 559 except:
565 560 return True
566 561
567 562 _fspathcache = {}
568 563 def fspath(name, root):
569 564 '''Get name in the case stored in the filesystem
570 565
571 566 The name is either relative to root, or it is an absolute path starting
572 567 with root. Note that this function is unnecessary, and should not be
573 568 called, for case-sensitive filesystems (simply because it's expensive).
574 569 '''
575 570 # If name is absolute, make it relative
576 571 if name.lower().startswith(root.lower()):
577 572 l = len(root)
578 573 if name[l] == os.sep or name[l] == os.altsep:
579 574 l = l + 1
580 575 name = name[l:]
581 576
582 577 if not os.path.lexists(os.path.join(root, name)):
583 578 return None
584 579
585 580 seps = os.sep
586 581 if os.altsep:
587 582 seps = seps + os.altsep
588 583 # Protect backslashes. This gets silly very quickly.
589 584 seps.replace('\\','\\\\')
590 585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
591 586 dir = os.path.normcase(os.path.normpath(root))
592 587 result = []
593 588 for part, sep in pattern.findall(name):
594 589 if sep:
595 590 result.append(sep)
596 591 continue
597 592
598 593 if dir not in _fspathcache:
599 594 _fspathcache[dir] = os.listdir(dir)
600 595 contents = _fspathcache[dir]
601 596
602 597 lpart = part.lower()
603 598 lenp = len(part)
604 599 for n in contents:
605 600 if lenp == len(n) and n.lower() == lpart:
606 601 result.append(n)
607 602 break
608 603 else:
609 604 # Cannot happen, as the file exists!
610 605 result.append(part)
611 606 dir = os.path.join(dir, lpart)
612 607
613 608 return ''.join(result)
614 609
615 610 def checknlink(testfile):
616 611 '''check whether hardlink count reporting works properly'''
617 612
618 613 # testfile may be open, so we need a separate file for checking to
619 614 # work around issue2543 (or testfile may get lost on Samba shares)
620 615 f1 = testfile + ".hgtmp1"
621 616 if os.path.lexists(f1):
622 617 return False
623 618 try:
624 619 posixfile(f1, 'w').close()
625 620 except IOError:
626 621 return False
627 622
628 623 f2 = testfile + ".hgtmp2"
629 624 fd = None
630 625 try:
631 626 try:
632 627 os_link(f1, f2)
633 628 except OSError:
634 629 return False
635 630
636 631 # nlinks() may behave differently for files on Windows shares if
637 632 # the file is open.
638 633 fd = posixfile(f2)
639 634 return nlinks(f2) > 1
640 635 finally:
641 636 if fd is not None:
642 637 fd.close()
643 638 for f in (f1, f2):
644 639 try:
645 640 os.unlink(f)
646 641 except OSError:
647 642 pass
648 643
649 644 return False
650 645
651 646 def endswithsep(path):
652 647 '''Check path ends with os.sep or os.altsep.'''
653 648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
654 649
655 650 def splitpath(path):
656 651 '''Split path by os.sep.
657 652 Note that this function does not use os.altsep because this is
658 653 an alternative of simple "xxx.split(os.sep)".
659 654 It is recommended to use os.path.normpath() before using this
660 655 function if need.'''
661 656 return path.split(os.sep)
662 657
663 658 def gui():
664 659 '''Are we running in a GUI?'''
665 660 if sys.platform == 'darwin':
666 661 if 'SSH_CONNECTION' in os.environ:
667 662 # handle SSH access to a box where the user is logged in
668 663 return False
669 664 elif getattr(osutil, 'isgui', None):
670 665 # check if a CoreGraphics session is available
671 666 return osutil.isgui()
672 667 else:
673 668 # pure build; use a safe default
674 669 return True
675 670 else:
676 671 return os.name == "nt" or os.environ.get("DISPLAY")
677 672
678 673 def mktempcopy(name, emptyok=False, createmode=None):
679 674 """Create a temporary file with the same contents from name
680 675
681 676 The permission bits are copied from the original file.
682 677
683 678 If the temporary file is going to be truncated immediately, you
684 679 can use emptyok=True as an optimization.
685 680
686 681 Returns the name of the temporary file.
687 682 """
688 683 d, fn = os.path.split(name)
689 684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
690 685 os.close(fd)
691 686 # Temporary files are created with mode 0600, which is usually not
692 687 # what we want. If the original file already exists, just copy
693 688 # its mode. Otherwise, manually obey umask.
694 689 try:
695 690 st_mode = os.lstat(name).st_mode & 0777
696 691 except OSError, inst:
697 692 if inst.errno != errno.ENOENT:
698 693 raise
699 694 st_mode = createmode
700 695 if st_mode is None:
701 696 st_mode = ~umask
702 697 st_mode &= 0666
703 698 os.chmod(temp, st_mode)
704 699 if emptyok:
705 700 return temp
706 701 try:
707 702 try:
708 703 ifp = posixfile(name, "rb")
709 704 except IOError, inst:
710 705 if inst.errno == errno.ENOENT:
711 706 return temp
712 707 if not getattr(inst, 'filename', None):
713 708 inst.filename = name
714 709 raise
715 710 ofp = posixfile(temp, "wb")
716 711 for chunk in filechunkiter(ifp):
717 712 ofp.write(chunk)
718 713 ifp.close()
719 714 ofp.close()
720 715 except:
721 716 try: os.unlink(temp)
722 717 except: pass
723 718 raise
724 719 return temp
725 720
726 721 class atomictempfile(object):
727 722 """file-like object that atomically updates a file
728 723
729 724 All writes will be redirected to a temporary copy of the original
730 725 file. When rename is called, the copy is renamed to the original
731 726 name, making the changes visible.
732 727 """
733 728 def __init__(self, name, mode='w+b', createmode=None):
734 729 self.__name = name
735 730 self._fp = None
736 731 self.temp = mktempcopy(name, emptyok=('w' in mode),
737 732 createmode=createmode)
738 733 self._fp = posixfile(self.temp, mode)
739 734
740 735 def __getattr__(self, name):
741 736 return getattr(self._fp, name)
742 737
743 738 def rename(self):
744 739 if not self._fp.closed:
745 740 self._fp.close()
746 741 rename(self.temp, localpath(self.__name))
747 742
748 743 def close(self):
749 744 if not self._fp:
750 745 return
751 746 if not self._fp.closed:
752 747 try:
753 748 os.unlink(self.temp)
754 749 except: pass
755 750 self._fp.close()
756 751
757 752 def __del__(self):
758 753 self.close()
759 754
760 755 def makedirs(name, mode=None):
761 756 """recursive directory creation with parent mode inheritance"""
762 757 parent = os.path.abspath(os.path.dirname(name))
763 758 try:
764 759 os.mkdir(name)
765 760 if mode is not None:
766 761 os.chmod(name, mode)
767 762 return
768 763 except OSError, err:
769 764 if err.errno == errno.EEXIST:
770 765 return
771 766 if not name or parent == name or err.errno != errno.ENOENT:
772 767 raise
773 768 makedirs(parent, mode)
774 769 makedirs(name, mode)
775 770
776 771 class chunkbuffer(object):
777 772 """Allow arbitrary sized chunks of data to be efficiently read from an
778 773 iterator over chunks of arbitrary size."""
779 774
780 775 def __init__(self, in_iter):
781 776 """in_iter is the iterator that's iterating over the input chunks.
782 777 targetsize is how big a buffer to try to maintain."""
783 778 def splitbig(chunks):
784 779 for chunk in chunks:
785 780 if len(chunk) > 2**20:
786 781 pos = 0
787 782 while pos < len(chunk):
788 783 end = pos + 2 ** 18
789 784 yield chunk[pos:end]
790 785 pos = end
791 786 else:
792 787 yield chunk
793 788 self.iter = splitbig(in_iter)
794 789 self._queue = []
795 790
796 791 def read(self, l):
797 792 """Read L bytes of data from the iterator of chunks of data.
798 793 Returns less than L bytes if the iterator runs dry."""
799 794 left = l
800 795 buf = ''
801 796 queue = self._queue
802 797 while left > 0:
803 798 # refill the queue
804 799 if not queue:
805 800 target = 2**18
806 801 for chunk in self.iter:
807 802 queue.append(chunk)
808 803 target -= len(chunk)
809 804 if target <= 0:
810 805 break
811 806 if not queue:
812 807 break
813 808
814 809 chunk = queue.pop(0)
815 810 left -= len(chunk)
816 811 if left < 0:
817 812 queue.insert(0, chunk[left:])
818 813 buf += chunk[:left]
819 814 else:
820 815 buf += chunk
821 816
822 817 return buf
823 818
824 819 def filechunkiter(f, size=65536, limit=None):
825 820 """Create a generator that produces the data in the file size
826 821 (default 65536) bytes at a time, up to optional limit (default is
827 822 to read all data). Chunks may be less than size bytes if the
828 823 chunk is the last chunk in the file, or the file is a socket or
829 824 some other type of file that sometimes reads less data than is
830 825 requested."""
831 826 assert size >= 0
832 827 assert limit is None or limit >= 0
833 828 while True:
834 829 if limit is None:
835 830 nbytes = size
836 831 else:
837 832 nbytes = min(limit, size)
838 833 s = nbytes and f.read(nbytes)
839 834 if not s:
840 835 break
841 836 if limit:
842 837 limit -= len(s)
843 838 yield s
844 839
845 840 def makedate():
846 841 lt = time.localtime()
847 842 if lt[8] == 1 and time.daylight:
848 843 tz = time.altzone
849 844 else:
850 845 tz = time.timezone
851 846 t = time.mktime(lt)
852 847 if t < 0:
853 848 hint = _("check your clock")
854 849 raise Abort(_("negative timestamp: %d") % t, hint=hint)
855 850 return t, tz
856 851
857 852 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
858 853 """represent a (unixtime, offset) tuple as a localized time.
859 854 unixtime is seconds since the epoch, and offset is the time zone's
860 855 number of seconds away from UTC. if timezone is false, do not
861 856 append time zone to string."""
862 857 t, tz = date or makedate()
863 858 if t < 0:
864 859 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
865 860 tz = 0
866 861 if "%1" in format or "%2" in format:
867 862 sign = (tz > 0) and "-" or "+"
868 863 minutes = abs(tz) // 60
869 864 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
870 865 format = format.replace("%2", "%02d" % (minutes % 60))
871 866 s = time.strftime(format, time.gmtime(float(t) - tz))
872 867 return s
873 868
874 869 def shortdate(date=None):
875 870 """turn (timestamp, tzoff) tuple into iso 8631 date."""
876 871 return datestr(date, format='%Y-%m-%d')
877 872
878 873 def strdate(string, format, defaults=[]):
879 874 """parse a localized time string and return a (unixtime, offset) tuple.
880 875 if the string cannot be parsed, ValueError is raised."""
881 876 def timezone(string):
882 877 tz = string.split()[-1]
883 878 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
884 879 sign = (tz[0] == "+") and 1 or -1
885 880 hours = int(tz[1:3])
886 881 minutes = int(tz[3:5])
887 882 return -sign * (hours * 60 + minutes) * 60
888 883 if tz == "GMT" or tz == "UTC":
889 884 return 0
890 885 return None
891 886
892 887 # NOTE: unixtime = localunixtime + offset
893 888 offset, date = timezone(string), string
894 889 if offset is not None:
895 890 date = " ".join(string.split()[:-1])
896 891
897 892 # add missing elements from defaults
898 893 usenow = False # default to using biased defaults
899 894 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
900 895 found = [True for p in part if ("%"+p) in format]
901 896 if not found:
902 897 date += "@" + defaults[part][usenow]
903 898 format += "@%" + part[0]
904 899 else:
905 900 # We've found a specific time element, less specific time
906 901 # elements are relative to today
907 902 usenow = True
908 903
909 904 timetuple = time.strptime(date, format)
910 905 localunixtime = int(calendar.timegm(timetuple))
911 906 if offset is None:
912 907 # local timezone
913 908 unixtime = int(time.mktime(timetuple))
914 909 offset = unixtime - localunixtime
915 910 else:
916 911 unixtime = localunixtime + offset
917 912 return unixtime, offset
918 913
919 914 def parsedate(date, formats=None, bias={}):
920 915 """parse a localized date/time and return a (unixtime, offset) tuple.
921 916
922 917 The date may be a "unixtime offset" string or in one of the specified
923 918 formats. If the date already is a (unixtime, offset) tuple, it is returned.
924 919 """
925 920 if not date:
926 921 return 0, 0
927 922 if isinstance(date, tuple) and len(date) == 2:
928 923 return date
929 924 if not formats:
930 925 formats = defaultdateformats
931 926 date = date.strip()
932 927 try:
933 928 when, offset = map(int, date.split(' '))
934 929 except ValueError:
935 930 # fill out defaults
936 931 now = makedate()
937 932 defaults = {}
938 933 nowmap = {}
939 934 for part in ("d", "mb", "yY", "HI", "M", "S"):
940 935 # this piece is for rounding the specific end of unknowns
941 936 b = bias.get(part)
942 937 if b is None:
943 938 if part[0] in "HMS":
944 939 b = "00"
945 940 else:
946 941 b = "0"
947 942
948 943 # this piece is for matching the generic end to today's date
949 944 n = datestr(now, "%" + part[0])
950 945
951 946 defaults[part] = (b, n)
952 947
953 948 for format in formats:
954 949 try:
955 950 when, offset = strdate(date, format, defaults)
956 951 except (ValueError, OverflowError):
957 952 pass
958 953 else:
959 954 break
960 955 else:
961 956 raise Abort(_('invalid date: %r') % date)
962 957 # validate explicit (probably user-specified) date and
963 958 # time zone offset. values must fit in signed 32 bits for
964 959 # current 32-bit linux runtimes. timezones go from UTC-12
965 960 # to UTC+14
966 961 if abs(when) > 0x7fffffff:
967 962 raise Abort(_('date exceeds 32 bits: %d') % when)
968 963 if when < 0:
969 964 raise Abort(_('negative date value: %d') % when)
970 965 if offset < -50400 or offset > 43200:
971 966 raise Abort(_('impossible time zone offset: %d') % offset)
972 967 return when, offset
973 968
974 969 def matchdate(date):
975 970 """Return a function that matches a given date match specifier
976 971
977 972 Formats include:
978 973
979 974 '{date}' match a given date to the accuracy provided
980 975
981 976 '<{date}' on or before a given date
982 977
983 978 '>{date}' on or after a given date
984 979
985 980 >>> p1 = parsedate("10:29:59")
986 981 >>> p2 = parsedate("10:30:00")
987 982 >>> p3 = parsedate("10:30:59")
988 983 >>> p4 = parsedate("10:31:00")
989 984 >>> p5 = parsedate("Sep 15 10:30:00 1999")
990 985 >>> f = matchdate("10:30")
991 986 >>> f(p1[0])
992 987 False
993 988 >>> f(p2[0])
994 989 True
995 990 >>> f(p3[0])
996 991 True
997 992 >>> f(p4[0])
998 993 False
999 994 >>> f(p5[0])
1000 995 False
1001 996 """
1002 997
1003 998 def lower(date):
1004 999 d = dict(mb="1", d="1")
1005 1000 return parsedate(date, extendeddateformats, d)[0]
1006 1001
1007 1002 def upper(date):
1008 1003 d = dict(mb="12", HI="23", M="59", S="59")
1009 1004 for days in ("31", "30", "29"):
1010 1005 try:
1011 1006 d["d"] = days
1012 1007 return parsedate(date, extendeddateformats, d)[0]
1013 1008 except:
1014 1009 pass
1015 1010 d["d"] = "28"
1016 1011 return parsedate(date, extendeddateformats, d)[0]
1017 1012
1018 1013 date = date.strip()
1019 1014
1020 1015 if not date:
1021 1016 raise Abort(_("dates cannot consist entirely of whitespace"))
1022 1017 elif date[0] == "<":
1023 1018 if not date[1:]:
1024 1019 raise Abort(_("invalid day spec, use '<DATE'"))
1025 1020 when = upper(date[1:])
1026 1021 return lambda x: x <= when
1027 1022 elif date[0] == ">":
1028 1023 if not date[1:]:
1029 1024 raise Abort(_("invalid day spec, use '>DATE'"))
1030 1025 when = lower(date[1:])
1031 1026 return lambda x: x >= when
1032 1027 elif date[0] == "-":
1033 1028 try:
1034 1029 days = int(date[1:])
1035 1030 except ValueError:
1036 1031 raise Abort(_("invalid day spec: %s") % date[1:])
1037 1032 if days < 0:
1038 1033 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1039 1034 % date[1:])
1040 1035 when = makedate()[0] - days * 3600 * 24
1041 1036 return lambda x: x >= when
1042 1037 elif " to " in date:
1043 1038 a, b = date.split(" to ")
1044 1039 start, stop = lower(a), upper(b)
1045 1040 return lambda x: x >= start and x <= stop
1046 1041 else:
1047 1042 start, stop = lower(date), upper(date)
1048 1043 return lambda x: x >= start and x <= stop
1049 1044
1050 1045 def shortuser(user):
1051 1046 """Return a short representation of a user name or email address."""
1052 1047 f = user.find('@')
1053 1048 if f >= 0:
1054 1049 user = user[:f]
1055 1050 f = user.find('<')
1056 1051 if f >= 0:
1057 1052 user = user[f + 1:]
1058 1053 f = user.find(' ')
1059 1054 if f >= 0:
1060 1055 user = user[:f]
1061 1056 f = user.find('.')
1062 1057 if f >= 0:
1063 1058 user = user[:f]
1064 1059 return user
1065 1060
1066 1061 def email(author):
1067 1062 '''get email of author.'''
1068 1063 r = author.find('>')
1069 1064 if r == -1:
1070 1065 r = None
1071 1066 return author[author.find('<') + 1:r]
1072 1067
1073 1068 def _ellipsis(text, maxlength):
1074 1069 if len(text) <= maxlength:
1075 1070 return text, False
1076 1071 else:
1077 1072 return "%s..." % (text[:maxlength - 3]), True
1078 1073
1079 1074 def ellipsis(text, maxlength=400):
1080 1075 """Trim string to at most maxlength (default: 400) characters."""
1081 1076 try:
1082 1077 # use unicode not to split at intermediate multi-byte sequence
1083 1078 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1084 1079 maxlength)
1085 1080 if not truncated:
1086 1081 return text
1087 1082 return utext.encode(encoding.encoding)
1088 1083 except (UnicodeDecodeError, UnicodeEncodeError):
1089 1084 return _ellipsis(text, maxlength)[0]
1090 1085
1091 1086 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1092 1087 '''yield every hg repository under path, recursively.'''
1093 1088 def errhandler(err):
1094 1089 if err.filename == path:
1095 1090 raise err
1096 1091 if followsym and hasattr(os.path, 'samestat'):
1097 1092 def _add_dir_if_not_there(dirlst, dirname):
1098 1093 match = False
1099 1094 samestat = os.path.samestat
1100 1095 dirstat = os.stat(dirname)
1101 1096 for lstdirstat in dirlst:
1102 1097 if samestat(dirstat, lstdirstat):
1103 1098 match = True
1104 1099 break
1105 1100 if not match:
1106 1101 dirlst.append(dirstat)
1107 1102 return not match
1108 1103 else:
1109 1104 followsym = False
1110 1105
1111 1106 if (seen_dirs is None) and followsym:
1112 1107 seen_dirs = []
1113 1108 _add_dir_if_not_there(seen_dirs, path)
1114 1109 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1115 1110 dirs.sort()
1116 1111 if '.hg' in dirs:
1117 1112 yield root # found a repository
1118 1113 qroot = os.path.join(root, '.hg', 'patches')
1119 1114 if os.path.isdir(os.path.join(qroot, '.hg')):
1120 1115 yield qroot # we have a patch queue repo here
1121 1116 if recurse:
1122 1117 # avoid recursing inside the .hg directory
1123 1118 dirs.remove('.hg')
1124 1119 else:
1125 1120 dirs[:] = [] # don't descend further
1126 1121 elif followsym:
1127 1122 newdirs = []
1128 1123 for d in dirs:
1129 1124 fname = os.path.join(root, d)
1130 1125 if _add_dir_if_not_there(seen_dirs, fname):
1131 1126 if os.path.islink(fname):
1132 1127 for hgname in walkrepos(fname, True, seen_dirs):
1133 1128 yield hgname
1134 1129 else:
1135 1130 newdirs.append(d)
1136 1131 dirs[:] = newdirs
1137 1132
1138 1133 _rcpath = None
1139 1134
1140 1135 def os_rcpath():
1141 1136 '''return default os-specific hgrc search path'''
1142 1137 path = system_rcpath()
1143 1138 path.extend(user_rcpath())
1144 1139 path = [os.path.normpath(f) for f in path]
1145 1140 return path
1146 1141
1147 1142 def rcpath():
1148 1143 '''return hgrc search path. if env var HGRCPATH is set, use it.
1149 1144 for each item in path, if directory, use files ending in .rc,
1150 1145 else use item.
1151 1146 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1152 1147 if no HGRCPATH, use default os-specific path.'''
1153 1148 global _rcpath
1154 1149 if _rcpath is None:
1155 1150 if 'HGRCPATH' in os.environ:
1156 1151 _rcpath = []
1157 1152 for p in os.environ['HGRCPATH'].split(os.pathsep):
1158 1153 if not p:
1159 1154 continue
1160 1155 p = expandpath(p)
1161 1156 if os.path.isdir(p):
1162 1157 for f, kind in osutil.listdir(p):
1163 1158 if f.endswith('.rc'):
1164 1159 _rcpath.append(os.path.join(p, f))
1165 1160 else:
1166 1161 _rcpath.append(p)
1167 1162 else:
1168 1163 _rcpath = os_rcpath()
1169 1164 return _rcpath
1170 1165
1171 1166 def bytecount(nbytes):
1172 1167 '''return byte count formatted as readable string, with units'''
1173 1168
1174 1169 units = (
1175 1170 (100, 1 << 30, _('%.0f GB')),
1176 1171 (10, 1 << 30, _('%.1f GB')),
1177 1172 (1, 1 << 30, _('%.2f GB')),
1178 1173 (100, 1 << 20, _('%.0f MB')),
1179 1174 (10, 1 << 20, _('%.1f MB')),
1180 1175 (1, 1 << 20, _('%.2f MB')),
1181 1176 (100, 1 << 10, _('%.0f KB')),
1182 1177 (10, 1 << 10, _('%.1f KB')),
1183 1178 (1, 1 << 10, _('%.2f KB')),
1184 1179 (1, 1, _('%.0f bytes')),
1185 1180 )
1186 1181
1187 1182 for multiplier, divisor, format in units:
1188 1183 if nbytes >= divisor * multiplier:
1189 1184 return format % (nbytes / float(divisor))
1190 1185 return units[-1][2] % nbytes
1191 1186
1192 1187 def uirepr(s):
1193 1188 # Avoid double backslash in Windows path repr()
1194 1189 return repr(s).replace('\\\\', '\\')
1195 1190
1196 1191 # delay import of textwrap
1197 1192 def MBTextWrapper(**kwargs):
1198 1193 class tw(textwrap.TextWrapper):
1199 1194 """
1200 1195 Extend TextWrapper for double-width characters.
1201 1196
1202 1197 Some Asian characters use two terminal columns instead of one.
1203 1198 A good example of this behavior can be seen with u'\u65e5\u672c',
1204 1199 the two Japanese characters for "Japan":
1205 1200 len() returns 2, but when printed to a terminal, they eat 4 columns.
1206 1201
1207 1202 (Note that this has nothing to do whatsoever with unicode
1208 1203 representation, or encoding of the underlying string)
1209 1204 """
1210 1205 def __init__(self, **kwargs):
1211 1206 textwrap.TextWrapper.__init__(self, **kwargs)
1212 1207
1213 1208 def _cutdown(self, str, space_left):
1214 1209 l = 0
1215 1210 ucstr = unicode(str, encoding.encoding)
1216 1211 colwidth = unicodedata.east_asian_width
1217 1212 for i in xrange(len(ucstr)):
1218 1213 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1219 1214 if space_left < l:
1220 1215 return (ucstr[:i].encode(encoding.encoding),
1221 1216 ucstr[i:].encode(encoding.encoding))
1222 1217 return str, ''
1223 1218
1224 1219 # overriding of base class
1225 1220 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1226 1221 space_left = max(width - cur_len, 1)
1227 1222
1228 1223 if self.break_long_words:
1229 1224 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1230 1225 cur_line.append(cut)
1231 1226 reversed_chunks[-1] = res
1232 1227 elif not cur_line:
1233 1228 cur_line.append(reversed_chunks.pop())
1234 1229
1235 1230 global MBTextWrapper
1236 1231 MBTextWrapper = tw
1237 1232 return tw(**kwargs)
1238 1233
1239 1234 def wrap(line, width, initindent='', hangindent=''):
1240 1235 maxindent = max(len(hangindent), len(initindent))
1241 1236 if width <= maxindent:
1242 1237 # adjust for weird terminal size
1243 1238 width = max(78, maxindent + 1)
1244 1239 wrapper = MBTextWrapper(width=width,
1245 1240 initial_indent=initindent,
1246 1241 subsequent_indent=hangindent)
1247 1242 return wrapper.fill(line)
1248 1243
1249 1244 def iterlines(iterator):
1250 1245 for chunk in iterator:
1251 1246 for line in chunk.splitlines():
1252 1247 yield line
1253 1248
1254 1249 def expandpath(path):
1255 1250 return os.path.expanduser(os.path.expandvars(path))
1256 1251
1257 1252 def hgcmd():
1258 1253 """Return the command used to execute current hg
1259 1254
1260 1255 This is different from hgexecutable() because on Windows we want
1261 1256 to avoid things opening new shell windows like batch files, so we
1262 1257 get either the python call or current executable.
1263 1258 """
1264 1259 if main_is_frozen():
1265 1260 return [sys.executable]
1266 1261 return gethgcmd()
1267 1262
1268 1263 def rundetached(args, condfn):
1269 1264 """Execute the argument list in a detached process.
1270 1265
1271 1266 condfn is a callable which is called repeatedly and should return
1272 1267 True once the child process is known to have started successfully.
1273 1268 At this point, the child process PID is returned. If the child
1274 1269 process fails to start or finishes before condfn() evaluates to
1275 1270 True, return -1.
1276 1271 """
1277 1272 # Windows case is easier because the child process is either
1278 1273 # successfully starting and validating the condition or exiting
1279 1274 # on failure. We just poll on its PID. On Unix, if the child
1280 1275 # process fails to start, it will be left in a zombie state until
1281 1276 # the parent wait on it, which we cannot do since we expect a long
1282 1277 # running process on success. Instead we listen for SIGCHLD telling
1283 1278 # us our child process terminated.
1284 1279 terminated = set()
1285 1280 def handler(signum, frame):
1286 1281 terminated.add(os.wait())
1287 1282 prevhandler = None
1288 1283 if hasattr(signal, 'SIGCHLD'):
1289 1284 prevhandler = signal.signal(signal.SIGCHLD, handler)
1290 1285 try:
1291 1286 pid = spawndetached(args)
1292 1287 while not condfn():
1293 1288 if ((pid in terminated or not testpid(pid))
1294 1289 and not condfn()):
1295 1290 return -1
1296 1291 time.sleep(0.1)
1297 1292 return pid
1298 1293 finally:
1299 1294 if prevhandler is not None:
1300 1295 signal.signal(signal.SIGCHLD, prevhandler)
1301 1296
1302 1297 try:
1303 1298 any, all = any, all
1304 1299 except NameError:
1305 1300 def any(iterable):
1306 1301 for i in iterable:
1307 1302 if i:
1308 1303 return True
1309 1304 return False
1310 1305
1311 1306 def all(iterable):
1312 1307 for i in iterable:
1313 1308 if not i:
1314 1309 return False
1315 1310 return True
1316 1311
1317 1312 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1318 1313 """Return the result of interpolating items in the mapping into string s.
1319 1314
1320 1315 prefix is a single character string, or a two character string with
1321 1316 a backslash as the first character if the prefix needs to be escaped in
1322 1317 a regular expression.
1323 1318
1324 1319 fn is an optional function that will be applied to the replacement text
1325 1320 just before replacement.
1326 1321
1327 1322 escape_prefix is an optional flag that allows using doubled prefix for
1328 1323 its escaping.
1329 1324 """
1330 1325 fn = fn or (lambda s: s)
1331 1326 patterns = '|'.join(mapping.keys())
1332 1327 if escape_prefix:
1333 1328 patterns += '|' + prefix
1334 1329 if len(prefix) > 1:
1335 1330 prefix_char = prefix[1:]
1336 1331 else:
1337 1332 prefix_char = prefix
1338 1333 mapping[prefix_char] = prefix_char
1339 1334 r = re.compile(r'%s(%s)' % (prefix, patterns))
1340 1335 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1341 1336
1342 1337 def getport(port):
1343 1338 """Return the port for a given network service.
1344 1339
1345 1340 If port is an integer, it's returned as is. If it's a string, it's
1346 1341 looked up using socket.getservbyname(). If there's no matching
1347 1342 service, util.Abort is raised.
1348 1343 """
1349 1344 try:
1350 1345 return int(port)
1351 1346 except ValueError:
1352 1347 pass
1353 1348
1354 1349 try:
1355 1350 return socket.getservbyname(port)
1356 1351 except socket.error:
1357 1352 raise Abort(_("no port number associated with service '%s'") % port)
1358 1353
1359 1354 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1360 1355 '0': False, 'no': False, 'false': False, 'off': False,
1361 1356 'never': False}
1362 1357
1363 1358 def parsebool(s):
1364 1359 """Parse s into a boolean.
1365 1360
1366 1361 If s is not a valid boolean, returns None.
1367 1362 """
1368 1363 return _booleans.get(s.lower(), None)
General Comments 0
You need to be logged in to leave comments. Login now