##// END OF EJS Templates
util: extract stub function to get mtime with second accuracy...
Yuya Nishihara -
r26492:3a0bb613 default
parent child Browse files
Show More
@@ -1,1929 +1,1929 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 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, nullrev, wdirid, short, hex, bin
9 9 from i18n import _
10 10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 11 import match as matchmod
12 12 import os, errno, stat
13 13 import obsolete as obsmod
14 14 import repoview
15 15 import fileset
16 16 import revlog
17 17
18 18 propertycache = util.propertycache
19 19
20 20 # Phony node value to stand-in for new files in some uses of
21 21 # manifests. Manifests support 21-byte hashes for nodes which are
22 22 # dirty in the working copy.
23 23 _newnode = '!' * 21
24 24
25 25 class basectx(object):
26 26 """A basectx object represents the common logic for its children:
27 27 changectx: read-only context that is already present in the repo,
28 28 workingctx: a context that represents the working directory and can
29 29 be committed,
30 30 memctx: a context that represents changes in-memory and can also
31 31 be committed."""
32 32 def __new__(cls, repo, changeid='', *args, **kwargs):
33 33 if isinstance(changeid, basectx):
34 34 return changeid
35 35
36 36 o = super(basectx, cls).__new__(cls)
37 37
38 38 o._repo = repo
39 39 o._rev = nullrev
40 40 o._node = nullid
41 41
42 42 return o
43 43
44 44 def __str__(self):
45 45 return short(self.node())
46 46
47 47 def __int__(self):
48 48 return self.rev()
49 49
50 50 def __repr__(self):
51 51 return "<%s %s>" % (type(self).__name__, str(self))
52 52
53 53 def __eq__(self, other):
54 54 try:
55 55 return type(self) == type(other) and self._rev == other._rev
56 56 except AttributeError:
57 57 return False
58 58
59 59 def __ne__(self, other):
60 60 return not (self == other)
61 61
62 62 def __contains__(self, key):
63 63 return key in self._manifest
64 64
65 65 def __getitem__(self, key):
66 66 return self.filectx(key)
67 67
68 68 def __iter__(self):
69 69 return iter(self._manifest)
70 70
71 71 def _manifestmatches(self, match, s):
72 72 """generate a new manifest filtered by the match argument
73 73
74 74 This method is for internal use only and mainly exists to provide an
75 75 object oriented way for other contexts to customize the manifest
76 76 generation.
77 77 """
78 78 return self.manifest().matches(match)
79 79
80 80 def _matchstatus(self, other, match):
81 81 """return match.always if match is none
82 82
83 83 This internal method provides a way for child objects to override the
84 84 match operator.
85 85 """
86 86 return match or matchmod.always(self._repo.root, self._repo.getcwd())
87 87
88 88 def _buildstatus(self, other, s, match, listignored, listclean,
89 89 listunknown):
90 90 """build a status with respect to another context"""
91 91 # Load earliest manifest first for caching reasons. More specifically,
92 92 # if you have revisions 1000 and 1001, 1001 is probably stored as a
93 93 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
94 94 # 1000 and cache it so that when you read 1001, we just need to apply a
95 95 # delta to what's in the cache. So that's one full reconstruction + one
96 96 # delta application.
97 97 if self.rev() is not None and self.rev() < other.rev():
98 98 self.manifest()
99 99 mf1 = other._manifestmatches(match, s)
100 100 mf2 = self._manifestmatches(match, s)
101 101
102 102 modified, added = [], []
103 103 removed = []
104 104 clean = []
105 105 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
106 106 deletedset = set(deleted)
107 107 d = mf1.diff(mf2, clean=listclean)
108 108 for fn, value in d.iteritems():
109 109 if fn in deletedset:
110 110 continue
111 111 if value is None:
112 112 clean.append(fn)
113 113 continue
114 114 (node1, flag1), (node2, flag2) = value
115 115 if node1 is None:
116 116 added.append(fn)
117 117 elif node2 is None:
118 118 removed.append(fn)
119 119 elif node2 != _newnode:
120 120 # The file was not a new file in mf2, so an entry
121 121 # from diff is really a difference.
122 122 modified.append(fn)
123 123 elif self[fn].cmp(other[fn]):
124 124 # node2 was newnode, but the working file doesn't
125 125 # match the one in mf1.
126 126 modified.append(fn)
127 127 else:
128 128 clean.append(fn)
129 129
130 130 if removed:
131 131 # need to filter files if they are already reported as removed
132 132 unknown = [fn for fn in unknown if fn not in mf1]
133 133 ignored = [fn for fn in ignored if fn not in mf1]
134 134 # if they're deleted, don't report them as removed
135 135 removed = [fn for fn in removed if fn not in deletedset]
136 136
137 137 return scmutil.status(modified, added, removed, deleted, unknown,
138 138 ignored, clean)
139 139
140 140 @propertycache
141 141 def substate(self):
142 142 return subrepo.state(self, self._repo.ui)
143 143
144 144 def subrev(self, subpath):
145 145 return self.substate[subpath][1]
146 146
147 147 def rev(self):
148 148 return self._rev
149 149 def node(self):
150 150 return self._node
151 151 def hex(self):
152 152 return hex(self.node())
153 153 def manifest(self):
154 154 return self._manifest
155 155 def repo(self):
156 156 return self._repo
157 157 def phasestr(self):
158 158 return phases.phasenames[self.phase()]
159 159 def mutable(self):
160 160 return self.phase() > phases.public
161 161
162 162 def getfileset(self, expr):
163 163 return fileset.getfileset(self, expr)
164 164
165 165 def obsolete(self):
166 166 """True if the changeset is obsolete"""
167 167 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
168 168
169 169 def extinct(self):
170 170 """True if the changeset is extinct"""
171 171 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
172 172
173 173 def unstable(self):
174 174 """True if the changeset is not obsolete but it's ancestor are"""
175 175 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
176 176
177 177 def bumped(self):
178 178 """True if the changeset try to be a successor of a public changeset
179 179
180 180 Only non-public and non-obsolete changesets may be bumped.
181 181 """
182 182 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
183 183
184 184 def divergent(self):
185 185 """Is a successors of a changeset with multiple possible successors set
186 186
187 187 Only non-public and non-obsolete changesets may be divergent.
188 188 """
189 189 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
190 190
191 191 def troubled(self):
192 192 """True if the changeset is either unstable, bumped or divergent"""
193 193 return self.unstable() or self.bumped() or self.divergent()
194 194
195 195 def troubles(self):
196 196 """return the list of troubles affecting this changesets.
197 197
198 198 Troubles are returned as strings. possible values are:
199 199 - unstable,
200 200 - bumped,
201 201 - divergent.
202 202 """
203 203 troubles = []
204 204 if self.unstable():
205 205 troubles.append('unstable')
206 206 if self.bumped():
207 207 troubles.append('bumped')
208 208 if self.divergent():
209 209 troubles.append('divergent')
210 210 return troubles
211 211
212 212 def parents(self):
213 213 """return contexts for each parent changeset"""
214 214 return self._parents
215 215
216 216 def p1(self):
217 217 return self._parents[0]
218 218
219 219 def p2(self):
220 220 if len(self._parents) == 2:
221 221 return self._parents[1]
222 222 return changectx(self._repo, -1)
223 223
224 224 def _fileinfo(self, path):
225 225 if '_manifest' in self.__dict__:
226 226 try:
227 227 return self._manifest[path], self._manifest.flags(path)
228 228 except KeyError:
229 229 raise error.ManifestLookupError(self._node, path,
230 230 _('not found in manifest'))
231 231 if '_manifestdelta' in self.__dict__ or path in self.files():
232 232 if path in self._manifestdelta:
233 233 return (self._manifestdelta[path],
234 234 self._manifestdelta.flags(path))
235 235 node, flag = self._repo.manifest.find(self._changeset[0], path)
236 236 if not node:
237 237 raise error.ManifestLookupError(self._node, path,
238 238 _('not found in manifest'))
239 239
240 240 return node, flag
241 241
242 242 def filenode(self, path):
243 243 return self._fileinfo(path)[0]
244 244
245 245 def flags(self, path):
246 246 try:
247 247 return self._fileinfo(path)[1]
248 248 except error.LookupError:
249 249 return ''
250 250
251 251 def sub(self, path):
252 252 '''return a subrepo for the stored revision of path, never wdir()'''
253 253 return subrepo.subrepo(self, path)
254 254
255 255 def nullsub(self, path, pctx):
256 256 return subrepo.nullsubrepo(self, path, pctx)
257 257
258 258 def workingsub(self, path):
259 259 '''return a subrepo for the stored revision, or wdir if this is a wdir
260 260 context.
261 261 '''
262 262 return subrepo.subrepo(self, path, allowwdir=True)
263 263
264 264 def match(self, pats=[], include=None, exclude=None, default='glob',
265 265 listsubrepos=False, badfn=None):
266 266 r = self._repo
267 267 return matchmod.match(r.root, r.getcwd(), pats,
268 268 include, exclude, default,
269 269 auditor=r.auditor, ctx=self,
270 270 listsubrepos=listsubrepos, badfn=badfn)
271 271
272 272 def diff(self, ctx2=None, match=None, **opts):
273 273 """Returns a diff generator for the given contexts and matcher"""
274 274 if ctx2 is None:
275 275 ctx2 = self.p1()
276 276 if ctx2 is not None:
277 277 ctx2 = self._repo[ctx2]
278 278 diffopts = patch.diffopts(self._repo.ui, opts)
279 279 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
280 280
281 281 def dirs(self):
282 282 return self._manifest.dirs()
283 283
284 284 def hasdir(self, dir):
285 285 return self._manifest.hasdir(dir)
286 286
287 287 def dirty(self, missing=False, merge=True, branch=True):
288 288 return False
289 289
290 290 def status(self, other=None, match=None, listignored=False,
291 291 listclean=False, listunknown=False, listsubrepos=False):
292 292 """return status of files between two nodes or node and working
293 293 directory.
294 294
295 295 If other is None, compare this node with working directory.
296 296
297 297 returns (modified, added, removed, deleted, unknown, ignored, clean)
298 298 """
299 299
300 300 ctx1 = self
301 301 ctx2 = self._repo[other]
302 302
303 303 # This next code block is, admittedly, fragile logic that tests for
304 304 # reversing the contexts and wouldn't need to exist if it weren't for
305 305 # the fast (and common) code path of comparing the working directory
306 306 # with its first parent.
307 307 #
308 308 # What we're aiming for here is the ability to call:
309 309 #
310 310 # workingctx.status(parentctx)
311 311 #
312 312 # If we always built the manifest for each context and compared those,
313 313 # then we'd be done. But the special case of the above call means we
314 314 # just copy the manifest of the parent.
315 315 reversed = False
316 316 if (not isinstance(ctx1, changectx)
317 317 and isinstance(ctx2, changectx)):
318 318 reversed = True
319 319 ctx1, ctx2 = ctx2, ctx1
320 320
321 321 match = ctx2._matchstatus(ctx1, match)
322 322 r = scmutil.status([], [], [], [], [], [], [])
323 323 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
324 324 listunknown)
325 325
326 326 if reversed:
327 327 # Reverse added and removed. Clear deleted, unknown and ignored as
328 328 # these make no sense to reverse.
329 329 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
330 330 r.clean)
331 331
332 332 if listsubrepos:
333 333 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
334 334 rev2 = ctx2.subrev(subpath)
335 335 try:
336 336 submatch = matchmod.narrowmatcher(subpath, match)
337 337 s = sub.status(rev2, match=submatch, ignored=listignored,
338 338 clean=listclean, unknown=listunknown,
339 339 listsubrepos=True)
340 340 for rfiles, sfiles in zip(r, s):
341 341 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
342 342 except error.LookupError:
343 343 self._repo.ui.status(_("skipping missing "
344 344 "subrepository: %s\n") % subpath)
345 345
346 346 for l in r:
347 347 l.sort()
348 348
349 349 return r
350 350
351 351
352 352 def makememctx(repo, parents, text, user, date, branch, files, store,
353 353 editor=None, extra=None):
354 354 def getfilectx(repo, memctx, path):
355 355 data, mode, copied = store.getfile(path)
356 356 if data is None:
357 357 return None
358 358 islink, isexec = mode
359 359 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
360 360 copied=copied, memctx=memctx)
361 361 if extra is None:
362 362 extra = {}
363 363 if branch:
364 364 extra['branch'] = encoding.fromlocal(branch)
365 365 ctx = memctx(repo, parents, text, files, getfilectx, user,
366 366 date, extra, editor)
367 367 return ctx
368 368
369 369 class changectx(basectx):
370 370 """A changecontext object makes access to data related to a particular
371 371 changeset convenient. It represents a read-only context already present in
372 372 the repo."""
373 373 def __init__(self, repo, changeid=''):
374 374 """changeid is a revision number, node, or tag"""
375 375
376 376 # since basectx.__new__ already took care of copying the object, we
377 377 # don't need to do anything in __init__, so we just exit here
378 378 if isinstance(changeid, basectx):
379 379 return
380 380
381 381 if changeid == '':
382 382 changeid = '.'
383 383 self._repo = repo
384 384
385 385 try:
386 386 if isinstance(changeid, int):
387 387 self._node = repo.changelog.node(changeid)
388 388 self._rev = changeid
389 389 return
390 390 if isinstance(changeid, long):
391 391 changeid = str(changeid)
392 392 if changeid == 'null':
393 393 self._node = nullid
394 394 self._rev = nullrev
395 395 return
396 396 if changeid == 'tip':
397 397 self._node = repo.changelog.tip()
398 398 self._rev = repo.changelog.rev(self._node)
399 399 return
400 400 if changeid == '.' or changeid == repo.dirstate.p1():
401 401 # this is a hack to delay/avoid loading obsmarkers
402 402 # when we know that '.' won't be hidden
403 403 self._node = repo.dirstate.p1()
404 404 self._rev = repo.unfiltered().changelog.rev(self._node)
405 405 return
406 406 if len(changeid) == 20:
407 407 try:
408 408 self._node = changeid
409 409 self._rev = repo.changelog.rev(changeid)
410 410 return
411 411 except error.FilteredRepoLookupError:
412 412 raise
413 413 except LookupError:
414 414 pass
415 415
416 416 try:
417 417 r = int(changeid)
418 418 if str(r) != changeid:
419 419 raise ValueError
420 420 l = len(repo.changelog)
421 421 if r < 0:
422 422 r += l
423 423 if r < 0 or r >= l:
424 424 raise ValueError
425 425 self._rev = r
426 426 self._node = repo.changelog.node(r)
427 427 return
428 428 except error.FilteredIndexError:
429 429 raise
430 430 except (ValueError, OverflowError, IndexError):
431 431 pass
432 432
433 433 if len(changeid) == 40:
434 434 try:
435 435 self._node = bin(changeid)
436 436 self._rev = repo.changelog.rev(self._node)
437 437 return
438 438 except error.FilteredLookupError:
439 439 raise
440 440 except (TypeError, LookupError):
441 441 pass
442 442
443 443 # lookup bookmarks through the name interface
444 444 try:
445 445 self._node = repo.names.singlenode(repo, changeid)
446 446 self._rev = repo.changelog.rev(self._node)
447 447 return
448 448 except KeyError:
449 449 pass
450 450 except error.FilteredRepoLookupError:
451 451 raise
452 452 except error.RepoLookupError:
453 453 pass
454 454
455 455 self._node = repo.unfiltered().changelog._partialmatch(changeid)
456 456 if self._node is not None:
457 457 self._rev = repo.changelog.rev(self._node)
458 458 return
459 459
460 460 # lookup failed
461 461 # check if it might have come from damaged dirstate
462 462 #
463 463 # XXX we could avoid the unfiltered if we had a recognizable
464 464 # exception for filtered changeset access
465 465 if changeid in repo.unfiltered().dirstate.parents():
466 466 msg = _("working directory has unknown parent '%s'!")
467 467 raise error.Abort(msg % short(changeid))
468 468 try:
469 469 if len(changeid) == 20:
470 470 changeid = hex(changeid)
471 471 except TypeError:
472 472 pass
473 473 except (error.FilteredIndexError, error.FilteredLookupError,
474 474 error.FilteredRepoLookupError):
475 475 if repo.filtername.startswith('visible'):
476 476 msg = _("hidden revision '%s'") % changeid
477 477 hint = _('use --hidden to access hidden revisions')
478 478 raise error.FilteredRepoLookupError(msg, hint=hint)
479 479 msg = _("filtered revision '%s' (not in '%s' subset)")
480 480 msg %= (changeid, repo.filtername)
481 481 raise error.FilteredRepoLookupError(msg)
482 482 except IndexError:
483 483 pass
484 484 raise error.RepoLookupError(
485 485 _("unknown revision '%s'") % changeid)
486 486
487 487 def __hash__(self):
488 488 try:
489 489 return hash(self._rev)
490 490 except AttributeError:
491 491 return id(self)
492 492
493 493 def __nonzero__(self):
494 494 return self._rev != nullrev
495 495
496 496 @propertycache
497 497 def _changeset(self):
498 498 return self._repo.changelog.read(self.rev())
499 499
500 500 @propertycache
501 501 def _manifest(self):
502 502 return self._repo.manifest.read(self._changeset[0])
503 503
504 504 @propertycache
505 505 def _manifestdelta(self):
506 506 return self._repo.manifest.readdelta(self._changeset[0])
507 507
508 508 @propertycache
509 509 def _parents(self):
510 510 p = self._repo.changelog.parentrevs(self._rev)
511 511 if p[1] == nullrev:
512 512 p = p[:-1]
513 513 return [changectx(self._repo, x) for x in p]
514 514
515 515 def changeset(self):
516 516 return self._changeset
517 517 def manifestnode(self):
518 518 return self._changeset[0]
519 519
520 520 def user(self):
521 521 return self._changeset[1]
522 522 def date(self):
523 523 return self._changeset[2]
524 524 def files(self):
525 525 return self._changeset[3]
526 526 def description(self):
527 527 return self._changeset[4]
528 528 def branch(self):
529 529 return encoding.tolocal(self._changeset[5].get("branch"))
530 530 def closesbranch(self):
531 531 return 'close' in self._changeset[5]
532 532 def extra(self):
533 533 return self._changeset[5]
534 534 def tags(self):
535 535 return self._repo.nodetags(self._node)
536 536 def bookmarks(self):
537 537 return self._repo.nodebookmarks(self._node)
538 538 def phase(self):
539 539 return self._repo._phasecache.phase(self._repo, self._rev)
540 540 def hidden(self):
541 541 return self._rev in repoview.filterrevs(self._repo, 'visible')
542 542
543 543 def children(self):
544 544 """return contexts for each child changeset"""
545 545 c = self._repo.changelog.children(self._node)
546 546 return [changectx(self._repo, x) for x in c]
547 547
548 548 def ancestors(self):
549 549 for a in self._repo.changelog.ancestors([self._rev]):
550 550 yield changectx(self._repo, a)
551 551
552 552 def descendants(self):
553 553 for d in self._repo.changelog.descendants([self._rev]):
554 554 yield changectx(self._repo, d)
555 555
556 556 def filectx(self, path, fileid=None, filelog=None):
557 557 """get a file context from this changeset"""
558 558 if fileid is None:
559 559 fileid = self.filenode(path)
560 560 return filectx(self._repo, path, fileid=fileid,
561 561 changectx=self, filelog=filelog)
562 562
563 563 def ancestor(self, c2, warn=False):
564 564 """return the "best" ancestor context of self and c2
565 565
566 566 If there are multiple candidates, it will show a message and check
567 567 merge.preferancestor configuration before falling back to the
568 568 revlog ancestor."""
569 569 # deal with workingctxs
570 570 n2 = c2._node
571 571 if n2 is None:
572 572 n2 = c2._parents[0]._node
573 573 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
574 574 if not cahs:
575 575 anc = nullid
576 576 elif len(cahs) == 1:
577 577 anc = cahs[0]
578 578 else:
579 579 # experimental config: merge.preferancestor
580 580 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
581 581 try:
582 582 ctx = changectx(self._repo, r)
583 583 except error.RepoLookupError:
584 584 continue
585 585 anc = ctx.node()
586 586 if anc in cahs:
587 587 break
588 588 else:
589 589 anc = self._repo.changelog.ancestor(self._node, n2)
590 590 if warn:
591 591 self._repo.ui.status(
592 592 (_("note: using %s as ancestor of %s and %s\n") %
593 593 (short(anc), short(self._node), short(n2))) +
594 594 ''.join(_(" alternatively, use --config "
595 595 "merge.preferancestor=%s\n") %
596 596 short(n) for n in sorted(cahs) if n != anc))
597 597 return changectx(self._repo, anc)
598 598
599 599 def descendant(self, other):
600 600 """True if other is descendant of this changeset"""
601 601 return self._repo.changelog.descendant(self._rev, other._rev)
602 602
603 603 def walk(self, match):
604 604 '''Generates matching file names.'''
605 605
606 606 # Wrap match.bad method to have message with nodeid
607 607 def bad(fn, msg):
608 608 # The manifest doesn't know about subrepos, so don't complain about
609 609 # paths into valid subrepos.
610 610 if any(fn == s or fn.startswith(s + '/')
611 611 for s in self.substate):
612 612 return
613 613 match.bad(fn, _('no such file in rev %s') % self)
614 614
615 615 m = matchmod.badmatch(match, bad)
616 616 return self._manifest.walk(m)
617 617
618 618 def matches(self, match):
619 619 return self.walk(match)
620 620
621 621 class basefilectx(object):
622 622 """A filecontext object represents the common logic for its children:
623 623 filectx: read-only access to a filerevision that is already present
624 624 in the repo,
625 625 workingfilectx: a filecontext that represents files from the working
626 626 directory,
627 627 memfilectx: a filecontext that represents files in-memory."""
628 628 def __new__(cls, repo, path, *args, **kwargs):
629 629 return super(basefilectx, cls).__new__(cls)
630 630
631 631 @propertycache
632 632 def _filelog(self):
633 633 return self._repo.file(self._path)
634 634
635 635 @propertycache
636 636 def _changeid(self):
637 637 if '_changeid' in self.__dict__:
638 638 return self._changeid
639 639 elif '_changectx' in self.__dict__:
640 640 return self._changectx.rev()
641 641 elif '_descendantrev' in self.__dict__:
642 642 # this file context was created from a revision with a known
643 643 # descendant, we can (lazily) correct for linkrev aliases
644 644 return self._adjustlinkrev(self._path, self._filelog,
645 645 self._filenode, self._descendantrev)
646 646 else:
647 647 return self._filelog.linkrev(self._filerev)
648 648
649 649 @propertycache
650 650 def _filenode(self):
651 651 if '_fileid' in self.__dict__:
652 652 return self._filelog.lookup(self._fileid)
653 653 else:
654 654 return self._changectx.filenode(self._path)
655 655
656 656 @propertycache
657 657 def _filerev(self):
658 658 return self._filelog.rev(self._filenode)
659 659
660 660 @propertycache
661 661 def _repopath(self):
662 662 return self._path
663 663
664 664 def __nonzero__(self):
665 665 try:
666 666 self._filenode
667 667 return True
668 668 except error.LookupError:
669 669 # file is missing
670 670 return False
671 671
672 672 def __str__(self):
673 673 return "%s@%s" % (self.path(), self._changectx)
674 674
675 675 def __repr__(self):
676 676 return "<%s %s>" % (type(self).__name__, str(self))
677 677
678 678 def __hash__(self):
679 679 try:
680 680 return hash((self._path, self._filenode))
681 681 except AttributeError:
682 682 return id(self)
683 683
684 684 def __eq__(self, other):
685 685 try:
686 686 return (type(self) == type(other) and self._path == other._path
687 687 and self._filenode == other._filenode)
688 688 except AttributeError:
689 689 return False
690 690
691 691 def __ne__(self, other):
692 692 return not (self == other)
693 693
694 694 def filerev(self):
695 695 return self._filerev
696 696 def filenode(self):
697 697 return self._filenode
698 698 def flags(self):
699 699 return self._changectx.flags(self._path)
700 700 def filelog(self):
701 701 return self._filelog
702 702 def rev(self):
703 703 return self._changeid
704 704 def linkrev(self):
705 705 return self._filelog.linkrev(self._filerev)
706 706 def node(self):
707 707 return self._changectx.node()
708 708 def hex(self):
709 709 return self._changectx.hex()
710 710 def user(self):
711 711 return self._changectx.user()
712 712 def date(self):
713 713 return self._changectx.date()
714 714 def files(self):
715 715 return self._changectx.files()
716 716 def description(self):
717 717 return self._changectx.description()
718 718 def branch(self):
719 719 return self._changectx.branch()
720 720 def extra(self):
721 721 return self._changectx.extra()
722 722 def phase(self):
723 723 return self._changectx.phase()
724 724 def phasestr(self):
725 725 return self._changectx.phasestr()
726 726 def manifest(self):
727 727 return self._changectx.manifest()
728 728 def changectx(self):
729 729 return self._changectx
730 730 def repo(self):
731 731 return self._repo
732 732
733 733 def path(self):
734 734 return self._path
735 735
736 736 def isbinary(self):
737 737 try:
738 738 return util.binary(self.data())
739 739 except IOError:
740 740 return False
741 741 def isexec(self):
742 742 return 'x' in self.flags()
743 743 def islink(self):
744 744 return 'l' in self.flags()
745 745
746 746 def cmp(self, fctx):
747 747 """compare with other file context
748 748
749 749 returns True if different than fctx.
750 750 """
751 751 if (fctx._filerev is None
752 752 and (self._repo._encodefilterpats
753 753 # if file data starts with '\1\n', empty metadata block is
754 754 # prepended, which adds 4 bytes to filelog.size().
755 755 or self.size() - 4 == fctx.size())
756 756 or self.size() == fctx.size()):
757 757 return self._filelog.cmp(self._filenode, fctx.data())
758 758
759 759 return True
760 760
761 761 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
762 762 """return the first ancestor of <srcrev> introducing <fnode>
763 763
764 764 If the linkrev of the file revision does not point to an ancestor of
765 765 srcrev, we'll walk down the ancestors until we find one introducing
766 766 this file revision.
767 767
768 768 :repo: a localrepository object (used to access changelog and manifest)
769 769 :path: the file path
770 770 :fnode: the nodeid of the file revision
771 771 :filelog: the filelog of this path
772 772 :srcrev: the changeset revision we search ancestors from
773 773 :inclusive: if true, the src revision will also be checked
774 774 """
775 775 repo = self._repo
776 776 cl = repo.unfiltered().changelog
777 777 ma = repo.manifest
778 778 # fetch the linkrev
779 779 fr = filelog.rev(fnode)
780 780 lkr = filelog.linkrev(fr)
781 781 # hack to reuse ancestor computation when searching for renames
782 782 memberanc = getattr(self, '_ancestrycontext', None)
783 783 iteranc = None
784 784 if srcrev is None:
785 785 # wctx case, used by workingfilectx during mergecopy
786 786 revs = [p.rev() for p in self._repo[None].parents()]
787 787 inclusive = True # we skipped the real (revless) source
788 788 else:
789 789 revs = [srcrev]
790 790 if memberanc is None:
791 791 memberanc = iteranc = cl.ancestors(revs, lkr,
792 792 inclusive=inclusive)
793 793 # check if this linkrev is an ancestor of srcrev
794 794 if lkr not in memberanc:
795 795 if iteranc is None:
796 796 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
797 797 for a in iteranc:
798 798 ac = cl.read(a) # get changeset data (we avoid object creation)
799 799 if path in ac[3]: # checking the 'files' field.
800 800 # The file has been touched, check if the content is
801 801 # similar to the one we search for.
802 802 if fnode == ma.readfast(ac[0]).get(path):
803 803 return a
804 804 # In theory, we should never get out of that loop without a result.
805 805 # But if manifest uses a buggy file revision (not children of the
806 806 # one it replaces) we could. Such a buggy situation will likely
807 807 # result is crash somewhere else at to some point.
808 808 return lkr
809 809
810 810 def introrev(self):
811 811 """return the rev of the changeset which introduced this file revision
812 812
813 813 This method is different from linkrev because it take into account the
814 814 changeset the filectx was created from. It ensures the returned
815 815 revision is one of its ancestors. This prevents bugs from
816 816 'linkrev-shadowing' when a file revision is used by multiple
817 817 changesets.
818 818 """
819 819 lkr = self.linkrev()
820 820 attrs = vars(self)
821 821 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
822 822 if noctx or self.rev() == lkr:
823 823 return self.linkrev()
824 824 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
825 825 self.rev(), inclusive=True)
826 826
827 827 def _parentfilectx(self, path, fileid, filelog):
828 828 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
829 829 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
830 830 if '_changeid' in vars(self) or '_changectx' in vars(self):
831 831 # If self is associated with a changeset (probably explicitly
832 832 # fed), ensure the created filectx is associated with a
833 833 # changeset that is an ancestor of self.changectx.
834 834 # This lets us later use _adjustlinkrev to get a correct link.
835 835 fctx._descendantrev = self.rev()
836 836 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
837 837 elif '_descendantrev' in vars(self):
838 838 # Otherwise propagate _descendantrev if we have one associated.
839 839 fctx._descendantrev = self._descendantrev
840 840 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
841 841 return fctx
842 842
843 843 def parents(self):
844 844 _path = self._path
845 845 fl = self._filelog
846 846 parents = self._filelog.parents(self._filenode)
847 847 pl = [(_path, node, fl) for node in parents if node != nullid]
848 848
849 849 r = fl.renamed(self._filenode)
850 850 if r:
851 851 # - In the simple rename case, both parent are nullid, pl is empty.
852 852 # - In case of merge, only one of the parent is null id and should
853 853 # be replaced with the rename information. This parent is -always-
854 854 # the first one.
855 855 #
856 856 # As null id have always been filtered out in the previous list
857 857 # comprehension, inserting to 0 will always result in "replacing
858 858 # first nullid parent with rename information.
859 859 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
860 860
861 861 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
862 862
863 863 def p1(self):
864 864 return self.parents()[0]
865 865
866 866 def p2(self):
867 867 p = self.parents()
868 868 if len(p) == 2:
869 869 return p[1]
870 870 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
871 871
872 872 def annotate(self, follow=False, linenumber=None, diffopts=None):
873 873 '''returns a list of tuples of (ctx, line) for each line
874 874 in the file, where ctx is the filectx of the node where
875 875 that line was last changed.
876 876 This returns tuples of ((ctx, linenumber), line) for each line,
877 877 if "linenumber" parameter is NOT "None".
878 878 In such tuples, linenumber means one at the first appearance
879 879 in the managed file.
880 880 To reduce annotation cost,
881 881 this returns fixed value(False is used) as linenumber,
882 882 if "linenumber" parameter is "False".'''
883 883
884 884 if linenumber is None:
885 885 def decorate(text, rev):
886 886 return ([rev] * len(text.splitlines()), text)
887 887 elif linenumber:
888 888 def decorate(text, rev):
889 889 size = len(text.splitlines())
890 890 return ([(rev, i) for i in xrange(1, size + 1)], text)
891 891 else:
892 892 def decorate(text, rev):
893 893 return ([(rev, False)] * len(text.splitlines()), text)
894 894
895 895 def pair(parent, child):
896 896 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
897 897 refine=True)
898 898 for (a1, a2, b1, b2), t in blocks:
899 899 # Changed blocks ('!') or blocks made only of blank lines ('~')
900 900 # belong to the child.
901 901 if t == '=':
902 902 child[0][b1:b2] = parent[0][a1:a2]
903 903 return child
904 904
905 905 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
906 906
907 907 def parents(f):
908 908 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
909 909 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
910 910 # from the topmost introrev (= srcrev) down to p.linkrev() if it
911 911 # isn't an ancestor of the srcrev.
912 912 f._changeid
913 913 pl = f.parents()
914 914
915 915 # Don't return renamed parents if we aren't following.
916 916 if not follow:
917 917 pl = [p for p in pl if p.path() == f.path()]
918 918
919 919 # renamed filectx won't have a filelog yet, so set it
920 920 # from the cache to save time
921 921 for p in pl:
922 922 if not '_filelog' in p.__dict__:
923 923 p._filelog = getlog(p.path())
924 924
925 925 return pl
926 926
927 927 # use linkrev to find the first changeset where self appeared
928 928 base = self
929 929 introrev = self.introrev()
930 930 if self.rev() != introrev:
931 931 base = self.filectx(self.filenode(), changeid=introrev)
932 932 if getattr(base, '_ancestrycontext', None) is None:
933 933 cl = self._repo.changelog
934 934 if introrev is None:
935 935 # wctx is not inclusive, but works because _ancestrycontext
936 936 # is used to test filelog revisions
937 937 ac = cl.ancestors([p.rev() for p in base.parents()],
938 938 inclusive=True)
939 939 else:
940 940 ac = cl.ancestors([introrev], inclusive=True)
941 941 base._ancestrycontext = ac
942 942
943 943 # This algorithm would prefer to be recursive, but Python is a
944 944 # bit recursion-hostile. Instead we do an iterative
945 945 # depth-first search.
946 946
947 947 visit = [base]
948 948 hist = {}
949 949 pcache = {}
950 950 needed = {base: 1}
951 951 while visit:
952 952 f = visit[-1]
953 953 pcached = f in pcache
954 954 if not pcached:
955 955 pcache[f] = parents(f)
956 956
957 957 ready = True
958 958 pl = pcache[f]
959 959 for p in pl:
960 960 if p not in hist:
961 961 ready = False
962 962 visit.append(p)
963 963 if not pcached:
964 964 needed[p] = needed.get(p, 0) + 1
965 965 if ready:
966 966 visit.pop()
967 967 reusable = f in hist
968 968 if reusable:
969 969 curr = hist[f]
970 970 else:
971 971 curr = decorate(f.data(), f)
972 972 for p in pl:
973 973 if not reusable:
974 974 curr = pair(hist[p], curr)
975 975 if needed[p] == 1:
976 976 del hist[p]
977 977 del needed[p]
978 978 else:
979 979 needed[p] -= 1
980 980
981 981 hist[f] = curr
982 982 pcache[f] = []
983 983
984 984 return zip(hist[base][0], hist[base][1].splitlines(True))
985 985
986 986 def ancestors(self, followfirst=False):
987 987 visit = {}
988 988 c = self
989 989 if followfirst:
990 990 cut = 1
991 991 else:
992 992 cut = None
993 993
994 994 while True:
995 995 for parent in c.parents()[:cut]:
996 996 visit[(parent.linkrev(), parent.filenode())] = parent
997 997 if not visit:
998 998 break
999 999 c = visit.pop(max(visit))
1000 1000 yield c
1001 1001
1002 1002 class filectx(basefilectx):
1003 1003 """A filecontext object makes access to data related to a particular
1004 1004 filerevision convenient."""
1005 1005 def __init__(self, repo, path, changeid=None, fileid=None,
1006 1006 filelog=None, changectx=None):
1007 1007 """changeid can be a changeset revision, node, or tag.
1008 1008 fileid can be a file revision or node."""
1009 1009 self._repo = repo
1010 1010 self._path = path
1011 1011
1012 1012 assert (changeid is not None
1013 1013 or fileid is not None
1014 1014 or changectx is not None), \
1015 1015 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1016 1016 % (changeid, fileid, changectx))
1017 1017
1018 1018 if filelog is not None:
1019 1019 self._filelog = filelog
1020 1020
1021 1021 if changeid is not None:
1022 1022 self._changeid = changeid
1023 1023 if changectx is not None:
1024 1024 self._changectx = changectx
1025 1025 if fileid is not None:
1026 1026 self._fileid = fileid
1027 1027
1028 1028 @propertycache
1029 1029 def _changectx(self):
1030 1030 try:
1031 1031 return changectx(self._repo, self._changeid)
1032 1032 except error.FilteredRepoLookupError:
1033 1033 # Linkrev may point to any revision in the repository. When the
1034 1034 # repository is filtered this may lead to `filectx` trying to build
1035 1035 # `changectx` for filtered revision. In such case we fallback to
1036 1036 # creating `changectx` on the unfiltered version of the reposition.
1037 1037 # This fallback should not be an issue because `changectx` from
1038 1038 # `filectx` are not used in complex operations that care about
1039 1039 # filtering.
1040 1040 #
1041 1041 # This fallback is a cheap and dirty fix that prevent several
1042 1042 # crashes. It does not ensure the behavior is correct. However the
1043 1043 # behavior was not correct before filtering either and "incorrect
1044 1044 # behavior" is seen as better as "crash"
1045 1045 #
1046 1046 # Linkrevs have several serious troubles with filtering that are
1047 1047 # complicated to solve. Proper handling of the issue here should be
1048 1048 # considered when solving linkrev issue are on the table.
1049 1049 return changectx(self._repo.unfiltered(), self._changeid)
1050 1050
1051 1051 def filectx(self, fileid, changeid=None):
1052 1052 '''opens an arbitrary revision of the file without
1053 1053 opening a new filelog'''
1054 1054 return filectx(self._repo, self._path, fileid=fileid,
1055 1055 filelog=self._filelog, changeid=changeid)
1056 1056
1057 1057 def data(self):
1058 1058 try:
1059 1059 return self._filelog.read(self._filenode)
1060 1060 except error.CensoredNodeError:
1061 1061 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1062 1062 return ""
1063 1063 raise util.Abort(_("censored node: %s") % short(self._filenode),
1064 1064 hint=_("set censor.policy to ignore errors"))
1065 1065
1066 1066 def size(self):
1067 1067 return self._filelog.size(self._filerev)
1068 1068
1069 1069 def renamed(self):
1070 1070 """check if file was actually renamed in this changeset revision
1071 1071
1072 1072 If rename logged in file revision, we report copy for changeset only
1073 1073 if file revisions linkrev points back to the changeset in question
1074 1074 or both changeset parents contain different file revisions.
1075 1075 """
1076 1076
1077 1077 renamed = self._filelog.renamed(self._filenode)
1078 1078 if not renamed:
1079 1079 return renamed
1080 1080
1081 1081 if self.rev() == self.linkrev():
1082 1082 return renamed
1083 1083
1084 1084 name = self.path()
1085 1085 fnode = self._filenode
1086 1086 for p in self._changectx.parents():
1087 1087 try:
1088 1088 if fnode == p.filenode(name):
1089 1089 return None
1090 1090 except error.LookupError:
1091 1091 pass
1092 1092 return renamed
1093 1093
1094 1094 def children(self):
1095 1095 # hard for renames
1096 1096 c = self._filelog.children(self._filenode)
1097 1097 return [filectx(self._repo, self._path, fileid=x,
1098 1098 filelog=self._filelog) for x in c]
1099 1099
1100 1100 class committablectx(basectx):
1101 1101 """A committablectx object provides common functionality for a context that
1102 1102 wants the ability to commit, e.g. workingctx or memctx."""
1103 1103 def __init__(self, repo, text="", user=None, date=None, extra=None,
1104 1104 changes=None):
1105 1105 self._repo = repo
1106 1106 self._rev = None
1107 1107 self._node = None
1108 1108 self._text = text
1109 1109 if date:
1110 1110 self._date = util.parsedate(date)
1111 1111 if user:
1112 1112 self._user = user
1113 1113 if changes:
1114 1114 self._status = changes
1115 1115
1116 1116 self._extra = {}
1117 1117 if extra:
1118 1118 self._extra = extra.copy()
1119 1119 if 'branch' not in self._extra:
1120 1120 try:
1121 1121 branch = encoding.fromlocal(self._repo.dirstate.branch())
1122 1122 except UnicodeDecodeError:
1123 1123 raise util.Abort(_('branch name not in UTF-8!'))
1124 1124 self._extra['branch'] = branch
1125 1125 if self._extra['branch'] == '':
1126 1126 self._extra['branch'] = 'default'
1127 1127
1128 1128 def __str__(self):
1129 1129 return str(self._parents[0]) + "+"
1130 1130
1131 1131 def __nonzero__(self):
1132 1132 return True
1133 1133
1134 1134 def _buildflagfunc(self):
1135 1135 # Create a fallback function for getting file flags when the
1136 1136 # filesystem doesn't support them
1137 1137
1138 1138 copiesget = self._repo.dirstate.copies().get
1139 1139
1140 1140 if len(self._parents) < 2:
1141 1141 # when we have one parent, it's easy: copy from parent
1142 1142 man = self._parents[0].manifest()
1143 1143 def func(f):
1144 1144 f = copiesget(f, f)
1145 1145 return man.flags(f)
1146 1146 else:
1147 1147 # merges are tricky: we try to reconstruct the unstored
1148 1148 # result from the merge (issue1802)
1149 1149 p1, p2 = self._parents
1150 1150 pa = p1.ancestor(p2)
1151 1151 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1152 1152
1153 1153 def func(f):
1154 1154 f = copiesget(f, f) # may be wrong for merges with copies
1155 1155 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1156 1156 if fl1 == fl2:
1157 1157 return fl1
1158 1158 if fl1 == fla:
1159 1159 return fl2
1160 1160 if fl2 == fla:
1161 1161 return fl1
1162 1162 return '' # punt for conflicts
1163 1163
1164 1164 return func
1165 1165
1166 1166 @propertycache
1167 1167 def _flagfunc(self):
1168 1168 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1169 1169
1170 1170 @propertycache
1171 1171 def _manifest(self):
1172 1172 """generate a manifest corresponding to the values in self._status
1173 1173
1174 1174 This reuse the file nodeid from parent, but we append an extra letter
1175 1175 when modified. Modified files get an extra 'm' while added files get
1176 1176 an extra 'a'. This is used by manifests merge to see that files
1177 1177 are different and by update logic to avoid deleting newly added files.
1178 1178 """
1179 1179
1180 1180 man1 = self._parents[0].manifest()
1181 1181 man = man1.copy()
1182 1182 if len(self._parents) > 1:
1183 1183 man2 = self.p2().manifest()
1184 1184 def getman(f):
1185 1185 if f in man1:
1186 1186 return man1
1187 1187 return man2
1188 1188 else:
1189 1189 getman = lambda f: man1
1190 1190
1191 1191 copied = self._repo.dirstate.copies()
1192 1192 ff = self._flagfunc
1193 1193 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1194 1194 for f in l:
1195 1195 orig = copied.get(f, f)
1196 1196 man[f] = getman(orig).get(orig, nullid) + i
1197 1197 try:
1198 1198 man.setflag(f, ff(f))
1199 1199 except OSError:
1200 1200 pass
1201 1201
1202 1202 for f in self._status.deleted + self._status.removed:
1203 1203 if f in man:
1204 1204 del man[f]
1205 1205
1206 1206 return man
1207 1207
1208 1208 @propertycache
1209 1209 def _status(self):
1210 1210 return self._repo.status()
1211 1211
1212 1212 @propertycache
1213 1213 def _user(self):
1214 1214 return self._repo.ui.username()
1215 1215
1216 1216 @propertycache
1217 1217 def _date(self):
1218 1218 return util.makedate()
1219 1219
1220 1220 def subrev(self, subpath):
1221 1221 return None
1222 1222
1223 1223 def manifestnode(self):
1224 1224 return None
1225 1225 def user(self):
1226 1226 return self._user or self._repo.ui.username()
1227 1227 def date(self):
1228 1228 return self._date
1229 1229 def description(self):
1230 1230 return self._text
1231 1231 def files(self):
1232 1232 return sorted(self._status.modified + self._status.added +
1233 1233 self._status.removed)
1234 1234
1235 1235 def modified(self):
1236 1236 return self._status.modified
1237 1237 def added(self):
1238 1238 return self._status.added
1239 1239 def removed(self):
1240 1240 return self._status.removed
1241 1241 def deleted(self):
1242 1242 return self._status.deleted
1243 1243 def branch(self):
1244 1244 return encoding.tolocal(self._extra['branch'])
1245 1245 def closesbranch(self):
1246 1246 return 'close' in self._extra
1247 1247 def extra(self):
1248 1248 return self._extra
1249 1249
1250 1250 def tags(self):
1251 1251 return []
1252 1252
1253 1253 def bookmarks(self):
1254 1254 b = []
1255 1255 for p in self.parents():
1256 1256 b.extend(p.bookmarks())
1257 1257 return b
1258 1258
1259 1259 def phase(self):
1260 1260 phase = phases.draft # default phase to draft
1261 1261 for p in self.parents():
1262 1262 phase = max(phase, p.phase())
1263 1263 return phase
1264 1264
1265 1265 def hidden(self):
1266 1266 return False
1267 1267
1268 1268 def children(self):
1269 1269 return []
1270 1270
1271 1271 def flags(self, path):
1272 1272 if '_manifest' in self.__dict__:
1273 1273 try:
1274 1274 return self._manifest.flags(path)
1275 1275 except KeyError:
1276 1276 return ''
1277 1277
1278 1278 try:
1279 1279 return self._flagfunc(path)
1280 1280 except OSError:
1281 1281 return ''
1282 1282
1283 1283 def ancestor(self, c2):
1284 1284 """return the "best" ancestor context of self and c2"""
1285 1285 return self._parents[0].ancestor(c2) # punt on two parents for now
1286 1286
1287 1287 def walk(self, match):
1288 1288 '''Generates matching file names.'''
1289 1289 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1290 1290 True, False))
1291 1291
1292 1292 def matches(self, match):
1293 1293 return sorted(self._repo.dirstate.matches(match))
1294 1294
1295 1295 def ancestors(self):
1296 1296 for p in self._parents:
1297 1297 yield p
1298 1298 for a in self._repo.changelog.ancestors(
1299 1299 [p.rev() for p in self._parents]):
1300 1300 yield changectx(self._repo, a)
1301 1301
1302 1302 def markcommitted(self, node):
1303 1303 """Perform post-commit cleanup necessary after committing this ctx
1304 1304
1305 1305 Specifically, this updates backing stores this working context
1306 1306 wraps to reflect the fact that the changes reflected by this
1307 1307 workingctx have been committed. For example, it marks
1308 1308 modified and added files as normal in the dirstate.
1309 1309
1310 1310 """
1311 1311
1312 1312 self._repo.dirstate.beginparentchange()
1313 1313 for f in self.modified() + self.added():
1314 1314 self._repo.dirstate.normal(f)
1315 1315 for f in self.removed():
1316 1316 self._repo.dirstate.drop(f)
1317 1317 self._repo.dirstate.setparents(node)
1318 1318 self._repo.dirstate.endparentchange()
1319 1319
1320 1320 # write changes out explicitly, because nesting wlock at
1321 1321 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1322 1322 # from immediately doing so for subsequent changing files
1323 1323 self._repo.dirstate.write()
1324 1324
1325 1325 class workingctx(committablectx):
1326 1326 """A workingctx object makes access to data related to
1327 1327 the current working directory convenient.
1328 1328 date - any valid date string or (unixtime, offset), or None.
1329 1329 user - username string, or None.
1330 1330 extra - a dictionary of extra values, or None.
1331 1331 changes - a list of file lists as returned by localrepo.status()
1332 1332 or None to use the repository status.
1333 1333 """
1334 1334 def __init__(self, repo, text="", user=None, date=None, extra=None,
1335 1335 changes=None):
1336 1336 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1337 1337
1338 1338 def __iter__(self):
1339 1339 d = self._repo.dirstate
1340 1340 for f in d:
1341 1341 if d[f] != 'r':
1342 1342 yield f
1343 1343
1344 1344 def __contains__(self, key):
1345 1345 return self._repo.dirstate[key] not in "?r"
1346 1346
1347 1347 def hex(self):
1348 1348 return hex(wdirid)
1349 1349
1350 1350 @propertycache
1351 1351 def _parents(self):
1352 1352 p = self._repo.dirstate.parents()
1353 1353 if p[1] == nullid:
1354 1354 p = p[:-1]
1355 1355 return [changectx(self._repo, x) for x in p]
1356 1356
1357 1357 def filectx(self, path, filelog=None):
1358 1358 """get a file context from the working directory"""
1359 1359 return workingfilectx(self._repo, path, workingctx=self,
1360 1360 filelog=filelog)
1361 1361
1362 1362 def dirty(self, missing=False, merge=True, branch=True):
1363 1363 "check whether a working directory is modified"
1364 1364 # check subrepos first
1365 1365 for s in sorted(self.substate):
1366 1366 if self.sub(s).dirty():
1367 1367 return True
1368 1368 # check current working dir
1369 1369 return ((merge and self.p2()) or
1370 1370 (branch and self.branch() != self.p1().branch()) or
1371 1371 self.modified() or self.added() or self.removed() or
1372 1372 (missing and self.deleted()))
1373 1373
1374 1374 def add(self, list, prefix=""):
1375 1375 join = lambda f: os.path.join(prefix, f)
1376 1376 wlock = self._repo.wlock()
1377 1377 ui, ds = self._repo.ui, self._repo.dirstate
1378 1378 try:
1379 1379 rejected = []
1380 1380 lstat = self._repo.wvfs.lstat
1381 1381 for f in list:
1382 1382 scmutil.checkportable(ui, join(f))
1383 1383 try:
1384 1384 st = lstat(f)
1385 1385 except OSError:
1386 1386 ui.warn(_("%s does not exist!\n") % join(f))
1387 1387 rejected.append(f)
1388 1388 continue
1389 1389 if st.st_size > 10000000:
1390 1390 ui.warn(_("%s: up to %d MB of RAM may be required "
1391 1391 "to manage this file\n"
1392 1392 "(use 'hg revert %s' to cancel the "
1393 1393 "pending addition)\n")
1394 1394 % (f, 3 * st.st_size // 1000000, join(f)))
1395 1395 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1396 1396 ui.warn(_("%s not added: only files and symlinks "
1397 1397 "supported currently\n") % join(f))
1398 1398 rejected.append(f)
1399 1399 elif ds[f] in 'amn':
1400 1400 ui.warn(_("%s already tracked!\n") % join(f))
1401 1401 elif ds[f] == 'r':
1402 1402 ds.normallookup(f)
1403 1403 else:
1404 1404 ds.add(f)
1405 1405 return rejected
1406 1406 finally:
1407 1407 wlock.release()
1408 1408
1409 1409 def forget(self, files, prefix=""):
1410 1410 join = lambda f: os.path.join(prefix, f)
1411 1411 wlock = self._repo.wlock()
1412 1412 try:
1413 1413 rejected = []
1414 1414 for f in files:
1415 1415 if f not in self._repo.dirstate:
1416 1416 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1417 1417 rejected.append(f)
1418 1418 elif self._repo.dirstate[f] != 'a':
1419 1419 self._repo.dirstate.remove(f)
1420 1420 else:
1421 1421 self._repo.dirstate.drop(f)
1422 1422 return rejected
1423 1423 finally:
1424 1424 wlock.release()
1425 1425
1426 1426 def undelete(self, list):
1427 1427 pctxs = self.parents()
1428 1428 wlock = self._repo.wlock()
1429 1429 try:
1430 1430 for f in list:
1431 1431 if self._repo.dirstate[f] != 'r':
1432 1432 self._repo.ui.warn(_("%s not removed!\n") % f)
1433 1433 else:
1434 1434 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1435 1435 t = fctx.data()
1436 1436 self._repo.wwrite(f, t, fctx.flags())
1437 1437 self._repo.dirstate.normal(f)
1438 1438 finally:
1439 1439 wlock.release()
1440 1440
1441 1441 def copy(self, source, dest):
1442 1442 try:
1443 1443 st = self._repo.wvfs.lstat(dest)
1444 1444 except OSError as err:
1445 1445 if err.errno != errno.ENOENT:
1446 1446 raise
1447 1447 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1448 1448 return
1449 1449 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1450 1450 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1451 1451 "symbolic link\n") % dest)
1452 1452 else:
1453 1453 wlock = self._repo.wlock()
1454 1454 try:
1455 1455 if self._repo.dirstate[dest] in '?':
1456 1456 self._repo.dirstate.add(dest)
1457 1457 elif self._repo.dirstate[dest] in 'r':
1458 1458 self._repo.dirstate.normallookup(dest)
1459 1459 self._repo.dirstate.copy(source, dest)
1460 1460 finally:
1461 1461 wlock.release()
1462 1462
1463 1463 def match(self, pats=[], include=None, exclude=None, default='glob',
1464 1464 listsubrepos=False, badfn=None):
1465 1465 r = self._repo
1466 1466
1467 1467 # Only a case insensitive filesystem needs magic to translate user input
1468 1468 # to actual case in the filesystem.
1469 1469 if not util.checkcase(r.root):
1470 1470 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1471 1471 exclude, default, r.auditor, self,
1472 1472 listsubrepos=listsubrepos,
1473 1473 badfn=badfn)
1474 1474 return matchmod.match(r.root, r.getcwd(), pats,
1475 1475 include, exclude, default,
1476 1476 auditor=r.auditor, ctx=self,
1477 1477 listsubrepos=listsubrepos, badfn=badfn)
1478 1478
1479 1479 def _filtersuspectsymlink(self, files):
1480 1480 if not files or self._repo.dirstate._checklink:
1481 1481 return files
1482 1482
1483 1483 # Symlink placeholders may get non-symlink-like contents
1484 1484 # via user error or dereferencing by NFS or Samba servers,
1485 1485 # so we filter out any placeholders that don't look like a
1486 1486 # symlink
1487 1487 sane = []
1488 1488 for f in files:
1489 1489 if self.flags(f) == 'l':
1490 1490 d = self[f].data()
1491 1491 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1492 1492 self._repo.ui.debug('ignoring suspect symlink placeholder'
1493 1493 ' "%s"\n' % f)
1494 1494 continue
1495 1495 sane.append(f)
1496 1496 return sane
1497 1497
1498 1498 def _checklookup(self, files):
1499 1499 # check for any possibly clean files
1500 1500 if not files:
1501 1501 return [], []
1502 1502
1503 1503 modified = []
1504 1504 fixup = []
1505 1505 pctx = self._parents[0]
1506 1506 # do a full compare of any files that might have changed
1507 1507 for f in sorted(files):
1508 1508 if (f not in pctx or self.flags(f) != pctx.flags(f)
1509 1509 or pctx[f].cmp(self[f])):
1510 1510 modified.append(f)
1511 1511 else:
1512 1512 fixup.append(f)
1513 1513
1514 1514 # update dirstate for files that are actually clean
1515 1515 if fixup:
1516 1516 try:
1517 1517 # updating the dirstate is optional
1518 1518 # so we don't wait on the lock
1519 1519 # wlock can invalidate the dirstate, so cache normal _after_
1520 1520 # taking the lock
1521 1521 wlock = self._repo.wlock(False)
1522 1522 normal = self._repo.dirstate.normal
1523 1523 try:
1524 1524 for f in fixup:
1525 1525 normal(f)
1526 1526 # write changes out explicitly, because nesting
1527 1527 # wlock at runtime may prevent 'wlock.release()'
1528 1528 # below from doing so for subsequent changing files
1529 1529 self._repo.dirstate.write()
1530 1530 finally:
1531 1531 wlock.release()
1532 1532 except error.LockError:
1533 1533 pass
1534 1534 return modified, fixup
1535 1535
1536 1536 def _manifestmatches(self, match, s):
1537 1537 """Slow path for workingctx
1538 1538
1539 1539 The fast path is when we compare the working directory to its parent
1540 1540 which means this function is comparing with a non-parent; therefore we
1541 1541 need to build a manifest and return what matches.
1542 1542 """
1543 1543 mf = self._repo['.']._manifestmatches(match, s)
1544 1544 for f in s.modified + s.added:
1545 1545 mf[f] = _newnode
1546 1546 mf.setflag(f, self.flags(f))
1547 1547 for f in s.removed:
1548 1548 if f in mf:
1549 1549 del mf[f]
1550 1550 return mf
1551 1551
1552 1552 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1553 1553 unknown=False):
1554 1554 '''Gets the status from the dirstate -- internal use only.'''
1555 1555 listignored, listclean, listunknown = ignored, clean, unknown
1556 1556 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1557 1557 subrepos = []
1558 1558 if '.hgsub' in self:
1559 1559 subrepos = sorted(self.substate)
1560 1560 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1561 1561 listclean, listunknown)
1562 1562
1563 1563 # check for any possibly clean files
1564 1564 if cmp:
1565 1565 modified2, fixup = self._checklookup(cmp)
1566 1566 s.modified.extend(modified2)
1567 1567
1568 1568 # update dirstate for files that are actually clean
1569 1569 if fixup and listclean:
1570 1570 s.clean.extend(fixup)
1571 1571
1572 1572 if match.always():
1573 1573 # cache for performance
1574 1574 if s.unknown or s.ignored or s.clean:
1575 1575 # "_status" is cached with list*=False in the normal route
1576 1576 self._status = scmutil.status(s.modified, s.added, s.removed,
1577 1577 s.deleted, [], [], [])
1578 1578 else:
1579 1579 self._status = s
1580 1580
1581 1581 return s
1582 1582
1583 1583 def _buildstatus(self, other, s, match, listignored, listclean,
1584 1584 listunknown):
1585 1585 """build a status with respect to another context
1586 1586
1587 1587 This includes logic for maintaining the fast path of status when
1588 1588 comparing the working directory against its parent, which is to skip
1589 1589 building a new manifest if self (working directory) is not comparing
1590 1590 against its parent (repo['.']).
1591 1591 """
1592 1592 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1593 1593 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1594 1594 # might have accidentally ended up with the entire contents of the file
1595 1595 # they are supposed to be linking to.
1596 1596 s.modified[:] = self._filtersuspectsymlink(s.modified)
1597 1597 if other != self._repo['.']:
1598 1598 s = super(workingctx, self)._buildstatus(other, s, match,
1599 1599 listignored, listclean,
1600 1600 listunknown)
1601 1601 return s
1602 1602
1603 1603 def _matchstatus(self, other, match):
1604 1604 """override the match method with a filter for directory patterns
1605 1605
1606 1606 We use inheritance to customize the match.bad method only in cases of
1607 1607 workingctx since it belongs only to the working directory when
1608 1608 comparing against the parent changeset.
1609 1609
1610 1610 If we aren't comparing against the working directory's parent, then we
1611 1611 just use the default match object sent to us.
1612 1612 """
1613 1613 superself = super(workingctx, self)
1614 1614 match = superself._matchstatus(other, match)
1615 1615 if other != self._repo['.']:
1616 1616 def bad(f, msg):
1617 1617 # 'f' may be a directory pattern from 'match.files()',
1618 1618 # so 'f not in ctx1' is not enough
1619 1619 if f not in other and not other.hasdir(f):
1620 1620 self._repo.ui.warn('%s: %s\n' %
1621 1621 (self._repo.dirstate.pathto(f), msg))
1622 1622 match.bad = bad
1623 1623 return match
1624 1624
1625 1625 class committablefilectx(basefilectx):
1626 1626 """A committablefilectx provides common functionality for a file context
1627 1627 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1628 1628 def __init__(self, repo, path, filelog=None, ctx=None):
1629 1629 self._repo = repo
1630 1630 self._path = path
1631 1631 self._changeid = None
1632 1632 self._filerev = self._filenode = None
1633 1633
1634 1634 if filelog is not None:
1635 1635 self._filelog = filelog
1636 1636 if ctx:
1637 1637 self._changectx = ctx
1638 1638
1639 1639 def __nonzero__(self):
1640 1640 return True
1641 1641
1642 1642 def linkrev(self):
1643 1643 # linked to self._changectx no matter if file is modified or not
1644 1644 return self.rev()
1645 1645
1646 1646 def parents(self):
1647 1647 '''return parent filectxs, following copies if necessary'''
1648 1648 def filenode(ctx, path):
1649 1649 return ctx._manifest.get(path, nullid)
1650 1650
1651 1651 path = self._path
1652 1652 fl = self._filelog
1653 1653 pcl = self._changectx._parents
1654 1654 renamed = self.renamed()
1655 1655
1656 1656 if renamed:
1657 1657 pl = [renamed + (None,)]
1658 1658 else:
1659 1659 pl = [(path, filenode(pcl[0], path), fl)]
1660 1660
1661 1661 for pc in pcl[1:]:
1662 1662 pl.append((path, filenode(pc, path), fl))
1663 1663
1664 1664 return [self._parentfilectx(p, fileid=n, filelog=l)
1665 1665 for p, n, l in pl if n != nullid]
1666 1666
1667 1667 def children(self):
1668 1668 return []
1669 1669
1670 1670 class workingfilectx(committablefilectx):
1671 1671 """A workingfilectx object makes access to data related to a particular
1672 1672 file in the working directory convenient."""
1673 1673 def __init__(self, repo, path, filelog=None, workingctx=None):
1674 1674 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1675 1675
1676 1676 @propertycache
1677 1677 def _changectx(self):
1678 1678 return workingctx(self._repo)
1679 1679
1680 1680 def data(self):
1681 1681 return self._repo.wread(self._path)
1682 1682 def renamed(self):
1683 1683 rp = self._repo.dirstate.copied(self._path)
1684 1684 if not rp:
1685 1685 return None
1686 1686 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1687 1687
1688 1688 def size(self):
1689 1689 return self._repo.wvfs.lstat(self._path).st_size
1690 1690 def date(self):
1691 1691 t, tz = self._changectx.date()
1692 1692 try:
1693 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1693 return (util.statmtimesec(self._repo.wvfs.lstat(self._path)), tz)
1694 1694 except OSError as err:
1695 1695 if err.errno != errno.ENOENT:
1696 1696 raise
1697 1697 return (t, tz)
1698 1698
1699 1699 def cmp(self, fctx):
1700 1700 """compare with other file context
1701 1701
1702 1702 returns True if different than fctx.
1703 1703 """
1704 1704 # fctx should be a filectx (not a workingfilectx)
1705 1705 # invert comparison to reuse the same code path
1706 1706 return fctx.cmp(self)
1707 1707
1708 1708 def remove(self, ignoremissing=False):
1709 1709 """wraps unlink for a repo's working directory"""
1710 1710 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1711 1711
1712 1712 def write(self, data, flags):
1713 1713 """wraps repo.wwrite"""
1714 1714 self._repo.wwrite(self._path, data, flags)
1715 1715
1716 1716 class workingcommitctx(workingctx):
1717 1717 """A workingcommitctx object makes access to data related to
1718 1718 the revision being committed convenient.
1719 1719
1720 1720 This hides changes in the working directory, if they aren't
1721 1721 committed in this context.
1722 1722 """
1723 1723 def __init__(self, repo, changes,
1724 1724 text="", user=None, date=None, extra=None):
1725 1725 super(workingctx, self).__init__(repo, text, user, date, extra,
1726 1726 changes)
1727 1727
1728 1728 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1729 1729 unknown=False):
1730 1730 """Return matched files only in ``self._status``
1731 1731
1732 1732 Uncommitted files appear "clean" via this context, even if
1733 1733 they aren't actually so in the working directory.
1734 1734 """
1735 1735 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1736 1736 if clean:
1737 1737 clean = [f for f in self._manifest if f not in self._changedset]
1738 1738 else:
1739 1739 clean = []
1740 1740 return scmutil.status([f for f in self._status.modified if match(f)],
1741 1741 [f for f in self._status.added if match(f)],
1742 1742 [f for f in self._status.removed if match(f)],
1743 1743 [], [], [], clean)
1744 1744
1745 1745 @propertycache
1746 1746 def _changedset(self):
1747 1747 """Return the set of files changed in this context
1748 1748 """
1749 1749 changed = set(self._status.modified)
1750 1750 changed.update(self._status.added)
1751 1751 changed.update(self._status.removed)
1752 1752 return changed
1753 1753
1754 1754 class memctx(committablectx):
1755 1755 """Use memctx to perform in-memory commits via localrepo.commitctx().
1756 1756
1757 1757 Revision information is supplied at initialization time while
1758 1758 related files data and is made available through a callback
1759 1759 mechanism. 'repo' is the current localrepo, 'parents' is a
1760 1760 sequence of two parent revisions identifiers (pass None for every
1761 1761 missing parent), 'text' is the commit message and 'files' lists
1762 1762 names of files touched by the revision (normalized and relative to
1763 1763 repository root).
1764 1764
1765 1765 filectxfn(repo, memctx, path) is a callable receiving the
1766 1766 repository, the current memctx object and the normalized path of
1767 1767 requested file, relative to repository root. It is fired by the
1768 1768 commit function for every file in 'files', but calls order is
1769 1769 undefined. If the file is available in the revision being
1770 1770 committed (updated or added), filectxfn returns a memfilectx
1771 1771 object. If the file was removed, filectxfn raises an
1772 1772 IOError. Moved files are represented by marking the source file
1773 1773 removed and the new file added with copy information (see
1774 1774 memfilectx).
1775 1775
1776 1776 user receives the committer name and defaults to current
1777 1777 repository username, date is the commit date in any format
1778 1778 supported by util.parsedate() and defaults to current date, extra
1779 1779 is a dictionary of metadata or is left empty.
1780 1780 """
1781 1781
1782 1782 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1783 1783 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1784 1784 # this field to determine what to do in filectxfn.
1785 1785 _returnnoneformissingfiles = True
1786 1786
1787 1787 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1788 1788 date=None, extra=None, editor=False):
1789 1789 super(memctx, self).__init__(repo, text, user, date, extra)
1790 1790 self._rev = None
1791 1791 self._node = None
1792 1792 parents = [(p or nullid) for p in parents]
1793 1793 p1, p2 = parents
1794 1794 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1795 1795 files = sorted(set(files))
1796 1796 self._files = files
1797 1797 self.substate = {}
1798 1798
1799 1799 # if store is not callable, wrap it in a function
1800 1800 if not callable(filectxfn):
1801 1801 def getfilectx(repo, memctx, path):
1802 1802 fctx = filectxfn[path]
1803 1803 # this is weird but apparently we only keep track of one parent
1804 1804 # (why not only store that instead of a tuple?)
1805 1805 copied = fctx.renamed()
1806 1806 if copied:
1807 1807 copied = copied[0]
1808 1808 return memfilectx(repo, path, fctx.data(),
1809 1809 islink=fctx.islink(), isexec=fctx.isexec(),
1810 1810 copied=copied, memctx=memctx)
1811 1811 self._filectxfn = getfilectx
1812 1812 else:
1813 1813 # "util.cachefunc" reduces invocation of possibly expensive
1814 1814 # "filectxfn" for performance (e.g. converting from another VCS)
1815 1815 self._filectxfn = util.cachefunc(filectxfn)
1816 1816
1817 1817 if extra:
1818 1818 self._extra = extra.copy()
1819 1819 else:
1820 1820 self._extra = {}
1821 1821
1822 1822 if self._extra.get('branch', '') == '':
1823 1823 self._extra['branch'] = 'default'
1824 1824
1825 1825 if editor:
1826 1826 self._text = editor(self._repo, self, [])
1827 1827 self._repo.savecommitmessage(self._text)
1828 1828
1829 1829 def filectx(self, path, filelog=None):
1830 1830 """get a file context from the working directory
1831 1831
1832 1832 Returns None if file doesn't exist and should be removed."""
1833 1833 return self._filectxfn(self._repo, self, path)
1834 1834
1835 1835 def commit(self):
1836 1836 """commit context to the repo"""
1837 1837 return self._repo.commitctx(self)
1838 1838
1839 1839 @propertycache
1840 1840 def _manifest(self):
1841 1841 """generate a manifest based on the return values of filectxfn"""
1842 1842
1843 1843 # keep this simple for now; just worry about p1
1844 1844 pctx = self._parents[0]
1845 1845 man = pctx.manifest().copy()
1846 1846
1847 1847 for f in self._status.modified:
1848 1848 p1node = nullid
1849 1849 p2node = nullid
1850 1850 p = pctx[f].parents() # if file isn't in pctx, check p2?
1851 1851 if len(p) > 0:
1852 1852 p1node = p[0].node()
1853 1853 if len(p) > 1:
1854 1854 p2node = p[1].node()
1855 1855 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1856 1856
1857 1857 for f in self._status.added:
1858 1858 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1859 1859
1860 1860 for f in self._status.removed:
1861 1861 if f in man:
1862 1862 del man[f]
1863 1863
1864 1864 return man
1865 1865
1866 1866 @propertycache
1867 1867 def _status(self):
1868 1868 """Calculate exact status from ``files`` specified at construction
1869 1869 """
1870 1870 man1 = self.p1().manifest()
1871 1871 p2 = self._parents[1]
1872 1872 # "1 < len(self._parents)" can't be used for checking
1873 1873 # existence of the 2nd parent, because "memctx._parents" is
1874 1874 # explicitly initialized by the list, of which length is 2.
1875 1875 if p2.node() != nullid:
1876 1876 man2 = p2.manifest()
1877 1877 managing = lambda f: f in man1 or f in man2
1878 1878 else:
1879 1879 managing = lambda f: f in man1
1880 1880
1881 1881 modified, added, removed = [], [], []
1882 1882 for f in self._files:
1883 1883 if not managing(f):
1884 1884 added.append(f)
1885 1885 elif self[f]:
1886 1886 modified.append(f)
1887 1887 else:
1888 1888 removed.append(f)
1889 1889
1890 1890 return scmutil.status(modified, added, removed, [], [], [], [])
1891 1891
1892 1892 class memfilectx(committablefilectx):
1893 1893 """memfilectx represents an in-memory file to commit.
1894 1894
1895 1895 See memctx and committablefilectx for more details.
1896 1896 """
1897 1897 def __init__(self, repo, path, data, islink=False,
1898 1898 isexec=False, copied=None, memctx=None):
1899 1899 """
1900 1900 path is the normalized file path relative to repository root.
1901 1901 data is the file content as a string.
1902 1902 islink is True if the file is a symbolic link.
1903 1903 isexec is True if the file is executable.
1904 1904 copied is the source file path if current file was copied in the
1905 1905 revision being committed, or None."""
1906 1906 super(memfilectx, self).__init__(repo, path, None, memctx)
1907 1907 self._data = data
1908 1908 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1909 1909 self._copied = None
1910 1910 if copied:
1911 1911 self._copied = (copied, nullid)
1912 1912
1913 1913 def data(self):
1914 1914 return self._data
1915 1915 def size(self):
1916 1916 return len(self.data())
1917 1917 def flags(self):
1918 1918 return self._flags
1919 1919 def renamed(self):
1920 1920 return self._copied
1921 1921
1922 1922 def remove(self, ignoremissing=False):
1923 1923 """wraps unlink for a repo's working directory"""
1924 1924 # need to figure out what to do here
1925 1925 del self._changectx[self._path]
1926 1926
1927 1927 def write(self, data, flags):
1928 1928 """wraps repo.wwrite"""
1929 1929 self._data = data
@@ -1,1044 +1,1044 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 10 import scmutil, util, osutil, parsers, encoding, pathutil
11 11 import os, stat, errno
12 12 import match as matchmod
13 13
14 14 propertycache = util.propertycache
15 15 filecache = scmutil.filecache
16 16 _rangemask = 0x7fffffff
17 17
18 18 dirstatetuple = parsers.dirstatetuple
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 class dirstate(object):
31 31
32 32 def __init__(self, opener, ui, root, validate):
33 33 '''Create a new dirstate object.
34 34
35 35 opener is an open()-like callable that can be used to open the
36 36 dirstate file; root is the root of the directory tracked by
37 37 the dirstate.
38 38 '''
39 39 self._opener = opener
40 40 self._validate = validate
41 41 self._root = root
42 42 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
43 43 # UNC path pointing to root share (issue4557)
44 44 self._rootdir = pathutil.normasprefix(root)
45 45 # internal config: ui.forcecwd
46 46 forcecwd = ui.config('ui', 'forcecwd')
47 47 if forcecwd:
48 48 self._cwd = forcecwd
49 49 self._dirty = False
50 50 self._dirtypl = False
51 51 self._lastnormaltime = 0
52 52 self._ui = ui
53 53 self._filecache = {}
54 54 self._parentwriters = 0
55 55 self._filename = 'dirstate'
56 56
57 57 def beginparentchange(self):
58 58 '''Marks the beginning of a set of changes that involve changing
59 59 the dirstate parents. If there is an exception during this time,
60 60 the dirstate will not be written when the wlock is released. This
61 61 prevents writing an incoherent dirstate where the parent doesn't
62 62 match the contents.
63 63 '''
64 64 self._parentwriters += 1
65 65
66 66 def endparentchange(self):
67 67 '''Marks the end of a set of changes that involve changing the
68 68 dirstate parents. Once all parent changes have been marked done,
69 69 the wlock will be free to write the dirstate on release.
70 70 '''
71 71 if self._parentwriters > 0:
72 72 self._parentwriters -= 1
73 73
74 74 def pendingparentchange(self):
75 75 '''Returns true if the dirstate is in the middle of a set of changes
76 76 that modify the dirstate parent.
77 77 '''
78 78 return self._parentwriters > 0
79 79
80 80 @propertycache
81 81 def _map(self):
82 82 '''Return the dirstate contents as a map from filename to
83 83 (state, mode, size, time).'''
84 84 self._read()
85 85 return self._map
86 86
87 87 @propertycache
88 88 def _copymap(self):
89 89 self._read()
90 90 return self._copymap
91 91
92 92 @propertycache
93 93 def _filefoldmap(self):
94 94 try:
95 95 makefilefoldmap = parsers.make_file_foldmap
96 96 except AttributeError:
97 97 pass
98 98 else:
99 99 return makefilefoldmap(self._map, util.normcasespec,
100 100 util.normcasefallback)
101 101
102 102 f = {}
103 103 normcase = util.normcase
104 104 for name, s in self._map.iteritems():
105 105 if s[0] != 'r':
106 106 f[normcase(name)] = name
107 107 f['.'] = '.' # prevents useless util.fspath() invocation
108 108 return f
109 109
110 110 @propertycache
111 111 def _dirfoldmap(self):
112 112 f = {}
113 113 normcase = util.normcase
114 114 for name in self._dirs:
115 115 f[normcase(name)] = name
116 116 return f
117 117
118 118 @repocache('branch')
119 119 def _branch(self):
120 120 try:
121 121 return self._opener.read("branch").strip() or "default"
122 122 except IOError as inst:
123 123 if inst.errno != errno.ENOENT:
124 124 raise
125 125 return "default"
126 126
127 127 @propertycache
128 128 def _pl(self):
129 129 try:
130 130 fp = self._opener(self._filename)
131 131 st = fp.read(40)
132 132 fp.close()
133 133 l = len(st)
134 134 if l == 40:
135 135 return st[:20], st[20:40]
136 136 elif l > 0 and l < 40:
137 137 raise util.Abort(_('working directory state appears damaged!'))
138 138 except IOError as err:
139 139 if err.errno != errno.ENOENT:
140 140 raise
141 141 return [nullid, nullid]
142 142
143 143 @propertycache
144 144 def _dirs(self):
145 145 return util.dirs(self._map, 'r')
146 146
147 147 def dirs(self):
148 148 return self._dirs
149 149
150 150 @rootcache('.hgignore')
151 151 def _ignore(self):
152 152 files = []
153 153 if os.path.exists(self._join('.hgignore')):
154 154 files.append(self._join('.hgignore'))
155 155 for name, path in self._ui.configitems("ui"):
156 156 if name == 'ignore' or name.startswith('ignore.'):
157 157 # we need to use os.path.join here rather than self._join
158 158 # because path is arbitrary and user-specified
159 159 files.append(os.path.join(self._rootdir, util.expandpath(path)))
160 160
161 161 if not files:
162 162 return util.never
163 163
164 164 pats = ['include:%s' % f for f in files]
165 165 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
166 166
167 167 @propertycache
168 168 def _slash(self):
169 169 return self._ui.configbool('ui', 'slash') and os.sep != '/'
170 170
171 171 @propertycache
172 172 def _checklink(self):
173 173 return util.checklink(self._root)
174 174
175 175 @propertycache
176 176 def _checkexec(self):
177 177 return util.checkexec(self._root)
178 178
179 179 @propertycache
180 180 def _checkcase(self):
181 181 return not util.checkcase(self._join('.hg'))
182 182
183 183 def _join(self, f):
184 184 # much faster than os.path.join()
185 185 # it's safe because f is always a relative path
186 186 return self._rootdir + f
187 187
188 188 def flagfunc(self, buildfallback):
189 189 if self._checklink and self._checkexec:
190 190 def f(x):
191 191 try:
192 192 st = os.lstat(self._join(x))
193 193 if util.statislink(st):
194 194 return 'l'
195 195 if util.statisexec(st):
196 196 return 'x'
197 197 except OSError:
198 198 pass
199 199 return ''
200 200 return f
201 201
202 202 fallback = buildfallback()
203 203 if self._checklink:
204 204 def f(x):
205 205 if os.path.islink(self._join(x)):
206 206 return 'l'
207 207 if 'x' in fallback(x):
208 208 return 'x'
209 209 return ''
210 210 return f
211 211 if self._checkexec:
212 212 def f(x):
213 213 if 'l' in fallback(x):
214 214 return 'l'
215 215 if util.isexec(self._join(x)):
216 216 return 'x'
217 217 return ''
218 218 return f
219 219 else:
220 220 return fallback
221 221
222 222 @propertycache
223 223 def _cwd(self):
224 224 return os.getcwd()
225 225
226 226 def getcwd(self):
227 227 '''Return the path from which a canonical path is calculated.
228 228
229 229 This path should be used to resolve file patterns or to convert
230 230 canonical paths back to file paths for display. It shouldn't be
231 231 used to get real file paths. Use vfs functions instead.
232 232 '''
233 233 cwd = self._cwd
234 234 if cwd == self._root:
235 235 return ''
236 236 # self._root ends with a path separator if self._root is '/' or 'C:\'
237 237 rootsep = self._root
238 238 if not util.endswithsep(rootsep):
239 239 rootsep += os.sep
240 240 if cwd.startswith(rootsep):
241 241 return cwd[len(rootsep):]
242 242 else:
243 243 # we're outside the repo. return an absolute path.
244 244 return cwd
245 245
246 246 def pathto(self, f, cwd=None):
247 247 if cwd is None:
248 248 cwd = self.getcwd()
249 249 path = util.pathto(self._root, cwd, f)
250 250 if self._slash:
251 251 return util.pconvert(path)
252 252 return path
253 253
254 254 def __getitem__(self, key):
255 255 '''Return the current state of key (a filename) in the dirstate.
256 256
257 257 States are:
258 258 n normal
259 259 m needs merging
260 260 r marked for removal
261 261 a marked for addition
262 262 ? not tracked
263 263 '''
264 264 return self._map.get(key, ("?",))[0]
265 265
266 266 def __contains__(self, key):
267 267 return key in self._map
268 268
269 269 def __iter__(self):
270 270 for x in sorted(self._map):
271 271 yield x
272 272
273 273 def iteritems(self):
274 274 return self._map.iteritems()
275 275
276 276 def parents(self):
277 277 return [self._validate(p) for p in self._pl]
278 278
279 279 def p1(self):
280 280 return self._validate(self._pl[0])
281 281
282 282 def p2(self):
283 283 return self._validate(self._pl[1])
284 284
285 285 def branch(self):
286 286 return encoding.tolocal(self._branch)
287 287
288 288 def setparents(self, p1, p2=nullid):
289 289 """Set dirstate parents to p1 and p2.
290 290
291 291 When moving from two parents to one, 'm' merged entries a
292 292 adjusted to normal and previous copy records discarded and
293 293 returned by the call.
294 294
295 295 See localrepo.setparents()
296 296 """
297 297 if self._parentwriters == 0:
298 298 raise ValueError("cannot set dirstate parent without "
299 299 "calling dirstate.beginparentchange")
300 300
301 301 self._dirty = self._dirtypl = True
302 302 oldp2 = self._pl[1]
303 303 self._pl = p1, p2
304 304 copies = {}
305 305 if oldp2 != nullid and p2 == nullid:
306 306 for f, s in self._map.iteritems():
307 307 # Discard 'm' markers when moving away from a merge state
308 308 if s[0] == 'm':
309 309 if f in self._copymap:
310 310 copies[f] = self._copymap[f]
311 311 self.normallookup(f)
312 312 # Also fix up otherparent markers
313 313 elif s[0] == 'n' and s[2] == -2:
314 314 if f in self._copymap:
315 315 copies[f] = self._copymap[f]
316 316 self.add(f)
317 317 return copies
318 318
319 319 def setbranch(self, branch):
320 320 self._branch = encoding.fromlocal(branch)
321 321 f = self._opener('branch', 'w', atomictemp=True)
322 322 try:
323 323 f.write(self._branch + '\n')
324 324 f.close()
325 325
326 326 # make sure filecache has the correct stat info for _branch after
327 327 # replacing the underlying file
328 328 ce = self._filecache['_branch']
329 329 if ce:
330 330 ce.refresh()
331 331 except: # re-raises
332 332 f.discard()
333 333 raise
334 334
335 335 def _read(self):
336 336 self._map = {}
337 337 self._copymap = {}
338 338 try:
339 339 fp = self._opener.open(self._filename)
340 340 try:
341 341 st = fp.read()
342 342 finally:
343 343 fp.close()
344 344 except IOError as err:
345 345 if err.errno != errno.ENOENT:
346 346 raise
347 347 return
348 348 if not st:
349 349 return
350 350
351 351 if util.safehasattr(parsers, 'dict_new_presized'):
352 352 # Make an estimate of the number of files in the dirstate based on
353 353 # its size. From a linear regression on a set of real-world repos,
354 354 # all over 10,000 files, the size of a dirstate entry is 85
355 355 # bytes. The cost of resizing is significantly higher than the cost
356 356 # of filling in a larger presized dict, so subtract 20% from the
357 357 # size.
358 358 #
359 359 # This heuristic is imperfect in many ways, so in a future dirstate
360 360 # format update it makes sense to just record the number of entries
361 361 # on write.
362 362 self._map = parsers.dict_new_presized(len(st) / 71)
363 363
364 364 # Python's garbage collector triggers a GC each time a certain number
365 365 # of container objects (the number being defined by
366 366 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
367 367 # for each file in the dirstate. The C version then immediately marks
368 368 # them as not to be tracked by the collector. However, this has no
369 369 # effect on when GCs are triggered, only on what objects the GC looks
370 370 # into. This means that O(number of files) GCs are unavoidable.
371 371 # Depending on when in the process's lifetime the dirstate is parsed,
372 372 # this can get very expensive. As a workaround, disable GC while
373 373 # parsing the dirstate.
374 374 #
375 375 # (we cannot decorate the function directly since it is in a C module)
376 376 parse_dirstate = util.nogc(parsers.parse_dirstate)
377 377 p = parse_dirstate(self._map, self._copymap, st)
378 378 if not self._dirtypl:
379 379 self._pl = p
380 380
381 381 def invalidate(self):
382 382 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
383 383 "_pl", "_dirs", "_ignore"):
384 384 if a in self.__dict__:
385 385 delattr(self, a)
386 386 self._lastnormaltime = 0
387 387 self._dirty = False
388 388 self._parentwriters = 0
389 389
390 390 def copy(self, source, dest):
391 391 """Mark dest as a copy of source. Unmark dest if source is None."""
392 392 if source == dest:
393 393 return
394 394 self._dirty = True
395 395 if source is not None:
396 396 self._copymap[dest] = source
397 397 elif dest in self._copymap:
398 398 del self._copymap[dest]
399 399
400 400 def copied(self, file):
401 401 return self._copymap.get(file, None)
402 402
403 403 def copies(self):
404 404 return self._copymap
405 405
406 406 def _droppath(self, f):
407 407 if self[f] not in "?r" and "_dirs" in self.__dict__:
408 408 self._dirs.delpath(f)
409 409
410 410 def _addpath(self, f, state, mode, size, mtime):
411 411 oldstate = self[f]
412 412 if state == 'a' or oldstate == 'r':
413 413 scmutil.checkfilename(f)
414 414 if f in self._dirs:
415 415 raise util.Abort(_('directory %r already in dirstate') % f)
416 416 # shadows
417 417 for d in util.finddirs(f):
418 418 if d in self._dirs:
419 419 break
420 420 if d in self._map and self[d] != 'r':
421 421 raise util.Abort(
422 422 _('file %r in dirstate clashes with %r') % (d, f))
423 423 if oldstate in "?r" and "_dirs" in self.__dict__:
424 424 self._dirs.addpath(f)
425 425 self._dirty = True
426 426 self._map[f] = dirstatetuple(state, mode, size, mtime)
427 427
428 428 def normal(self, f):
429 429 '''Mark a file normal and clean.'''
430 430 s = os.lstat(self._join(f))
431 mtime = int(s.st_mtime)
431 mtime = util.statmtimesec(s)
432 432 self._addpath(f, 'n', s.st_mode,
433 433 s.st_size & _rangemask, mtime & _rangemask)
434 434 if f in self._copymap:
435 435 del self._copymap[f]
436 436 if mtime > self._lastnormaltime:
437 437 # Remember the most recent modification timeslot for status(),
438 438 # to make sure we won't miss future size-preserving file content
439 439 # modifications that happen within the same timeslot.
440 440 self._lastnormaltime = mtime
441 441
442 442 def normallookup(self, f):
443 443 '''Mark a file normal, but possibly dirty.'''
444 444 if self._pl[1] != nullid and f in self._map:
445 445 # if there is a merge going on and the file was either
446 446 # in state 'm' (-1) or coming from other parent (-2) before
447 447 # being removed, restore that state.
448 448 entry = self._map[f]
449 449 if entry[0] == 'r' and entry[2] in (-1, -2):
450 450 source = self._copymap.get(f)
451 451 if entry[2] == -1:
452 452 self.merge(f)
453 453 elif entry[2] == -2:
454 454 self.otherparent(f)
455 455 if source:
456 456 self.copy(source, f)
457 457 return
458 458 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
459 459 return
460 460 self._addpath(f, 'n', 0, -1, -1)
461 461 if f in self._copymap:
462 462 del self._copymap[f]
463 463
464 464 def otherparent(self, f):
465 465 '''Mark as coming from the other parent, always dirty.'''
466 466 if self._pl[1] == nullid:
467 467 raise util.Abort(_("setting %r to other parent "
468 468 "only allowed in merges") % f)
469 469 if f in self and self[f] == 'n':
470 470 # merge-like
471 471 self._addpath(f, 'm', 0, -2, -1)
472 472 else:
473 473 # add-like
474 474 self._addpath(f, 'n', 0, -2, -1)
475 475
476 476 if f in self._copymap:
477 477 del self._copymap[f]
478 478
479 479 def add(self, f):
480 480 '''Mark a file added.'''
481 481 self._addpath(f, 'a', 0, -1, -1)
482 482 if f in self._copymap:
483 483 del self._copymap[f]
484 484
485 485 def remove(self, f):
486 486 '''Mark a file removed.'''
487 487 self._dirty = True
488 488 self._droppath(f)
489 489 size = 0
490 490 if self._pl[1] != nullid and f in self._map:
491 491 # backup the previous state
492 492 entry = self._map[f]
493 493 if entry[0] == 'm': # merge
494 494 size = -1
495 495 elif entry[0] == 'n' and entry[2] == -2: # other parent
496 496 size = -2
497 497 self._map[f] = dirstatetuple('r', 0, size, 0)
498 498 if size == 0 and f in self._copymap:
499 499 del self._copymap[f]
500 500
501 501 def merge(self, f):
502 502 '''Mark a file merged.'''
503 503 if self._pl[1] == nullid:
504 504 return self.normallookup(f)
505 505 return self.otherparent(f)
506 506
507 507 def drop(self, f):
508 508 '''Drop a file from the dirstate'''
509 509 if f in self._map:
510 510 self._dirty = True
511 511 self._droppath(f)
512 512 del self._map[f]
513 513
514 514 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
515 515 if exists is None:
516 516 exists = os.path.lexists(os.path.join(self._root, path))
517 517 if not exists:
518 518 # Maybe a path component exists
519 519 if not ignoremissing and '/' in path:
520 520 d, f = path.rsplit('/', 1)
521 521 d = self._normalize(d, False, ignoremissing, None)
522 522 folded = d + "/" + f
523 523 else:
524 524 # No path components, preserve original case
525 525 folded = path
526 526 else:
527 527 # recursively normalize leading directory components
528 528 # against dirstate
529 529 if '/' in normed:
530 530 d, f = normed.rsplit('/', 1)
531 531 d = self._normalize(d, False, ignoremissing, True)
532 532 r = self._root + "/" + d
533 533 folded = d + "/" + util.fspath(f, r)
534 534 else:
535 535 folded = util.fspath(normed, self._root)
536 536 storemap[normed] = folded
537 537
538 538 return folded
539 539
540 540 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
541 541 normed = util.normcase(path)
542 542 folded = self._filefoldmap.get(normed, None)
543 543 if folded is None:
544 544 if isknown:
545 545 folded = path
546 546 else:
547 547 folded = self._discoverpath(path, normed, ignoremissing, exists,
548 548 self._filefoldmap)
549 549 return folded
550 550
551 551 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
552 552 normed = util.normcase(path)
553 553 folded = self._filefoldmap.get(normed, None)
554 554 if folded is None:
555 555 folded = self._dirfoldmap.get(normed, None)
556 556 if folded is None:
557 557 if isknown:
558 558 folded = path
559 559 else:
560 560 # store discovered result in dirfoldmap so that future
561 561 # normalizefile calls don't start matching directories
562 562 folded = self._discoverpath(path, normed, ignoremissing, exists,
563 563 self._dirfoldmap)
564 564 return folded
565 565
566 566 def normalize(self, path, isknown=False, ignoremissing=False):
567 567 '''
568 568 normalize the case of a pathname when on a casefolding filesystem
569 569
570 570 isknown specifies whether the filename came from walking the
571 571 disk, to avoid extra filesystem access.
572 572
573 573 If ignoremissing is True, missing path are returned
574 574 unchanged. Otherwise, we try harder to normalize possibly
575 575 existing path components.
576 576
577 577 The normalized case is determined based on the following precedence:
578 578
579 579 - version of name already stored in the dirstate
580 580 - version of name stored on disk
581 581 - version provided via command arguments
582 582 '''
583 583
584 584 if self._checkcase:
585 585 return self._normalize(path, isknown, ignoremissing)
586 586 return path
587 587
588 588 def clear(self):
589 589 self._map = {}
590 590 if "_dirs" in self.__dict__:
591 591 delattr(self, "_dirs")
592 592 self._copymap = {}
593 593 self._pl = [nullid, nullid]
594 594 self._lastnormaltime = 0
595 595 self._dirty = True
596 596
597 597 def rebuild(self, parent, allfiles, changedfiles=None):
598 598 if changedfiles is None:
599 599 changedfiles = allfiles
600 600 oldmap = self._map
601 601 self.clear()
602 602 for f in allfiles:
603 603 if f not in changedfiles:
604 604 self._map[f] = oldmap[f]
605 605 else:
606 606 if 'x' in allfiles.flags(f):
607 607 self._map[f] = dirstatetuple('n', 0o777, -1, 0)
608 608 else:
609 609 self._map[f] = dirstatetuple('n', 0o666, -1, 0)
610 610 self._pl = (parent, nullid)
611 611 self._dirty = True
612 612
613 613 def write(self):
614 614 if not self._dirty:
615 615 return
616 616
617 617 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
618 618 # timestamp of each entries in dirstate, because of 'now > mtime'
619 619 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
620 620 if delaywrite > 0:
621 621 import time # to avoid useless import
622 622 time.sleep(delaywrite)
623 623
624 624 st = self._opener(self._filename, "w", atomictemp=True)
625 625 # use the modification time of the newly created temporary file as the
626 626 # filesystem's notion of 'now'
627 627 now = util.fstat(st).st_mtime
628 628 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
629 629 st.close()
630 630 self._lastnormaltime = 0
631 631 self._dirty = self._dirtypl = False
632 632
633 633 def _dirignore(self, f):
634 634 if f == '.':
635 635 return False
636 636 if self._ignore(f):
637 637 return True
638 638 for p in util.finddirs(f):
639 639 if self._ignore(p):
640 640 return True
641 641 return False
642 642
643 643 def _walkexplicit(self, match, subrepos):
644 644 '''Get stat data about the files explicitly specified by match.
645 645
646 646 Return a triple (results, dirsfound, dirsnotfound).
647 647 - results is a mapping from filename to stat result. It also contains
648 648 listings mapping subrepos and .hg to None.
649 649 - dirsfound is a list of files found to be directories.
650 650 - dirsnotfound is a list of files that the dirstate thinks are
651 651 directories and that were not found.'''
652 652
653 653 def badtype(mode):
654 654 kind = _('unknown')
655 655 if stat.S_ISCHR(mode):
656 656 kind = _('character device')
657 657 elif stat.S_ISBLK(mode):
658 658 kind = _('block device')
659 659 elif stat.S_ISFIFO(mode):
660 660 kind = _('fifo')
661 661 elif stat.S_ISSOCK(mode):
662 662 kind = _('socket')
663 663 elif stat.S_ISDIR(mode):
664 664 kind = _('directory')
665 665 return _('unsupported file type (type is %s)') % kind
666 666
667 667 matchedir = match.explicitdir
668 668 badfn = match.bad
669 669 dmap = self._map
670 670 lstat = os.lstat
671 671 getkind = stat.S_IFMT
672 672 dirkind = stat.S_IFDIR
673 673 regkind = stat.S_IFREG
674 674 lnkkind = stat.S_IFLNK
675 675 join = self._join
676 676 dirsfound = []
677 677 foundadd = dirsfound.append
678 678 dirsnotfound = []
679 679 notfoundadd = dirsnotfound.append
680 680
681 681 if not match.isexact() and self._checkcase:
682 682 normalize = self._normalize
683 683 else:
684 684 normalize = None
685 685
686 686 files = sorted(match.files())
687 687 subrepos.sort()
688 688 i, j = 0, 0
689 689 while i < len(files) and j < len(subrepos):
690 690 subpath = subrepos[j] + "/"
691 691 if files[i] < subpath:
692 692 i += 1
693 693 continue
694 694 while i < len(files) and files[i].startswith(subpath):
695 695 del files[i]
696 696 j += 1
697 697
698 698 if not files or '.' in files:
699 699 files = ['.']
700 700 results = dict.fromkeys(subrepos)
701 701 results['.hg'] = None
702 702
703 703 alldirs = None
704 704 for ff in files:
705 705 # constructing the foldmap is expensive, so don't do it for the
706 706 # common case where files is ['.']
707 707 if normalize and ff != '.':
708 708 nf = normalize(ff, False, True)
709 709 else:
710 710 nf = ff
711 711 if nf in results:
712 712 continue
713 713
714 714 try:
715 715 st = lstat(join(nf))
716 716 kind = getkind(st.st_mode)
717 717 if kind == dirkind:
718 718 if nf in dmap:
719 719 # file replaced by dir on disk but still in dirstate
720 720 results[nf] = None
721 721 if matchedir:
722 722 matchedir(nf)
723 723 foundadd((nf, ff))
724 724 elif kind == regkind or kind == lnkkind:
725 725 results[nf] = st
726 726 else:
727 727 badfn(ff, badtype(kind))
728 728 if nf in dmap:
729 729 results[nf] = None
730 730 except OSError as inst: # nf not found on disk - it is dirstate only
731 731 if nf in dmap: # does it exactly match a missing file?
732 732 results[nf] = None
733 733 else: # does it match a missing directory?
734 734 if alldirs is None:
735 735 alldirs = util.dirs(dmap)
736 736 if nf in alldirs:
737 737 if matchedir:
738 738 matchedir(nf)
739 739 notfoundadd(nf)
740 740 else:
741 741 badfn(ff, inst.strerror)
742 742
743 743 # Case insensitive filesystems cannot rely on lstat() failing to detect
744 744 # a case-only rename. Prune the stat object for any file that does not
745 745 # match the case in the filesystem, if there are multiple files that
746 746 # normalize to the same path.
747 747 if match.isexact() and self._checkcase:
748 748 normed = {}
749 749
750 750 for f, st in results.iteritems():
751 751 if st is None:
752 752 continue
753 753
754 754 nc = util.normcase(f)
755 755 paths = normed.get(nc)
756 756
757 757 if paths is None:
758 758 paths = set()
759 759 normed[nc] = paths
760 760
761 761 paths.add(f)
762 762
763 763 for norm, paths in normed.iteritems():
764 764 if len(paths) > 1:
765 765 for path in paths:
766 766 folded = self._discoverpath(path, norm, True, None,
767 767 self._dirfoldmap)
768 768 if path != folded:
769 769 results[path] = None
770 770
771 771 return results, dirsfound, dirsnotfound
772 772
773 773 def walk(self, match, subrepos, unknown, ignored, full=True):
774 774 '''
775 775 Walk recursively through the directory tree, finding all files
776 776 matched by match.
777 777
778 778 If full is False, maybe skip some known-clean files.
779 779
780 780 Return a dict mapping filename to stat-like object (either
781 781 mercurial.osutil.stat instance or return value of os.stat()).
782 782
783 783 '''
784 784 # full is a flag that extensions that hook into walk can use -- this
785 785 # implementation doesn't use it at all. This satisfies the contract
786 786 # because we only guarantee a "maybe".
787 787
788 788 if ignored:
789 789 ignore = util.never
790 790 dirignore = util.never
791 791 elif unknown:
792 792 ignore = self._ignore
793 793 dirignore = self._dirignore
794 794 else:
795 795 # if not unknown and not ignored, drop dir recursion and step 2
796 796 ignore = util.always
797 797 dirignore = util.always
798 798
799 799 matchfn = match.matchfn
800 800 matchalways = match.always()
801 801 matchtdir = match.traversedir
802 802 dmap = self._map
803 803 listdir = osutil.listdir
804 804 lstat = os.lstat
805 805 dirkind = stat.S_IFDIR
806 806 regkind = stat.S_IFREG
807 807 lnkkind = stat.S_IFLNK
808 808 join = self._join
809 809
810 810 exact = skipstep3 = False
811 811 if match.isexact(): # match.exact
812 812 exact = True
813 813 dirignore = util.always # skip step 2
814 814 elif match.prefix(): # match.match, no patterns
815 815 skipstep3 = True
816 816
817 817 if not exact and self._checkcase:
818 818 normalize = self._normalize
819 819 normalizefile = self._normalizefile
820 820 skipstep3 = False
821 821 else:
822 822 normalize = self._normalize
823 823 normalizefile = None
824 824
825 825 # step 1: find all explicit files
826 826 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
827 827
828 828 skipstep3 = skipstep3 and not (work or dirsnotfound)
829 829 work = [d for d in work if not dirignore(d[0])]
830 830
831 831 # step 2: visit subdirectories
832 832 def traverse(work, alreadynormed):
833 833 wadd = work.append
834 834 while work:
835 835 nd = work.pop()
836 836 skip = None
837 837 if nd == '.':
838 838 nd = ''
839 839 else:
840 840 skip = '.hg'
841 841 try:
842 842 entries = listdir(join(nd), stat=True, skip=skip)
843 843 except OSError as inst:
844 844 if inst.errno in (errno.EACCES, errno.ENOENT):
845 845 match.bad(self.pathto(nd), inst.strerror)
846 846 continue
847 847 raise
848 848 for f, kind, st in entries:
849 849 if normalizefile:
850 850 # even though f might be a directory, we're only
851 851 # interested in comparing it to files currently in the
852 852 # dmap -- therefore normalizefile is enough
853 853 nf = normalizefile(nd and (nd + "/" + f) or f, True,
854 854 True)
855 855 else:
856 856 nf = nd and (nd + "/" + f) or f
857 857 if nf not in results:
858 858 if kind == dirkind:
859 859 if not ignore(nf):
860 860 if matchtdir:
861 861 matchtdir(nf)
862 862 wadd(nf)
863 863 if nf in dmap and (matchalways or matchfn(nf)):
864 864 results[nf] = None
865 865 elif kind == regkind or kind == lnkkind:
866 866 if nf in dmap:
867 867 if matchalways or matchfn(nf):
868 868 results[nf] = st
869 869 elif ((matchalways or matchfn(nf))
870 870 and not ignore(nf)):
871 871 # unknown file -- normalize if necessary
872 872 if not alreadynormed:
873 873 nf = normalize(nf, False, True)
874 874 results[nf] = st
875 875 elif nf in dmap and (matchalways or matchfn(nf)):
876 876 results[nf] = None
877 877
878 878 for nd, d in work:
879 879 # alreadynormed means that processwork doesn't have to do any
880 880 # expensive directory normalization
881 881 alreadynormed = not normalize or nd == d
882 882 traverse([d], alreadynormed)
883 883
884 884 for s in subrepos:
885 885 del results[s]
886 886 del results['.hg']
887 887
888 888 # step 3: visit remaining files from dmap
889 889 if not skipstep3 and not exact:
890 890 # If a dmap file is not in results yet, it was either
891 891 # a) not matching matchfn b) ignored, c) missing, or d) under a
892 892 # symlink directory.
893 893 if not results and matchalways:
894 894 visit = dmap.keys()
895 895 else:
896 896 visit = [f for f in dmap if f not in results and matchfn(f)]
897 897 visit.sort()
898 898
899 899 if unknown:
900 900 # unknown == True means we walked all dirs under the roots
901 901 # that wasn't ignored, and everything that matched was stat'ed
902 902 # and is already in results.
903 903 # The rest must thus be ignored or under a symlink.
904 904 audit_path = pathutil.pathauditor(self._root)
905 905
906 906 for nf in iter(visit):
907 907 # If a stat for the same file was already added with a
908 908 # different case, don't add one for this, since that would
909 909 # make it appear as if the file exists under both names
910 910 # on disk.
911 911 if (normalizefile and
912 912 normalizefile(nf, True, True) in results):
913 913 results[nf] = None
914 914 # Report ignored items in the dmap as long as they are not
915 915 # under a symlink directory.
916 916 elif audit_path.check(nf):
917 917 try:
918 918 results[nf] = lstat(join(nf))
919 919 # file was just ignored, no links, and exists
920 920 except OSError:
921 921 # file doesn't exist
922 922 results[nf] = None
923 923 else:
924 924 # It's either missing or under a symlink directory
925 925 # which we in this case report as missing
926 926 results[nf] = None
927 927 else:
928 928 # We may not have walked the full directory tree above,
929 929 # so stat and check everything we missed.
930 930 nf = iter(visit).next
931 931 for st in util.statfiles([join(i) for i in visit]):
932 932 results[nf()] = st
933 933 return results
934 934
935 935 def status(self, match, subrepos, ignored, clean, unknown):
936 936 '''Determine the status of the working copy relative to the
937 937 dirstate and return a pair of (unsure, status), where status is of type
938 938 scmutil.status and:
939 939
940 940 unsure:
941 941 files that might have been modified since the dirstate was
942 942 written, but need to be read to be sure (size is the same
943 943 but mtime differs)
944 944 status.modified:
945 945 files that have definitely been modified since the dirstate
946 946 was written (different size or mode)
947 947 status.clean:
948 948 files that have definitely not been modified since the
949 949 dirstate was written
950 950 '''
951 951 listignored, listclean, listunknown = ignored, clean, unknown
952 952 lookup, modified, added, unknown, ignored = [], [], [], [], []
953 953 removed, deleted, clean = [], [], []
954 954
955 955 dmap = self._map
956 956 ladd = lookup.append # aka "unsure"
957 957 madd = modified.append
958 958 aadd = added.append
959 959 uadd = unknown.append
960 960 iadd = ignored.append
961 961 radd = removed.append
962 962 dadd = deleted.append
963 963 cadd = clean.append
964 964 mexact = match.exact
965 965 dirignore = self._dirignore
966 966 checkexec = self._checkexec
967 967 copymap = self._copymap
968 968 lastnormaltime = self._lastnormaltime
969 969
970 970 # We need to do full walks when either
971 971 # - we're listing all clean files, or
972 972 # - match.traversedir does something, because match.traversedir should
973 973 # be called for every dir in the working dir
974 974 full = listclean or match.traversedir is not None
975 975 for fn, st in self.walk(match, subrepos, listunknown, listignored,
976 976 full=full).iteritems():
977 977 if fn not in dmap:
978 978 if (listignored or mexact(fn)) and dirignore(fn):
979 979 if listignored:
980 980 iadd(fn)
981 981 else:
982 982 uadd(fn)
983 983 continue
984 984
985 985 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
986 986 # written like that for performance reasons. dmap[fn] is not a
987 987 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
988 988 # opcode has fast paths when the value to be unpacked is a tuple or
989 989 # a list, but falls back to creating a full-fledged iterator in
990 990 # general. That is much slower than simply accessing and storing the
991 991 # tuple members one by one.
992 992 t = dmap[fn]
993 993 state = t[0]
994 994 mode = t[1]
995 995 size = t[2]
996 996 time = t[3]
997 997
998 998 if not st and state in "nma":
999 999 dadd(fn)
1000 1000 elif state == 'n':
1001 mtime = int(st.st_mtime)
1001 mtime = util.statmtimesec(st)
1002 1002 if (size >= 0 and
1003 1003 ((size != st.st_size and size != st.st_size & _rangemask)
1004 1004 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1005 1005 or size == -2 # other parent
1006 1006 or fn in copymap):
1007 1007 madd(fn)
1008 1008 elif time != mtime and time != mtime & _rangemask:
1009 1009 ladd(fn)
1010 1010 elif mtime == lastnormaltime:
1011 1011 # fn may have just been marked as normal and it may have
1012 1012 # changed in the same second without changing its size.
1013 1013 # This can happen if we quickly do multiple commits.
1014 1014 # Force lookup, so we don't miss such a racy file change.
1015 1015 ladd(fn)
1016 1016 elif listclean:
1017 1017 cadd(fn)
1018 1018 elif state == 'm':
1019 1019 madd(fn)
1020 1020 elif state == 'a':
1021 1021 aadd(fn)
1022 1022 elif state == 'r':
1023 1023 radd(fn)
1024 1024
1025 1025 return (lookup, scmutil.status(modified, added, removed, deleted,
1026 1026 unknown, ignored, clean))
1027 1027
1028 1028 def matches(self, match):
1029 1029 '''
1030 1030 return files in the dirstate (in whatever state) filtered by match
1031 1031 '''
1032 1032 dmap = self._map
1033 1033 if match.always():
1034 1034 return dmap.keys()
1035 1035 files = match.files()
1036 1036 if match.isexact():
1037 1037 # fast path -- filter the other way around, since typically files is
1038 1038 # much smaller than dmap
1039 1039 return [f for f in files if f in dmap]
1040 1040 if match.prefix() and all(fn in dmap for fn in files):
1041 1041 # fast path -- all the values are known to be files, so just return
1042 1042 # that
1043 1043 return list(files)
1044 1044 return [f for f in dmap if match(f)]
@@ -1,2462 +1,2465 b''
1 1 # util.py - Mercurial utility functions and platform specific 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 specific 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 import i18n
17 17 _ = i18n._
18 18 import error, osutil, encoding, parsers
19 19 import errno, shutil, sys, tempfile, traceback
20 20 import re as remod
21 21 import os, time, datetime, calendar, textwrap, signal, collections
22 22 import imp, socket, urllib
23 23 import gc
24 24 import bz2
25 25 import zlib
26 26
27 27 if os.name == 'nt':
28 28 import windows as platform
29 29 else:
30 30 import posix as platform
31 31
32 32 cachestat = platform.cachestat
33 33 checkexec = platform.checkexec
34 34 checklink = platform.checklink
35 35 copymode = platform.copymode
36 36 executablepath = platform.executablepath
37 37 expandglobs = platform.expandglobs
38 38 explainexit = platform.explainexit
39 39 findexe = platform.findexe
40 40 gethgcmd = platform.gethgcmd
41 41 getuser = platform.getuser
42 42 groupmembers = platform.groupmembers
43 43 groupname = platform.groupname
44 44 hidewindow = platform.hidewindow
45 45 isexec = platform.isexec
46 46 isowner = platform.isowner
47 47 localpath = platform.localpath
48 48 lookupreg = platform.lookupreg
49 49 makedir = platform.makedir
50 50 nlinks = platform.nlinks
51 51 normpath = platform.normpath
52 52 normcase = platform.normcase
53 53 normcasespec = platform.normcasespec
54 54 normcasefallback = platform.normcasefallback
55 55 openhardlinks = platform.openhardlinks
56 56 oslink = platform.oslink
57 57 parsepatchoutput = platform.parsepatchoutput
58 58 pconvert = platform.pconvert
59 59 poll = platform.poll
60 60 popen = platform.popen
61 61 posixfile = platform.posixfile
62 62 quotecommand = platform.quotecommand
63 63 readpipe = platform.readpipe
64 64 rename = platform.rename
65 65 removedirs = platform.removedirs
66 66 samedevice = platform.samedevice
67 67 samefile = platform.samefile
68 68 samestat = platform.samestat
69 69 setbinary = platform.setbinary
70 70 setflags = platform.setflags
71 71 setsignalhandler = platform.setsignalhandler
72 72 shellquote = platform.shellquote
73 73 spawndetached = platform.spawndetached
74 74 split = platform.split
75 75 sshargs = platform.sshargs
76 76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
77 77 statisexec = platform.statisexec
78 78 statislink = platform.statislink
79 79 termwidth = platform.termwidth
80 80 testpid = platform.testpid
81 81 umask = platform.umask
82 82 unlink = platform.unlink
83 83 unlinkpath = platform.unlinkpath
84 84 username = platform.username
85 85
86 86 # Python compatibility
87 87
88 88 _notset = object()
89 89
90 90 def safehasattr(thing, attr):
91 91 return getattr(thing, attr, _notset) is not _notset
92 92
93 93 def sha1(s=''):
94 94 '''
95 95 Low-overhead wrapper around Python's SHA support
96 96
97 97 >>> f = _fastsha1
98 98 >>> a = sha1()
99 99 >>> a = f()
100 100 >>> a.hexdigest()
101 101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
102 102 '''
103 103
104 104 return _fastsha1(s)
105 105
106 106 def _fastsha1(s=''):
107 107 # This function will import sha1 from hashlib or sha (whichever is
108 108 # available) and overwrite itself with it on the first call.
109 109 # Subsequent calls will go directly to the imported function.
110 110 if sys.version_info >= (2, 5):
111 111 from hashlib import sha1 as _sha1
112 112 else:
113 113 from sha import sha as _sha1
114 114 global _fastsha1, sha1
115 115 _fastsha1 = sha1 = _sha1
116 116 return _sha1(s)
117 117
118 118 def md5(s=''):
119 119 try:
120 120 from hashlib import md5 as _md5
121 121 except ImportError:
122 122 from md5 import md5 as _md5
123 123 global md5
124 124 md5 = _md5
125 125 return _md5(s)
126 126
127 127 DIGESTS = {
128 128 'md5': md5,
129 129 'sha1': sha1,
130 130 }
131 131 # List of digest types from strongest to weakest
132 132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
133 133
134 134 try:
135 135 import hashlib
136 136 DIGESTS.update({
137 137 'sha512': hashlib.sha512,
138 138 })
139 139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
140 140 except ImportError:
141 141 pass
142 142
143 143 for k in DIGESTS_BY_STRENGTH:
144 144 assert k in DIGESTS
145 145
146 146 class digester(object):
147 147 """helper to compute digests.
148 148
149 149 This helper can be used to compute one or more digests given their name.
150 150
151 151 >>> d = digester(['md5', 'sha1'])
152 152 >>> d.update('foo')
153 153 >>> [k for k in sorted(d)]
154 154 ['md5', 'sha1']
155 155 >>> d['md5']
156 156 'acbd18db4cc2f85cedef654fccc4a4d8'
157 157 >>> d['sha1']
158 158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
159 159 >>> digester.preferred(['md5', 'sha1'])
160 160 'sha1'
161 161 """
162 162
163 163 def __init__(self, digests, s=''):
164 164 self._hashes = {}
165 165 for k in digests:
166 166 if k not in DIGESTS:
167 167 raise Abort(_('unknown digest type: %s') % k)
168 168 self._hashes[k] = DIGESTS[k]()
169 169 if s:
170 170 self.update(s)
171 171
172 172 def update(self, data):
173 173 for h in self._hashes.values():
174 174 h.update(data)
175 175
176 176 def __getitem__(self, key):
177 177 if key not in DIGESTS:
178 178 raise Abort(_('unknown digest type: %s') % k)
179 179 return self._hashes[key].hexdigest()
180 180
181 181 def __iter__(self):
182 182 return iter(self._hashes)
183 183
184 184 @staticmethod
185 185 def preferred(supported):
186 186 """returns the strongest digest type in both supported and DIGESTS."""
187 187
188 188 for k in DIGESTS_BY_STRENGTH:
189 189 if k in supported:
190 190 return k
191 191 return None
192 192
193 193 class digestchecker(object):
194 194 """file handle wrapper that additionally checks content against a given
195 195 size and digests.
196 196
197 197 d = digestchecker(fh, size, {'md5': '...'})
198 198
199 199 When multiple digests are given, all of them are validated.
200 200 """
201 201
202 202 def __init__(self, fh, size, digests):
203 203 self._fh = fh
204 204 self._size = size
205 205 self._got = 0
206 206 self._digests = dict(digests)
207 207 self._digester = digester(self._digests.keys())
208 208
209 209 def read(self, length=-1):
210 210 content = self._fh.read(length)
211 211 self._digester.update(content)
212 212 self._got += len(content)
213 213 return content
214 214
215 215 def validate(self):
216 216 if self._size != self._got:
217 217 raise Abort(_('size mismatch: expected %d, got %d') %
218 218 (self._size, self._got))
219 219 for k, v in self._digests.items():
220 220 if v != self._digester[k]:
221 221 # i18n: first parameter is a digest name
222 222 raise Abort(_('%s mismatch: expected %s, got %s') %
223 223 (k, v, self._digester[k]))
224 224
225 225 try:
226 226 buffer = buffer
227 227 except NameError:
228 228 if sys.version_info[0] < 3:
229 229 def buffer(sliceable, offset=0):
230 230 return sliceable[offset:]
231 231 else:
232 232 def buffer(sliceable, offset=0):
233 233 return memoryview(sliceable)[offset:]
234 234
235 235 import subprocess
236 236 closefds = os.name == 'posix'
237 237
238 238 _chunksize = 4096
239 239
240 240 class bufferedinputpipe(object):
241 241 """a manually buffered input pipe
242 242
243 243 Python will not let us use buffered IO and lazy reading with 'polling' at
244 244 the same time. We cannot probe the buffer state and select will not detect
245 245 that data are ready to read if they are already buffered.
246 246
247 247 This class let us work around that by implementing its own buffering
248 248 (allowing efficient readline) while offering a way to know if the buffer is
249 249 empty from the output (allowing collaboration of the buffer with polling).
250 250
251 251 This class lives in the 'util' module because it makes use of the 'os'
252 252 module from the python stdlib.
253 253 """
254 254
255 255 def __init__(self, input):
256 256 self._input = input
257 257 self._buffer = []
258 258 self._eof = False
259 259 self._lenbuf = 0
260 260
261 261 @property
262 262 def hasbuffer(self):
263 263 """True is any data is currently buffered
264 264
265 265 This will be used externally a pre-step for polling IO. If there is
266 266 already data then no polling should be set in place."""
267 267 return bool(self._buffer)
268 268
269 269 @property
270 270 def closed(self):
271 271 return self._input.closed
272 272
273 273 def fileno(self):
274 274 return self._input.fileno()
275 275
276 276 def close(self):
277 277 return self._input.close()
278 278
279 279 def read(self, size):
280 280 while (not self._eof) and (self._lenbuf < size):
281 281 self._fillbuffer()
282 282 return self._frombuffer(size)
283 283
284 284 def readline(self, *args, **kwargs):
285 285 if 1 < len(self._buffer):
286 286 # this should not happen because both read and readline end with a
287 287 # _frombuffer call that collapse it.
288 288 self._buffer = [''.join(self._buffer)]
289 289 self._lenbuf = len(self._buffer[0])
290 290 lfi = -1
291 291 if self._buffer:
292 292 lfi = self._buffer[-1].find('\n')
293 293 while (not self._eof) and lfi < 0:
294 294 self._fillbuffer()
295 295 if self._buffer:
296 296 lfi = self._buffer[-1].find('\n')
297 297 size = lfi + 1
298 298 if lfi < 0: # end of file
299 299 size = self._lenbuf
300 300 elif 1 < len(self._buffer):
301 301 # we need to take previous chunks into account
302 302 size += self._lenbuf - len(self._buffer[-1])
303 303 return self._frombuffer(size)
304 304
305 305 def _frombuffer(self, size):
306 306 """return at most 'size' data from the buffer
307 307
308 308 The data are removed from the buffer."""
309 309 if size == 0 or not self._buffer:
310 310 return ''
311 311 buf = self._buffer[0]
312 312 if 1 < len(self._buffer):
313 313 buf = ''.join(self._buffer)
314 314
315 315 data = buf[:size]
316 316 buf = buf[len(data):]
317 317 if buf:
318 318 self._buffer = [buf]
319 319 self._lenbuf = len(buf)
320 320 else:
321 321 self._buffer = []
322 322 self._lenbuf = 0
323 323 return data
324 324
325 325 def _fillbuffer(self):
326 326 """read data to the buffer"""
327 327 data = os.read(self._input.fileno(), _chunksize)
328 328 if not data:
329 329 self._eof = True
330 330 else:
331 331 self._lenbuf += len(data)
332 332 self._buffer.append(data)
333 333
334 334 def popen2(cmd, env=None, newlines=False):
335 335 # Setting bufsize to -1 lets the system decide the buffer size.
336 336 # The default for bufsize is 0, meaning unbuffered. This leads to
337 337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
338 338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
339 339 close_fds=closefds,
340 340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
341 341 universal_newlines=newlines,
342 342 env=env)
343 343 return p.stdin, p.stdout
344 344
345 345 def popen3(cmd, env=None, newlines=False):
346 346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
347 347 return stdin, stdout, stderr
348 348
349 349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
350 350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
351 351 close_fds=closefds,
352 352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
353 353 stderr=subprocess.PIPE,
354 354 universal_newlines=newlines,
355 355 env=env)
356 356 return p.stdin, p.stdout, p.stderr, p
357 357
358 358 def version():
359 359 """Return version information if available."""
360 360 try:
361 361 import __version__
362 362 return __version__.version
363 363 except ImportError:
364 364 return 'unknown'
365 365
366 366 # used by parsedate
367 367 defaultdateformats = (
368 368 '%Y-%m-%d %H:%M:%S',
369 369 '%Y-%m-%d %I:%M:%S%p',
370 370 '%Y-%m-%d %H:%M',
371 371 '%Y-%m-%d %I:%M%p',
372 372 '%Y-%m-%d',
373 373 '%m-%d',
374 374 '%m/%d',
375 375 '%m/%d/%y',
376 376 '%m/%d/%Y',
377 377 '%a %b %d %H:%M:%S %Y',
378 378 '%a %b %d %I:%M:%S%p %Y',
379 379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
380 380 '%b %d %H:%M:%S %Y',
381 381 '%b %d %I:%M:%S%p %Y',
382 382 '%b %d %H:%M:%S',
383 383 '%b %d %I:%M:%S%p',
384 384 '%b %d %H:%M',
385 385 '%b %d %I:%M%p',
386 386 '%b %d %Y',
387 387 '%b %d',
388 388 '%H:%M:%S',
389 389 '%I:%M:%S%p',
390 390 '%H:%M',
391 391 '%I:%M%p',
392 392 )
393 393
394 394 extendeddateformats = defaultdateformats + (
395 395 "%Y",
396 396 "%Y-%m",
397 397 "%b",
398 398 "%b %Y",
399 399 )
400 400
401 401 def cachefunc(func):
402 402 '''cache the result of function calls'''
403 403 # XXX doesn't handle keywords args
404 404 if func.func_code.co_argcount == 0:
405 405 cache = []
406 406 def f():
407 407 if len(cache) == 0:
408 408 cache.append(func())
409 409 return cache[0]
410 410 return f
411 411 cache = {}
412 412 if func.func_code.co_argcount == 1:
413 413 # we gain a small amount of time because
414 414 # we don't need to pack/unpack the list
415 415 def f(arg):
416 416 if arg not in cache:
417 417 cache[arg] = func(arg)
418 418 return cache[arg]
419 419 else:
420 420 def f(*args):
421 421 if args not in cache:
422 422 cache[args] = func(*args)
423 423 return cache[args]
424 424
425 425 return f
426 426
427 427 class sortdict(dict):
428 428 '''a simple sorted dictionary'''
429 429 def __init__(self, data=None):
430 430 self._list = []
431 431 if data:
432 432 self.update(data)
433 433 def copy(self):
434 434 return sortdict(self)
435 435 def __setitem__(self, key, val):
436 436 if key in self:
437 437 self._list.remove(key)
438 438 self._list.append(key)
439 439 dict.__setitem__(self, key, val)
440 440 def __iter__(self):
441 441 return self._list.__iter__()
442 442 def update(self, src):
443 443 if isinstance(src, dict):
444 444 src = src.iteritems()
445 445 for k, v in src:
446 446 self[k] = v
447 447 def clear(self):
448 448 dict.clear(self)
449 449 self._list = []
450 450 def items(self):
451 451 return [(k, self[k]) for k in self._list]
452 452 def __delitem__(self, key):
453 453 dict.__delitem__(self, key)
454 454 self._list.remove(key)
455 455 def pop(self, key, *args, **kwargs):
456 456 dict.pop(self, key, *args, **kwargs)
457 457 try:
458 458 self._list.remove(key)
459 459 except ValueError:
460 460 pass
461 461 def keys(self):
462 462 return self._list
463 463 def iterkeys(self):
464 464 return self._list.__iter__()
465 465 def iteritems(self):
466 466 for k in self._list:
467 467 yield k, self[k]
468 468 def insert(self, index, key, val):
469 469 self._list.insert(index, key)
470 470 dict.__setitem__(self, key, val)
471 471
472 472 class lrucachedict(object):
473 473 '''cache most recent gets from or sets to this dictionary'''
474 474 def __init__(self, maxsize):
475 475 self._cache = {}
476 476 self._maxsize = maxsize
477 477 self._order = collections.deque()
478 478
479 479 def __getitem__(self, key):
480 480 value = self._cache[key]
481 481 self._order.remove(key)
482 482 self._order.append(key)
483 483 return value
484 484
485 485 def __setitem__(self, key, value):
486 486 if key not in self._cache:
487 487 if len(self._cache) >= self._maxsize:
488 488 del self._cache[self._order.popleft()]
489 489 else:
490 490 self._order.remove(key)
491 491 self._cache[key] = value
492 492 self._order.append(key)
493 493
494 494 def __contains__(self, key):
495 495 return key in self._cache
496 496
497 497 def clear(self):
498 498 self._cache.clear()
499 499 self._order = collections.deque()
500 500
501 501 def lrucachefunc(func):
502 502 '''cache most recent results of function calls'''
503 503 cache = {}
504 504 order = collections.deque()
505 505 if func.func_code.co_argcount == 1:
506 506 def f(arg):
507 507 if arg not in cache:
508 508 if len(cache) > 20:
509 509 del cache[order.popleft()]
510 510 cache[arg] = func(arg)
511 511 else:
512 512 order.remove(arg)
513 513 order.append(arg)
514 514 return cache[arg]
515 515 else:
516 516 def f(*args):
517 517 if args not in cache:
518 518 if len(cache) > 20:
519 519 del cache[order.popleft()]
520 520 cache[args] = func(*args)
521 521 else:
522 522 order.remove(args)
523 523 order.append(args)
524 524 return cache[args]
525 525
526 526 return f
527 527
528 528 class propertycache(object):
529 529 def __init__(self, func):
530 530 self.func = func
531 531 self.name = func.__name__
532 532 def __get__(self, obj, type=None):
533 533 result = self.func(obj)
534 534 self.cachevalue(obj, result)
535 535 return result
536 536
537 537 def cachevalue(self, obj, value):
538 538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
539 539 obj.__dict__[self.name] = value
540 540
541 541 def pipefilter(s, cmd):
542 542 '''filter string S through command CMD, returning its output'''
543 543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
544 544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
545 545 pout, perr = p.communicate(s)
546 546 return pout
547 547
548 548 def tempfilter(s, cmd):
549 549 '''filter string S through a pair of temporary files with CMD.
550 550 CMD is used as a template to create the real command to be run,
551 551 with the strings INFILE and OUTFILE replaced by the real names of
552 552 the temporary files generated.'''
553 553 inname, outname = None, None
554 554 try:
555 555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
556 556 fp = os.fdopen(infd, 'wb')
557 557 fp.write(s)
558 558 fp.close()
559 559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
560 560 os.close(outfd)
561 561 cmd = cmd.replace('INFILE', inname)
562 562 cmd = cmd.replace('OUTFILE', outname)
563 563 code = os.system(cmd)
564 564 if sys.platform == 'OpenVMS' and code & 1:
565 565 code = 0
566 566 if code:
567 567 raise Abort(_("command '%s' failed: %s") %
568 568 (cmd, explainexit(code)))
569 569 fp = open(outname, 'rb')
570 570 r = fp.read()
571 571 fp.close()
572 572 return r
573 573 finally:
574 574 try:
575 575 if inname:
576 576 os.unlink(inname)
577 577 except OSError:
578 578 pass
579 579 try:
580 580 if outname:
581 581 os.unlink(outname)
582 582 except OSError:
583 583 pass
584 584
585 585 filtertable = {
586 586 'tempfile:': tempfilter,
587 587 'pipe:': pipefilter,
588 588 }
589 589
590 590 def filter(s, cmd):
591 591 "filter a string through a command that transforms its input to its output"
592 592 for name, fn in filtertable.iteritems():
593 593 if cmd.startswith(name):
594 594 return fn(s, cmd[len(name):].lstrip())
595 595 return pipefilter(s, cmd)
596 596
597 597 def binary(s):
598 598 """return true if a string is binary data"""
599 599 return bool(s and '\0' in s)
600 600
601 601 def increasingchunks(source, min=1024, max=65536):
602 602 '''return no less than min bytes per chunk while data remains,
603 603 doubling min after each chunk until it reaches max'''
604 604 def log2(x):
605 605 if not x:
606 606 return 0
607 607 i = 0
608 608 while x:
609 609 x >>= 1
610 610 i += 1
611 611 return i - 1
612 612
613 613 buf = []
614 614 blen = 0
615 615 for chunk in source:
616 616 buf.append(chunk)
617 617 blen += len(chunk)
618 618 if blen >= min:
619 619 if min < max:
620 620 min = min << 1
621 621 nmin = 1 << log2(blen)
622 622 if nmin > min:
623 623 min = nmin
624 624 if min > max:
625 625 min = max
626 626 yield ''.join(buf)
627 627 blen = 0
628 628 buf = []
629 629 if buf:
630 630 yield ''.join(buf)
631 631
632 632 Abort = error.Abort
633 633
634 634 def always(fn):
635 635 return True
636 636
637 637 def never(fn):
638 638 return False
639 639
640 640 def nogc(func):
641 641 """disable garbage collector
642 642
643 643 Python's garbage collector triggers a GC each time a certain number of
644 644 container objects (the number being defined by gc.get_threshold()) are
645 645 allocated even when marked not to be tracked by the collector. Tracking has
646 646 no effect on when GCs are triggered, only on what objects the GC looks
647 647 into. As a workaround, disable GC while building complex (huge)
648 648 containers.
649 649
650 650 This garbage collector issue have been fixed in 2.7.
651 651 """
652 652 def wrapper(*args, **kwargs):
653 653 gcenabled = gc.isenabled()
654 654 gc.disable()
655 655 try:
656 656 return func(*args, **kwargs)
657 657 finally:
658 658 if gcenabled:
659 659 gc.enable()
660 660 return wrapper
661 661
662 662 def pathto(root, n1, n2):
663 663 '''return the relative path from one place to another.
664 664 root should use os.sep to separate directories
665 665 n1 should use os.sep to separate directories
666 666 n2 should use "/" to separate directories
667 667 returns an os.sep-separated path.
668 668
669 669 If n1 is a relative path, it's assumed it's
670 670 relative to root.
671 671 n2 should always be relative to root.
672 672 '''
673 673 if not n1:
674 674 return localpath(n2)
675 675 if os.path.isabs(n1):
676 676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
677 677 return os.path.join(root, localpath(n2))
678 678 n2 = '/'.join((pconvert(root), n2))
679 679 a, b = splitpath(n1), n2.split('/')
680 680 a.reverse()
681 681 b.reverse()
682 682 while a and b and a[-1] == b[-1]:
683 683 a.pop()
684 684 b.pop()
685 685 b.reverse()
686 686 return os.sep.join((['..'] * len(a)) + b) or '.'
687 687
688 688 def mainfrozen():
689 689 """return True if we are a frozen executable.
690 690
691 691 The code supports py2exe (most common, Windows only) and tools/freeze
692 692 (portable, not much used).
693 693 """
694 694 return (safehasattr(sys, "frozen") or # new py2exe
695 695 safehasattr(sys, "importers") or # old py2exe
696 696 imp.is_frozen("__main__")) # tools/freeze
697 697
698 698 # the location of data files matching the source code
699 699 if mainfrozen():
700 700 # executable version (py2exe) doesn't support __file__
701 701 datapath = os.path.dirname(sys.executable)
702 702 else:
703 703 datapath = os.path.dirname(__file__)
704 704
705 705 i18n.setdatapath(datapath)
706 706
707 707 _hgexecutable = None
708 708
709 709 def hgexecutable():
710 710 """return location of the 'hg' executable.
711 711
712 712 Defaults to $HG or 'hg' in the search path.
713 713 """
714 714 if _hgexecutable is None:
715 715 hg = os.environ.get('HG')
716 716 mainmod = sys.modules['__main__']
717 717 if hg:
718 718 _sethgexecutable(hg)
719 719 elif mainfrozen():
720 720 _sethgexecutable(sys.executable)
721 721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
722 722 _sethgexecutable(mainmod.__file__)
723 723 else:
724 724 exe = findexe('hg') or os.path.basename(sys.argv[0])
725 725 _sethgexecutable(exe)
726 726 return _hgexecutable
727 727
728 728 def _sethgexecutable(path):
729 729 """set location of the 'hg' executable"""
730 730 global _hgexecutable
731 731 _hgexecutable = path
732 732
733 733 def _isstdout(f):
734 734 fileno = getattr(f, 'fileno', None)
735 735 return fileno and fileno() == sys.__stdout__.fileno()
736 736
737 737 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
738 738 '''enhanced shell command execution.
739 739 run with environment maybe modified, maybe in different dir.
740 740
741 741 if command fails and onerr is None, return status, else raise onerr
742 742 object as exception.
743 743
744 744 if out is specified, it is assumed to be a file-like object that has a
745 745 write() method. stdout and stderr will be redirected to out.'''
746 746 if environ is None:
747 747 environ = {}
748 748 try:
749 749 sys.stdout.flush()
750 750 except Exception:
751 751 pass
752 752 def py2shell(val):
753 753 'convert python object into string that is useful to shell'
754 754 if val is None or val is False:
755 755 return '0'
756 756 if val is True:
757 757 return '1'
758 758 return str(val)
759 759 origcmd = cmd
760 760 cmd = quotecommand(cmd)
761 761 if sys.platform == 'plan9' and (sys.version_info[0] == 2
762 762 and sys.version_info[1] < 7):
763 763 # subprocess kludge to work around issues in half-baked Python
764 764 # ports, notably bichued/python:
765 765 if not cwd is None:
766 766 os.chdir(cwd)
767 767 rc = os.system(cmd)
768 768 else:
769 769 env = dict(os.environ)
770 770 env.update((k, py2shell(v)) for k, v in environ.iteritems())
771 771 env['HG'] = hgexecutable()
772 772 if out is None or _isstdout(out):
773 773 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
774 774 env=env, cwd=cwd)
775 775 else:
776 776 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
777 777 env=env, cwd=cwd, stdout=subprocess.PIPE,
778 778 stderr=subprocess.STDOUT)
779 779 while True:
780 780 line = proc.stdout.readline()
781 781 if not line:
782 782 break
783 783 out.write(line)
784 784 proc.wait()
785 785 rc = proc.returncode
786 786 if sys.platform == 'OpenVMS' and rc & 1:
787 787 rc = 0
788 788 if rc and onerr:
789 789 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
790 790 explainexit(rc)[0])
791 791 if errprefix:
792 792 errmsg = '%s: %s' % (errprefix, errmsg)
793 793 raise onerr(errmsg)
794 794 return rc
795 795
796 796 def checksignature(func):
797 797 '''wrap a function with code to check for calling errors'''
798 798 def check(*args, **kwargs):
799 799 try:
800 800 return func(*args, **kwargs)
801 801 except TypeError:
802 802 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
803 803 raise error.SignatureError
804 804 raise
805 805
806 806 return check
807 807
808 808 def copyfile(src, dest, hardlink=False):
809 809 "copy a file, preserving mode and atime/mtime"
810 810 if os.path.lexists(dest):
811 811 unlink(dest)
812 812 # hardlinks are problematic on CIFS, quietly ignore this flag
813 813 # until we find a way to work around it cleanly (issue4546)
814 814 if False and hardlink:
815 815 try:
816 816 oslink(src, dest)
817 817 return
818 818 except (IOError, OSError):
819 819 pass # fall back to normal copy
820 820 if os.path.islink(src):
821 821 os.symlink(os.readlink(src), dest)
822 822 else:
823 823 try:
824 824 shutil.copyfile(src, dest)
825 825 shutil.copymode(src, dest)
826 826 except shutil.Error as inst:
827 827 raise Abort(str(inst))
828 828
829 829 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
830 830 """Copy a directory tree using hardlinks if possible."""
831 831 num = 0
832 832
833 833 if hardlink is None:
834 834 hardlink = (os.stat(src).st_dev ==
835 835 os.stat(os.path.dirname(dst)).st_dev)
836 836 if hardlink:
837 837 topic = _('linking')
838 838 else:
839 839 topic = _('copying')
840 840
841 841 if os.path.isdir(src):
842 842 os.mkdir(dst)
843 843 for name, kind in osutil.listdir(src):
844 844 srcname = os.path.join(src, name)
845 845 dstname = os.path.join(dst, name)
846 846 def nprog(t, pos):
847 847 if pos is not None:
848 848 return progress(t, pos + num)
849 849 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
850 850 num += n
851 851 else:
852 852 if hardlink:
853 853 try:
854 854 oslink(src, dst)
855 855 except (IOError, OSError):
856 856 hardlink = False
857 857 shutil.copy(src, dst)
858 858 else:
859 859 shutil.copy(src, dst)
860 860 num += 1
861 861 progress(topic, num)
862 862 progress(topic, None)
863 863
864 864 return hardlink, num
865 865
866 866 _winreservednames = '''con prn aux nul
867 867 com1 com2 com3 com4 com5 com6 com7 com8 com9
868 868 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
869 869 _winreservedchars = ':*?"<>|'
870 870 def checkwinfilename(path):
871 871 r'''Check that the base-relative path is a valid filename on Windows.
872 872 Returns None if the path is ok, or a UI string describing the problem.
873 873
874 874 >>> checkwinfilename("just/a/normal/path")
875 875 >>> checkwinfilename("foo/bar/con.xml")
876 876 "filename contains 'con', which is reserved on Windows"
877 877 >>> checkwinfilename("foo/con.xml/bar")
878 878 "filename contains 'con', which is reserved on Windows"
879 879 >>> checkwinfilename("foo/bar/xml.con")
880 880 >>> checkwinfilename("foo/bar/AUX/bla.txt")
881 881 "filename contains 'AUX', which is reserved on Windows"
882 882 >>> checkwinfilename("foo/bar/bla:.txt")
883 883 "filename contains ':', which is reserved on Windows"
884 884 >>> checkwinfilename("foo/bar/b\07la.txt")
885 885 "filename contains '\\x07', which is invalid on Windows"
886 886 >>> checkwinfilename("foo/bar/bla ")
887 887 "filename ends with ' ', which is not allowed on Windows"
888 888 >>> checkwinfilename("../bar")
889 889 >>> checkwinfilename("foo\\")
890 890 "filename ends with '\\', which is invalid on Windows"
891 891 >>> checkwinfilename("foo\\/bar")
892 892 "directory name ends with '\\', which is invalid on Windows"
893 893 '''
894 894 if path.endswith('\\'):
895 895 return _("filename ends with '\\', which is invalid on Windows")
896 896 if '\\/' in path:
897 897 return _("directory name ends with '\\', which is invalid on Windows")
898 898 for n in path.replace('\\', '/').split('/'):
899 899 if not n:
900 900 continue
901 901 for c in n:
902 902 if c in _winreservedchars:
903 903 return _("filename contains '%s', which is reserved "
904 904 "on Windows") % c
905 905 if ord(c) <= 31:
906 906 return _("filename contains %r, which is invalid "
907 907 "on Windows") % c
908 908 base = n.split('.')[0]
909 909 if base and base.lower() in _winreservednames:
910 910 return _("filename contains '%s', which is reserved "
911 911 "on Windows") % base
912 912 t = n[-1]
913 913 if t in '. ' and n not in '..':
914 914 return _("filename ends with '%s', which is not allowed "
915 915 "on Windows") % t
916 916
917 917 if os.name == 'nt':
918 918 checkosfilename = checkwinfilename
919 919 else:
920 920 checkosfilename = platform.checkosfilename
921 921
922 922 def makelock(info, pathname):
923 923 try:
924 924 return os.symlink(info, pathname)
925 925 except OSError as why:
926 926 if why.errno == errno.EEXIST:
927 927 raise
928 928 except AttributeError: # no symlink in os
929 929 pass
930 930
931 931 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
932 932 os.write(ld, info)
933 933 os.close(ld)
934 934
935 935 def readlock(pathname):
936 936 try:
937 937 return os.readlink(pathname)
938 938 except OSError as why:
939 939 if why.errno not in (errno.EINVAL, errno.ENOSYS):
940 940 raise
941 941 except AttributeError: # no symlink in os
942 942 pass
943 943 fp = posixfile(pathname)
944 944 r = fp.read()
945 945 fp.close()
946 946 return r
947 947
948 948 def fstat(fp):
949 949 '''stat file object that may not have fileno method.'''
950 950 try:
951 951 return os.fstat(fp.fileno())
952 952 except AttributeError:
953 953 return os.stat(fp.name)
954 954
955 def statmtimesec(st):
956 return int(st.st_mtime)
957
955 958 # File system features
956 959
957 960 def checkcase(path):
958 961 """
959 962 Return true if the given path is on a case-sensitive filesystem
960 963
961 964 Requires a path (like /foo/.hg) ending with a foldable final
962 965 directory component.
963 966 """
964 967 s1 = os.lstat(path)
965 968 d, b = os.path.split(path)
966 969 b2 = b.upper()
967 970 if b == b2:
968 971 b2 = b.lower()
969 972 if b == b2:
970 973 return True # no evidence against case sensitivity
971 974 p2 = os.path.join(d, b2)
972 975 try:
973 976 s2 = os.lstat(p2)
974 977 if s2 == s1:
975 978 return False
976 979 return True
977 980 except OSError:
978 981 return True
979 982
980 983 try:
981 984 import re2
982 985 _re2 = None
983 986 except ImportError:
984 987 _re2 = False
985 988
986 989 class _re(object):
987 990 def _checkre2(self):
988 991 global _re2
989 992 try:
990 993 # check if match works, see issue3964
991 994 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
992 995 except ImportError:
993 996 _re2 = False
994 997
995 998 def compile(self, pat, flags=0):
996 999 '''Compile a regular expression, using re2 if possible
997 1000
998 1001 For best performance, use only re2-compatible regexp features. The
999 1002 only flags from the re module that are re2-compatible are
1000 1003 IGNORECASE and MULTILINE.'''
1001 1004 if _re2 is None:
1002 1005 self._checkre2()
1003 1006 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1004 1007 if flags & remod.IGNORECASE:
1005 1008 pat = '(?i)' + pat
1006 1009 if flags & remod.MULTILINE:
1007 1010 pat = '(?m)' + pat
1008 1011 try:
1009 1012 return re2.compile(pat)
1010 1013 except re2.error:
1011 1014 pass
1012 1015 return remod.compile(pat, flags)
1013 1016
1014 1017 @propertycache
1015 1018 def escape(self):
1016 1019 '''Return the version of escape corresponding to self.compile.
1017 1020
1018 1021 This is imperfect because whether re2 or re is used for a particular
1019 1022 function depends on the flags, etc, but it's the best we can do.
1020 1023 '''
1021 1024 global _re2
1022 1025 if _re2 is None:
1023 1026 self._checkre2()
1024 1027 if _re2:
1025 1028 return re2.escape
1026 1029 else:
1027 1030 return remod.escape
1028 1031
1029 1032 re = _re()
1030 1033
1031 1034 _fspathcache = {}
1032 1035 def fspath(name, root):
1033 1036 '''Get name in the case stored in the filesystem
1034 1037
1035 1038 The name should be relative to root, and be normcase-ed for efficiency.
1036 1039
1037 1040 Note that this function is unnecessary, and should not be
1038 1041 called, for case-sensitive filesystems (simply because it's expensive).
1039 1042
1040 1043 The root should be normcase-ed, too.
1041 1044 '''
1042 1045 def _makefspathcacheentry(dir):
1043 1046 return dict((normcase(n), n) for n in os.listdir(dir))
1044 1047
1045 1048 seps = os.sep
1046 1049 if os.altsep:
1047 1050 seps = seps + os.altsep
1048 1051 # Protect backslashes. This gets silly very quickly.
1049 1052 seps.replace('\\','\\\\')
1050 1053 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1051 1054 dir = os.path.normpath(root)
1052 1055 result = []
1053 1056 for part, sep in pattern.findall(name):
1054 1057 if sep:
1055 1058 result.append(sep)
1056 1059 continue
1057 1060
1058 1061 if dir not in _fspathcache:
1059 1062 _fspathcache[dir] = _makefspathcacheentry(dir)
1060 1063 contents = _fspathcache[dir]
1061 1064
1062 1065 found = contents.get(part)
1063 1066 if not found:
1064 1067 # retry "once per directory" per "dirstate.walk" which
1065 1068 # may take place for each patches of "hg qpush", for example
1066 1069 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1067 1070 found = contents.get(part)
1068 1071
1069 1072 result.append(found or part)
1070 1073 dir = os.path.join(dir, part)
1071 1074
1072 1075 return ''.join(result)
1073 1076
1074 1077 def checknlink(testfile):
1075 1078 '''check whether hardlink count reporting works properly'''
1076 1079
1077 1080 # testfile may be open, so we need a separate file for checking to
1078 1081 # work around issue2543 (or testfile may get lost on Samba shares)
1079 1082 f1 = testfile + ".hgtmp1"
1080 1083 if os.path.lexists(f1):
1081 1084 return False
1082 1085 try:
1083 1086 posixfile(f1, 'w').close()
1084 1087 except IOError:
1085 1088 return False
1086 1089
1087 1090 f2 = testfile + ".hgtmp2"
1088 1091 fd = None
1089 1092 try:
1090 1093 oslink(f1, f2)
1091 1094 # nlinks() may behave differently for files on Windows shares if
1092 1095 # the file is open.
1093 1096 fd = posixfile(f2)
1094 1097 return nlinks(f2) > 1
1095 1098 except OSError:
1096 1099 return False
1097 1100 finally:
1098 1101 if fd is not None:
1099 1102 fd.close()
1100 1103 for f in (f1, f2):
1101 1104 try:
1102 1105 os.unlink(f)
1103 1106 except OSError:
1104 1107 pass
1105 1108
1106 1109 def endswithsep(path):
1107 1110 '''Check path ends with os.sep or os.altsep.'''
1108 1111 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1109 1112
1110 1113 def splitpath(path):
1111 1114 '''Split path by os.sep.
1112 1115 Note that this function does not use os.altsep because this is
1113 1116 an alternative of simple "xxx.split(os.sep)".
1114 1117 It is recommended to use os.path.normpath() before using this
1115 1118 function if need.'''
1116 1119 return path.split(os.sep)
1117 1120
1118 1121 def gui():
1119 1122 '''Are we running in a GUI?'''
1120 1123 if sys.platform == 'darwin':
1121 1124 if 'SSH_CONNECTION' in os.environ:
1122 1125 # handle SSH access to a box where the user is logged in
1123 1126 return False
1124 1127 elif getattr(osutil, 'isgui', None):
1125 1128 # check if a CoreGraphics session is available
1126 1129 return osutil.isgui()
1127 1130 else:
1128 1131 # pure build; use a safe default
1129 1132 return True
1130 1133 else:
1131 1134 return os.name == "nt" or os.environ.get("DISPLAY")
1132 1135
1133 1136 def mktempcopy(name, emptyok=False, createmode=None):
1134 1137 """Create a temporary file with the same contents from name
1135 1138
1136 1139 The permission bits are copied from the original file.
1137 1140
1138 1141 If the temporary file is going to be truncated immediately, you
1139 1142 can use emptyok=True as an optimization.
1140 1143
1141 1144 Returns the name of the temporary file.
1142 1145 """
1143 1146 d, fn = os.path.split(name)
1144 1147 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1145 1148 os.close(fd)
1146 1149 # Temporary files are created with mode 0600, which is usually not
1147 1150 # what we want. If the original file already exists, just copy
1148 1151 # its mode. Otherwise, manually obey umask.
1149 1152 copymode(name, temp, createmode)
1150 1153 if emptyok:
1151 1154 return temp
1152 1155 try:
1153 1156 try:
1154 1157 ifp = posixfile(name, "rb")
1155 1158 except IOError as inst:
1156 1159 if inst.errno == errno.ENOENT:
1157 1160 return temp
1158 1161 if not getattr(inst, 'filename', None):
1159 1162 inst.filename = name
1160 1163 raise
1161 1164 ofp = posixfile(temp, "wb")
1162 1165 for chunk in filechunkiter(ifp):
1163 1166 ofp.write(chunk)
1164 1167 ifp.close()
1165 1168 ofp.close()
1166 1169 except: # re-raises
1167 1170 try: os.unlink(temp)
1168 1171 except OSError: pass
1169 1172 raise
1170 1173 return temp
1171 1174
1172 1175 class atomictempfile(object):
1173 1176 '''writable file object that atomically updates a file
1174 1177
1175 1178 All writes will go to a temporary copy of the original file. Call
1176 1179 close() when you are done writing, and atomictempfile will rename
1177 1180 the temporary copy to the original name, making the changes
1178 1181 visible. If the object is destroyed without being closed, all your
1179 1182 writes are discarded.
1180 1183 '''
1181 1184 def __init__(self, name, mode='w+b', createmode=None):
1182 1185 self.__name = name # permanent name
1183 1186 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1184 1187 createmode=createmode)
1185 1188 self._fp = posixfile(self._tempname, mode)
1186 1189
1187 1190 # delegated methods
1188 1191 self.write = self._fp.write
1189 1192 self.seek = self._fp.seek
1190 1193 self.tell = self._fp.tell
1191 1194 self.fileno = self._fp.fileno
1192 1195
1193 1196 def close(self):
1194 1197 if not self._fp.closed:
1195 1198 self._fp.close()
1196 1199 rename(self._tempname, localpath(self.__name))
1197 1200
1198 1201 def discard(self):
1199 1202 if not self._fp.closed:
1200 1203 try:
1201 1204 os.unlink(self._tempname)
1202 1205 except OSError:
1203 1206 pass
1204 1207 self._fp.close()
1205 1208
1206 1209 def __del__(self):
1207 1210 if safehasattr(self, '_fp'): # constructor actually did something
1208 1211 self.discard()
1209 1212
1210 1213 def makedirs(name, mode=None, notindexed=False):
1211 1214 """recursive directory creation with parent mode inheritance"""
1212 1215 try:
1213 1216 makedir(name, notindexed)
1214 1217 except OSError as err:
1215 1218 if err.errno == errno.EEXIST:
1216 1219 return
1217 1220 if err.errno != errno.ENOENT or not name:
1218 1221 raise
1219 1222 parent = os.path.dirname(os.path.abspath(name))
1220 1223 if parent == name:
1221 1224 raise
1222 1225 makedirs(parent, mode, notindexed)
1223 1226 makedir(name, notindexed)
1224 1227 if mode is not None:
1225 1228 os.chmod(name, mode)
1226 1229
1227 1230 def ensuredirs(name, mode=None, notindexed=False):
1228 1231 """race-safe recursive directory creation
1229 1232
1230 1233 Newly created directories are marked as "not to be indexed by
1231 1234 the content indexing service", if ``notindexed`` is specified
1232 1235 for "write" mode access.
1233 1236 """
1234 1237 if os.path.isdir(name):
1235 1238 return
1236 1239 parent = os.path.dirname(os.path.abspath(name))
1237 1240 if parent != name:
1238 1241 ensuredirs(parent, mode, notindexed)
1239 1242 try:
1240 1243 makedir(name, notindexed)
1241 1244 except OSError as err:
1242 1245 if err.errno == errno.EEXIST and os.path.isdir(name):
1243 1246 # someone else seems to have won a directory creation race
1244 1247 return
1245 1248 raise
1246 1249 if mode is not None:
1247 1250 os.chmod(name, mode)
1248 1251
1249 1252 def readfile(path):
1250 1253 fp = open(path, 'rb')
1251 1254 try:
1252 1255 return fp.read()
1253 1256 finally:
1254 1257 fp.close()
1255 1258
1256 1259 def writefile(path, text):
1257 1260 fp = open(path, 'wb')
1258 1261 try:
1259 1262 fp.write(text)
1260 1263 finally:
1261 1264 fp.close()
1262 1265
1263 1266 def appendfile(path, text):
1264 1267 fp = open(path, 'ab')
1265 1268 try:
1266 1269 fp.write(text)
1267 1270 finally:
1268 1271 fp.close()
1269 1272
1270 1273 class chunkbuffer(object):
1271 1274 """Allow arbitrary sized chunks of data to be efficiently read from an
1272 1275 iterator over chunks of arbitrary size."""
1273 1276
1274 1277 def __init__(self, in_iter):
1275 1278 """in_iter is the iterator that's iterating over the input chunks.
1276 1279 targetsize is how big a buffer to try to maintain."""
1277 1280 def splitbig(chunks):
1278 1281 for chunk in chunks:
1279 1282 if len(chunk) > 2**20:
1280 1283 pos = 0
1281 1284 while pos < len(chunk):
1282 1285 end = pos + 2 ** 18
1283 1286 yield chunk[pos:end]
1284 1287 pos = end
1285 1288 else:
1286 1289 yield chunk
1287 1290 self.iter = splitbig(in_iter)
1288 1291 self._queue = collections.deque()
1289 1292 self._chunkoffset = 0
1290 1293
1291 1294 def read(self, l=None):
1292 1295 """Read L bytes of data from the iterator of chunks of data.
1293 1296 Returns less than L bytes if the iterator runs dry.
1294 1297
1295 1298 If size parameter is omitted, read everything"""
1296 1299 if l is None:
1297 1300 return ''.join(self.iter)
1298 1301
1299 1302 left = l
1300 1303 buf = []
1301 1304 queue = self._queue
1302 1305 while left > 0:
1303 1306 # refill the queue
1304 1307 if not queue:
1305 1308 target = 2**18
1306 1309 for chunk in self.iter:
1307 1310 queue.append(chunk)
1308 1311 target -= len(chunk)
1309 1312 if target <= 0:
1310 1313 break
1311 1314 if not queue:
1312 1315 break
1313 1316
1314 1317 # The easy way to do this would be to queue.popleft(), modify the
1315 1318 # chunk (if necessary), then queue.appendleft(). However, for cases
1316 1319 # where we read partial chunk content, this incurs 2 dequeue
1317 1320 # mutations and creates a new str for the remaining chunk in the
1318 1321 # queue. Our code below avoids this overhead.
1319 1322
1320 1323 chunk = queue[0]
1321 1324 chunkl = len(chunk)
1322 1325 offset = self._chunkoffset
1323 1326
1324 1327 # Use full chunk.
1325 1328 if offset == 0 and left >= chunkl:
1326 1329 left -= chunkl
1327 1330 queue.popleft()
1328 1331 buf.append(chunk)
1329 1332 # self._chunkoffset remains at 0.
1330 1333 continue
1331 1334
1332 1335 chunkremaining = chunkl - offset
1333 1336
1334 1337 # Use all of unconsumed part of chunk.
1335 1338 if left >= chunkremaining:
1336 1339 left -= chunkremaining
1337 1340 queue.popleft()
1338 1341 # offset == 0 is enabled by block above, so this won't merely
1339 1342 # copy via ``chunk[0:]``.
1340 1343 buf.append(chunk[offset:])
1341 1344 self._chunkoffset = 0
1342 1345
1343 1346 # Partial chunk needed.
1344 1347 else:
1345 1348 buf.append(chunk[offset:offset + left])
1346 1349 self._chunkoffset += left
1347 1350 left -= chunkremaining
1348 1351
1349 1352 return ''.join(buf)
1350 1353
1351 1354 def filechunkiter(f, size=65536, limit=None):
1352 1355 """Create a generator that produces the data in the file size
1353 1356 (default 65536) bytes at a time, up to optional limit (default is
1354 1357 to read all data). Chunks may be less than size bytes if the
1355 1358 chunk is the last chunk in the file, or the file is a socket or
1356 1359 some other type of file that sometimes reads less data than is
1357 1360 requested."""
1358 1361 assert size >= 0
1359 1362 assert limit is None or limit >= 0
1360 1363 while True:
1361 1364 if limit is None:
1362 1365 nbytes = size
1363 1366 else:
1364 1367 nbytes = min(limit, size)
1365 1368 s = nbytes and f.read(nbytes)
1366 1369 if not s:
1367 1370 break
1368 1371 if limit:
1369 1372 limit -= len(s)
1370 1373 yield s
1371 1374
1372 1375 def makedate(timestamp=None):
1373 1376 '''Return a unix timestamp (or the current time) as a (unixtime,
1374 1377 offset) tuple based off the local timezone.'''
1375 1378 if timestamp is None:
1376 1379 timestamp = time.time()
1377 1380 if timestamp < 0:
1378 1381 hint = _("check your clock")
1379 1382 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1380 1383 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1381 1384 datetime.datetime.fromtimestamp(timestamp))
1382 1385 tz = delta.days * 86400 + delta.seconds
1383 1386 return timestamp, tz
1384 1387
1385 1388 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1386 1389 """represent a (unixtime, offset) tuple as a localized time.
1387 1390 unixtime is seconds since the epoch, and offset is the time zone's
1388 1391 number of seconds away from UTC. if timezone is false, do not
1389 1392 append time zone to string."""
1390 1393 t, tz = date or makedate()
1391 1394 if t < 0:
1392 1395 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1393 1396 tz = 0
1394 1397 if "%1" in format or "%2" in format or "%z" in format:
1395 1398 sign = (tz > 0) and "-" or "+"
1396 1399 minutes = abs(tz) // 60
1397 1400 format = format.replace("%z", "%1%2")
1398 1401 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1399 1402 format = format.replace("%2", "%02d" % (minutes % 60))
1400 1403 try:
1401 1404 t = time.gmtime(float(t) - tz)
1402 1405 except ValueError:
1403 1406 # time was out of range
1404 1407 t = time.gmtime(sys.maxint)
1405 1408 s = time.strftime(format, t)
1406 1409 return s
1407 1410
1408 1411 def shortdate(date=None):
1409 1412 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1410 1413 return datestr(date, format='%Y-%m-%d')
1411 1414
1412 1415 def parsetimezone(tz):
1413 1416 """parse a timezone string and return an offset integer"""
1414 1417 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1415 1418 sign = (tz[0] == "+") and 1 or -1
1416 1419 hours = int(tz[1:3])
1417 1420 minutes = int(tz[3:5])
1418 1421 return -sign * (hours * 60 + minutes) * 60
1419 1422 if tz == "GMT" or tz == "UTC":
1420 1423 return 0
1421 1424 return None
1422 1425
1423 1426 def strdate(string, format, defaults=[]):
1424 1427 """parse a localized time string and return a (unixtime, offset) tuple.
1425 1428 if the string cannot be parsed, ValueError is raised."""
1426 1429 # NOTE: unixtime = localunixtime + offset
1427 1430 offset, date = parsetimezone(string.split()[-1]), string
1428 1431 if offset is not None:
1429 1432 date = " ".join(string.split()[:-1])
1430 1433
1431 1434 # add missing elements from defaults
1432 1435 usenow = False # default to using biased defaults
1433 1436 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1434 1437 found = [True for p in part if ("%"+p) in format]
1435 1438 if not found:
1436 1439 date += "@" + defaults[part][usenow]
1437 1440 format += "@%" + part[0]
1438 1441 else:
1439 1442 # We've found a specific time element, less specific time
1440 1443 # elements are relative to today
1441 1444 usenow = True
1442 1445
1443 1446 timetuple = time.strptime(date, format)
1444 1447 localunixtime = int(calendar.timegm(timetuple))
1445 1448 if offset is None:
1446 1449 # local timezone
1447 1450 unixtime = int(time.mktime(timetuple))
1448 1451 offset = unixtime - localunixtime
1449 1452 else:
1450 1453 unixtime = localunixtime + offset
1451 1454 return unixtime, offset
1452 1455
1453 1456 def parsedate(date, formats=None, bias=None):
1454 1457 """parse a localized date/time and return a (unixtime, offset) tuple.
1455 1458
1456 1459 The date may be a "unixtime offset" string or in one of the specified
1457 1460 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1458 1461
1459 1462 >>> parsedate(' today ') == parsedate(\
1460 1463 datetime.date.today().strftime('%b %d'))
1461 1464 True
1462 1465 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1463 1466 datetime.timedelta(days=1)\
1464 1467 ).strftime('%b %d'))
1465 1468 True
1466 1469 >>> now, tz = makedate()
1467 1470 >>> strnow, strtz = parsedate('now')
1468 1471 >>> (strnow - now) < 1
1469 1472 True
1470 1473 >>> tz == strtz
1471 1474 True
1472 1475 """
1473 1476 if bias is None:
1474 1477 bias = {}
1475 1478 if not date:
1476 1479 return 0, 0
1477 1480 if isinstance(date, tuple) and len(date) == 2:
1478 1481 return date
1479 1482 if not formats:
1480 1483 formats = defaultdateformats
1481 1484 date = date.strip()
1482 1485
1483 1486 if date == 'now' or date == _('now'):
1484 1487 return makedate()
1485 1488 if date == 'today' or date == _('today'):
1486 1489 date = datetime.date.today().strftime('%b %d')
1487 1490 elif date == 'yesterday' or date == _('yesterday'):
1488 1491 date = (datetime.date.today() -
1489 1492 datetime.timedelta(days=1)).strftime('%b %d')
1490 1493
1491 1494 try:
1492 1495 when, offset = map(int, date.split(' '))
1493 1496 except ValueError:
1494 1497 # fill out defaults
1495 1498 now = makedate()
1496 1499 defaults = {}
1497 1500 for part in ("d", "mb", "yY", "HI", "M", "S"):
1498 1501 # this piece is for rounding the specific end of unknowns
1499 1502 b = bias.get(part)
1500 1503 if b is None:
1501 1504 if part[0] in "HMS":
1502 1505 b = "00"
1503 1506 else:
1504 1507 b = "0"
1505 1508
1506 1509 # this piece is for matching the generic end to today's date
1507 1510 n = datestr(now, "%" + part[0])
1508 1511
1509 1512 defaults[part] = (b, n)
1510 1513
1511 1514 for format in formats:
1512 1515 try:
1513 1516 when, offset = strdate(date, format, defaults)
1514 1517 except (ValueError, OverflowError):
1515 1518 pass
1516 1519 else:
1517 1520 break
1518 1521 else:
1519 1522 raise Abort(_('invalid date: %r') % date)
1520 1523 # validate explicit (probably user-specified) date and
1521 1524 # time zone offset. values must fit in signed 32 bits for
1522 1525 # current 32-bit linux runtimes. timezones go from UTC-12
1523 1526 # to UTC+14
1524 1527 if abs(when) > 0x7fffffff:
1525 1528 raise Abort(_('date exceeds 32 bits: %d') % when)
1526 1529 if when < 0:
1527 1530 raise Abort(_('negative date value: %d') % when)
1528 1531 if offset < -50400 or offset > 43200:
1529 1532 raise Abort(_('impossible time zone offset: %d') % offset)
1530 1533 return when, offset
1531 1534
1532 1535 def matchdate(date):
1533 1536 """Return a function that matches a given date match specifier
1534 1537
1535 1538 Formats include:
1536 1539
1537 1540 '{date}' match a given date to the accuracy provided
1538 1541
1539 1542 '<{date}' on or before a given date
1540 1543
1541 1544 '>{date}' on or after a given date
1542 1545
1543 1546 >>> p1 = parsedate("10:29:59")
1544 1547 >>> p2 = parsedate("10:30:00")
1545 1548 >>> p3 = parsedate("10:30:59")
1546 1549 >>> p4 = parsedate("10:31:00")
1547 1550 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1548 1551 >>> f = matchdate("10:30")
1549 1552 >>> f(p1[0])
1550 1553 False
1551 1554 >>> f(p2[0])
1552 1555 True
1553 1556 >>> f(p3[0])
1554 1557 True
1555 1558 >>> f(p4[0])
1556 1559 False
1557 1560 >>> f(p5[0])
1558 1561 False
1559 1562 """
1560 1563
1561 1564 def lower(date):
1562 1565 d = {'mb': "1", 'd': "1"}
1563 1566 return parsedate(date, extendeddateformats, d)[0]
1564 1567
1565 1568 def upper(date):
1566 1569 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1567 1570 for days in ("31", "30", "29"):
1568 1571 try:
1569 1572 d["d"] = days
1570 1573 return parsedate(date, extendeddateformats, d)[0]
1571 1574 except Abort:
1572 1575 pass
1573 1576 d["d"] = "28"
1574 1577 return parsedate(date, extendeddateformats, d)[0]
1575 1578
1576 1579 date = date.strip()
1577 1580
1578 1581 if not date:
1579 1582 raise Abort(_("dates cannot consist entirely of whitespace"))
1580 1583 elif date[0] == "<":
1581 1584 if not date[1:]:
1582 1585 raise Abort(_("invalid day spec, use '<DATE'"))
1583 1586 when = upper(date[1:])
1584 1587 return lambda x: x <= when
1585 1588 elif date[0] == ">":
1586 1589 if not date[1:]:
1587 1590 raise Abort(_("invalid day spec, use '>DATE'"))
1588 1591 when = lower(date[1:])
1589 1592 return lambda x: x >= when
1590 1593 elif date[0] == "-":
1591 1594 try:
1592 1595 days = int(date[1:])
1593 1596 except ValueError:
1594 1597 raise Abort(_("invalid day spec: %s") % date[1:])
1595 1598 if days < 0:
1596 1599 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1597 1600 % date[1:])
1598 1601 when = makedate()[0] - days * 3600 * 24
1599 1602 return lambda x: x >= when
1600 1603 elif " to " in date:
1601 1604 a, b = date.split(" to ")
1602 1605 start, stop = lower(a), upper(b)
1603 1606 return lambda x: x >= start and x <= stop
1604 1607 else:
1605 1608 start, stop = lower(date), upper(date)
1606 1609 return lambda x: x >= start and x <= stop
1607 1610
1608 1611 def stringmatcher(pattern):
1609 1612 """
1610 1613 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1611 1614 returns the matcher name, pattern, and matcher function.
1612 1615 missing or unknown prefixes are treated as literal matches.
1613 1616
1614 1617 helper for tests:
1615 1618 >>> def test(pattern, *tests):
1616 1619 ... kind, pattern, matcher = stringmatcher(pattern)
1617 1620 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1618 1621
1619 1622 exact matching (no prefix):
1620 1623 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1621 1624 ('literal', 'abcdefg', [False, False, True])
1622 1625
1623 1626 regex matching ('re:' prefix)
1624 1627 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1625 1628 ('re', 'a.+b', [False, False, True])
1626 1629
1627 1630 force exact matches ('literal:' prefix)
1628 1631 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1629 1632 ('literal', 're:foobar', [False, True])
1630 1633
1631 1634 unknown prefixes are ignored and treated as literals
1632 1635 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1633 1636 ('literal', 'foo:bar', [False, False, True])
1634 1637 """
1635 1638 if pattern.startswith('re:'):
1636 1639 pattern = pattern[3:]
1637 1640 try:
1638 1641 regex = remod.compile(pattern)
1639 1642 except remod.error as e:
1640 1643 raise error.ParseError(_('invalid regular expression: %s')
1641 1644 % e)
1642 1645 return 're', pattern, regex.search
1643 1646 elif pattern.startswith('literal:'):
1644 1647 pattern = pattern[8:]
1645 1648 return 'literal', pattern, pattern.__eq__
1646 1649
1647 1650 def shortuser(user):
1648 1651 """Return a short representation of a user name or email address."""
1649 1652 f = user.find('@')
1650 1653 if f >= 0:
1651 1654 user = user[:f]
1652 1655 f = user.find('<')
1653 1656 if f >= 0:
1654 1657 user = user[f + 1:]
1655 1658 f = user.find(' ')
1656 1659 if f >= 0:
1657 1660 user = user[:f]
1658 1661 f = user.find('.')
1659 1662 if f >= 0:
1660 1663 user = user[:f]
1661 1664 return user
1662 1665
1663 1666 def emailuser(user):
1664 1667 """Return the user portion of an email address."""
1665 1668 f = user.find('@')
1666 1669 if f >= 0:
1667 1670 user = user[:f]
1668 1671 f = user.find('<')
1669 1672 if f >= 0:
1670 1673 user = user[f + 1:]
1671 1674 return user
1672 1675
1673 1676 def email(author):
1674 1677 '''get email of author.'''
1675 1678 r = author.find('>')
1676 1679 if r == -1:
1677 1680 r = None
1678 1681 return author[author.find('<') + 1:r]
1679 1682
1680 1683 def ellipsis(text, maxlength=400):
1681 1684 """Trim string to at most maxlength (default: 400) columns in display."""
1682 1685 return encoding.trim(text, maxlength, ellipsis='...')
1683 1686
1684 1687 def unitcountfn(*unittable):
1685 1688 '''return a function that renders a readable count of some quantity'''
1686 1689
1687 1690 def go(count):
1688 1691 for multiplier, divisor, format in unittable:
1689 1692 if count >= divisor * multiplier:
1690 1693 return format % (count / float(divisor))
1691 1694 return unittable[-1][2] % count
1692 1695
1693 1696 return go
1694 1697
1695 1698 bytecount = unitcountfn(
1696 1699 (100, 1 << 30, _('%.0f GB')),
1697 1700 (10, 1 << 30, _('%.1f GB')),
1698 1701 (1, 1 << 30, _('%.2f GB')),
1699 1702 (100, 1 << 20, _('%.0f MB')),
1700 1703 (10, 1 << 20, _('%.1f MB')),
1701 1704 (1, 1 << 20, _('%.2f MB')),
1702 1705 (100, 1 << 10, _('%.0f KB')),
1703 1706 (10, 1 << 10, _('%.1f KB')),
1704 1707 (1, 1 << 10, _('%.2f KB')),
1705 1708 (1, 1, _('%.0f bytes')),
1706 1709 )
1707 1710
1708 1711 def uirepr(s):
1709 1712 # Avoid double backslash in Windows path repr()
1710 1713 return repr(s).replace('\\\\', '\\')
1711 1714
1712 1715 # delay import of textwrap
1713 1716 def MBTextWrapper(**kwargs):
1714 1717 class tw(textwrap.TextWrapper):
1715 1718 """
1716 1719 Extend TextWrapper for width-awareness.
1717 1720
1718 1721 Neither number of 'bytes' in any encoding nor 'characters' is
1719 1722 appropriate to calculate terminal columns for specified string.
1720 1723
1721 1724 Original TextWrapper implementation uses built-in 'len()' directly,
1722 1725 so overriding is needed to use width information of each characters.
1723 1726
1724 1727 In addition, characters classified into 'ambiguous' width are
1725 1728 treated as wide in East Asian area, but as narrow in other.
1726 1729
1727 1730 This requires use decision to determine width of such characters.
1728 1731 """
1729 1732 def _cutdown(self, ucstr, space_left):
1730 1733 l = 0
1731 1734 colwidth = encoding.ucolwidth
1732 1735 for i in xrange(len(ucstr)):
1733 1736 l += colwidth(ucstr[i])
1734 1737 if space_left < l:
1735 1738 return (ucstr[:i], ucstr[i:])
1736 1739 return ucstr, ''
1737 1740
1738 1741 # overriding of base class
1739 1742 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1740 1743 space_left = max(width - cur_len, 1)
1741 1744
1742 1745 if self.break_long_words:
1743 1746 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1744 1747 cur_line.append(cut)
1745 1748 reversed_chunks[-1] = res
1746 1749 elif not cur_line:
1747 1750 cur_line.append(reversed_chunks.pop())
1748 1751
1749 1752 # this overriding code is imported from TextWrapper of Python 2.6
1750 1753 # to calculate columns of string by 'encoding.ucolwidth()'
1751 1754 def _wrap_chunks(self, chunks):
1752 1755 colwidth = encoding.ucolwidth
1753 1756
1754 1757 lines = []
1755 1758 if self.width <= 0:
1756 1759 raise ValueError("invalid width %r (must be > 0)" % self.width)
1757 1760
1758 1761 # Arrange in reverse order so items can be efficiently popped
1759 1762 # from a stack of chucks.
1760 1763 chunks.reverse()
1761 1764
1762 1765 while chunks:
1763 1766
1764 1767 # Start the list of chunks that will make up the current line.
1765 1768 # cur_len is just the length of all the chunks in cur_line.
1766 1769 cur_line = []
1767 1770 cur_len = 0
1768 1771
1769 1772 # Figure out which static string will prefix this line.
1770 1773 if lines:
1771 1774 indent = self.subsequent_indent
1772 1775 else:
1773 1776 indent = self.initial_indent
1774 1777
1775 1778 # Maximum width for this line.
1776 1779 width = self.width - len(indent)
1777 1780
1778 1781 # First chunk on line is whitespace -- drop it, unless this
1779 1782 # is the very beginning of the text (i.e. no lines started yet).
1780 1783 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1781 1784 del chunks[-1]
1782 1785
1783 1786 while chunks:
1784 1787 l = colwidth(chunks[-1])
1785 1788
1786 1789 # Can at least squeeze this chunk onto the current line.
1787 1790 if cur_len + l <= width:
1788 1791 cur_line.append(chunks.pop())
1789 1792 cur_len += l
1790 1793
1791 1794 # Nope, this line is full.
1792 1795 else:
1793 1796 break
1794 1797
1795 1798 # The current line is full, and the next chunk is too big to
1796 1799 # fit on *any* line (not just this one).
1797 1800 if chunks and colwidth(chunks[-1]) > width:
1798 1801 self._handle_long_word(chunks, cur_line, cur_len, width)
1799 1802
1800 1803 # If the last chunk on this line is all whitespace, drop it.
1801 1804 if (self.drop_whitespace and
1802 1805 cur_line and cur_line[-1].strip() == ''):
1803 1806 del cur_line[-1]
1804 1807
1805 1808 # Convert current line back to a string and store it in list
1806 1809 # of all lines (return value).
1807 1810 if cur_line:
1808 1811 lines.append(indent + ''.join(cur_line))
1809 1812
1810 1813 return lines
1811 1814
1812 1815 global MBTextWrapper
1813 1816 MBTextWrapper = tw
1814 1817 return tw(**kwargs)
1815 1818
1816 1819 def wrap(line, width, initindent='', hangindent=''):
1817 1820 maxindent = max(len(hangindent), len(initindent))
1818 1821 if width <= maxindent:
1819 1822 # adjust for weird terminal size
1820 1823 width = max(78, maxindent + 1)
1821 1824 line = line.decode(encoding.encoding, encoding.encodingmode)
1822 1825 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1823 1826 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1824 1827 wrapper = MBTextWrapper(width=width,
1825 1828 initial_indent=initindent,
1826 1829 subsequent_indent=hangindent)
1827 1830 return wrapper.fill(line).encode(encoding.encoding)
1828 1831
1829 1832 def iterlines(iterator):
1830 1833 for chunk in iterator:
1831 1834 for line in chunk.splitlines():
1832 1835 yield line
1833 1836
1834 1837 def expandpath(path):
1835 1838 return os.path.expanduser(os.path.expandvars(path))
1836 1839
1837 1840 def hgcmd():
1838 1841 """Return the command used to execute current hg
1839 1842
1840 1843 This is different from hgexecutable() because on Windows we want
1841 1844 to avoid things opening new shell windows like batch files, so we
1842 1845 get either the python call or current executable.
1843 1846 """
1844 1847 if mainfrozen():
1845 1848 return [sys.executable]
1846 1849 return gethgcmd()
1847 1850
1848 1851 def rundetached(args, condfn):
1849 1852 """Execute the argument list in a detached process.
1850 1853
1851 1854 condfn is a callable which is called repeatedly and should return
1852 1855 True once the child process is known to have started successfully.
1853 1856 At this point, the child process PID is returned. If the child
1854 1857 process fails to start or finishes before condfn() evaluates to
1855 1858 True, return -1.
1856 1859 """
1857 1860 # Windows case is easier because the child process is either
1858 1861 # successfully starting and validating the condition or exiting
1859 1862 # on failure. We just poll on its PID. On Unix, if the child
1860 1863 # process fails to start, it will be left in a zombie state until
1861 1864 # the parent wait on it, which we cannot do since we expect a long
1862 1865 # running process on success. Instead we listen for SIGCHLD telling
1863 1866 # us our child process terminated.
1864 1867 terminated = set()
1865 1868 def handler(signum, frame):
1866 1869 terminated.add(os.wait())
1867 1870 prevhandler = None
1868 1871 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1869 1872 if SIGCHLD is not None:
1870 1873 prevhandler = signal.signal(SIGCHLD, handler)
1871 1874 try:
1872 1875 pid = spawndetached(args)
1873 1876 while not condfn():
1874 1877 if ((pid in terminated or not testpid(pid))
1875 1878 and not condfn()):
1876 1879 return -1
1877 1880 time.sleep(0.1)
1878 1881 return pid
1879 1882 finally:
1880 1883 if prevhandler is not None:
1881 1884 signal.signal(signal.SIGCHLD, prevhandler)
1882 1885
1883 1886 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1884 1887 """Return the result of interpolating items in the mapping into string s.
1885 1888
1886 1889 prefix is a single character string, or a two character string with
1887 1890 a backslash as the first character if the prefix needs to be escaped in
1888 1891 a regular expression.
1889 1892
1890 1893 fn is an optional function that will be applied to the replacement text
1891 1894 just before replacement.
1892 1895
1893 1896 escape_prefix is an optional flag that allows using doubled prefix for
1894 1897 its escaping.
1895 1898 """
1896 1899 fn = fn or (lambda s: s)
1897 1900 patterns = '|'.join(mapping.keys())
1898 1901 if escape_prefix:
1899 1902 patterns += '|' + prefix
1900 1903 if len(prefix) > 1:
1901 1904 prefix_char = prefix[1:]
1902 1905 else:
1903 1906 prefix_char = prefix
1904 1907 mapping[prefix_char] = prefix_char
1905 1908 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1906 1909 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1907 1910
1908 1911 def getport(port):
1909 1912 """Return the port for a given network service.
1910 1913
1911 1914 If port is an integer, it's returned as is. If it's a string, it's
1912 1915 looked up using socket.getservbyname(). If there's no matching
1913 1916 service, util.Abort is raised.
1914 1917 """
1915 1918 try:
1916 1919 return int(port)
1917 1920 except ValueError:
1918 1921 pass
1919 1922
1920 1923 try:
1921 1924 return socket.getservbyname(port)
1922 1925 except socket.error:
1923 1926 raise Abort(_("no port number associated with service '%s'") % port)
1924 1927
1925 1928 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1926 1929 '0': False, 'no': False, 'false': False, 'off': False,
1927 1930 'never': False}
1928 1931
1929 1932 def parsebool(s):
1930 1933 """Parse s into a boolean.
1931 1934
1932 1935 If s is not a valid boolean, returns None.
1933 1936 """
1934 1937 return _booleans.get(s.lower(), None)
1935 1938
1936 1939 _hexdig = '0123456789ABCDEFabcdef'
1937 1940 _hextochr = dict((a + b, chr(int(a + b, 16)))
1938 1941 for a in _hexdig for b in _hexdig)
1939 1942
1940 1943 def _urlunquote(s):
1941 1944 """Decode HTTP/HTML % encoding.
1942 1945
1943 1946 >>> _urlunquote('abc%20def')
1944 1947 'abc def'
1945 1948 """
1946 1949 res = s.split('%')
1947 1950 # fastpath
1948 1951 if len(res) == 1:
1949 1952 return s
1950 1953 s = res[0]
1951 1954 for item in res[1:]:
1952 1955 try:
1953 1956 s += _hextochr[item[:2]] + item[2:]
1954 1957 except KeyError:
1955 1958 s += '%' + item
1956 1959 except UnicodeDecodeError:
1957 1960 s += unichr(int(item[:2], 16)) + item[2:]
1958 1961 return s
1959 1962
1960 1963 class url(object):
1961 1964 r"""Reliable URL parser.
1962 1965
1963 1966 This parses URLs and provides attributes for the following
1964 1967 components:
1965 1968
1966 1969 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1967 1970
1968 1971 Missing components are set to None. The only exception is
1969 1972 fragment, which is set to '' if present but empty.
1970 1973
1971 1974 If parsefragment is False, fragment is included in query. If
1972 1975 parsequery is False, query is included in path. If both are
1973 1976 False, both fragment and query are included in path.
1974 1977
1975 1978 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1976 1979
1977 1980 Note that for backward compatibility reasons, bundle URLs do not
1978 1981 take host names. That means 'bundle://../' has a path of '../'.
1979 1982
1980 1983 Examples:
1981 1984
1982 1985 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1983 1986 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1984 1987 >>> url('ssh://[::1]:2200//home/joe/repo')
1985 1988 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1986 1989 >>> url('file:///home/joe/repo')
1987 1990 <url scheme: 'file', path: '/home/joe/repo'>
1988 1991 >>> url('file:///c:/temp/foo/')
1989 1992 <url scheme: 'file', path: 'c:/temp/foo/'>
1990 1993 >>> url('bundle:foo')
1991 1994 <url scheme: 'bundle', path: 'foo'>
1992 1995 >>> url('bundle://../foo')
1993 1996 <url scheme: 'bundle', path: '../foo'>
1994 1997 >>> url(r'c:\foo\bar')
1995 1998 <url path: 'c:\\foo\\bar'>
1996 1999 >>> url(r'\\blah\blah\blah')
1997 2000 <url path: '\\\\blah\\blah\\blah'>
1998 2001 >>> url(r'\\blah\blah\blah#baz')
1999 2002 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2000 2003 >>> url(r'file:///C:\users\me')
2001 2004 <url scheme: 'file', path: 'C:\\users\\me'>
2002 2005
2003 2006 Authentication credentials:
2004 2007
2005 2008 >>> url('ssh://joe:xyz@x/repo')
2006 2009 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2007 2010 >>> url('ssh://joe@x/repo')
2008 2011 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2009 2012
2010 2013 Query strings and fragments:
2011 2014
2012 2015 >>> url('http://host/a?b#c')
2013 2016 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2014 2017 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2015 2018 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2016 2019 """
2017 2020
2018 2021 _safechars = "!~*'()+"
2019 2022 _safepchars = "/!~*'()+:\\"
2020 2023 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2021 2024
2022 2025 def __init__(self, path, parsequery=True, parsefragment=True):
2023 2026 # We slowly chomp away at path until we have only the path left
2024 2027 self.scheme = self.user = self.passwd = self.host = None
2025 2028 self.port = self.path = self.query = self.fragment = None
2026 2029 self._localpath = True
2027 2030 self._hostport = ''
2028 2031 self._origpath = path
2029 2032
2030 2033 if parsefragment and '#' in path:
2031 2034 path, self.fragment = path.split('#', 1)
2032 2035 if not path:
2033 2036 path = None
2034 2037
2035 2038 # special case for Windows drive letters and UNC paths
2036 2039 if hasdriveletter(path) or path.startswith(r'\\'):
2037 2040 self.path = path
2038 2041 return
2039 2042
2040 2043 # For compatibility reasons, we can't handle bundle paths as
2041 2044 # normal URLS
2042 2045 if path.startswith('bundle:'):
2043 2046 self.scheme = 'bundle'
2044 2047 path = path[7:]
2045 2048 if path.startswith('//'):
2046 2049 path = path[2:]
2047 2050 self.path = path
2048 2051 return
2049 2052
2050 2053 if self._matchscheme(path):
2051 2054 parts = path.split(':', 1)
2052 2055 if parts[0]:
2053 2056 self.scheme, path = parts
2054 2057 self._localpath = False
2055 2058
2056 2059 if not path:
2057 2060 path = None
2058 2061 if self._localpath:
2059 2062 self.path = ''
2060 2063 return
2061 2064 else:
2062 2065 if self._localpath:
2063 2066 self.path = path
2064 2067 return
2065 2068
2066 2069 if parsequery and '?' in path:
2067 2070 path, self.query = path.split('?', 1)
2068 2071 if not path:
2069 2072 path = None
2070 2073 if not self.query:
2071 2074 self.query = None
2072 2075
2073 2076 # // is required to specify a host/authority
2074 2077 if path and path.startswith('//'):
2075 2078 parts = path[2:].split('/', 1)
2076 2079 if len(parts) > 1:
2077 2080 self.host, path = parts
2078 2081 else:
2079 2082 self.host = parts[0]
2080 2083 path = None
2081 2084 if not self.host:
2082 2085 self.host = None
2083 2086 # path of file:///d is /d
2084 2087 # path of file:///d:/ is d:/, not /d:/
2085 2088 if path and not hasdriveletter(path):
2086 2089 path = '/' + path
2087 2090
2088 2091 if self.host and '@' in self.host:
2089 2092 self.user, self.host = self.host.rsplit('@', 1)
2090 2093 if ':' in self.user:
2091 2094 self.user, self.passwd = self.user.split(':', 1)
2092 2095 if not self.host:
2093 2096 self.host = None
2094 2097
2095 2098 # Don't split on colons in IPv6 addresses without ports
2096 2099 if (self.host and ':' in self.host and
2097 2100 not (self.host.startswith('[') and self.host.endswith(']'))):
2098 2101 self._hostport = self.host
2099 2102 self.host, self.port = self.host.rsplit(':', 1)
2100 2103 if not self.host:
2101 2104 self.host = None
2102 2105
2103 2106 if (self.host and self.scheme == 'file' and
2104 2107 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2105 2108 raise Abort(_('file:// URLs can only refer to localhost'))
2106 2109
2107 2110 self.path = path
2108 2111
2109 2112 # leave the query string escaped
2110 2113 for a in ('user', 'passwd', 'host', 'port',
2111 2114 'path', 'fragment'):
2112 2115 v = getattr(self, a)
2113 2116 if v is not None:
2114 2117 setattr(self, a, _urlunquote(v))
2115 2118
2116 2119 def __repr__(self):
2117 2120 attrs = []
2118 2121 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2119 2122 'query', 'fragment'):
2120 2123 v = getattr(self, a)
2121 2124 if v is not None:
2122 2125 attrs.append('%s: %r' % (a, v))
2123 2126 return '<url %s>' % ', '.join(attrs)
2124 2127
2125 2128 def __str__(self):
2126 2129 r"""Join the URL's components back into a URL string.
2127 2130
2128 2131 Examples:
2129 2132
2130 2133 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2131 2134 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2132 2135 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2133 2136 'http://user:pw@host:80/?foo=bar&baz=42'
2134 2137 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2135 2138 'http://user:pw@host:80/?foo=bar%3dbaz'
2136 2139 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2137 2140 'ssh://user:pw@[::1]:2200//home/joe#'
2138 2141 >>> str(url('http://localhost:80//'))
2139 2142 'http://localhost:80//'
2140 2143 >>> str(url('http://localhost:80/'))
2141 2144 'http://localhost:80/'
2142 2145 >>> str(url('http://localhost:80'))
2143 2146 'http://localhost:80/'
2144 2147 >>> str(url('bundle:foo'))
2145 2148 'bundle:foo'
2146 2149 >>> str(url('bundle://../foo'))
2147 2150 'bundle:../foo'
2148 2151 >>> str(url('path'))
2149 2152 'path'
2150 2153 >>> str(url('file:///tmp/foo/bar'))
2151 2154 'file:///tmp/foo/bar'
2152 2155 >>> str(url('file:///c:/tmp/foo/bar'))
2153 2156 'file:///c:/tmp/foo/bar'
2154 2157 >>> print url(r'bundle:foo\bar')
2155 2158 bundle:foo\bar
2156 2159 >>> print url(r'file:///D:\data\hg')
2157 2160 file:///D:\data\hg
2158 2161 """
2159 2162 if self._localpath:
2160 2163 s = self.path
2161 2164 if self.scheme == 'bundle':
2162 2165 s = 'bundle:' + s
2163 2166 if self.fragment:
2164 2167 s += '#' + self.fragment
2165 2168 return s
2166 2169
2167 2170 s = self.scheme + ':'
2168 2171 if self.user or self.passwd or self.host:
2169 2172 s += '//'
2170 2173 elif self.scheme and (not self.path or self.path.startswith('/')
2171 2174 or hasdriveletter(self.path)):
2172 2175 s += '//'
2173 2176 if hasdriveletter(self.path):
2174 2177 s += '/'
2175 2178 if self.user:
2176 2179 s += urllib.quote(self.user, safe=self._safechars)
2177 2180 if self.passwd:
2178 2181 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2179 2182 if self.user or self.passwd:
2180 2183 s += '@'
2181 2184 if self.host:
2182 2185 if not (self.host.startswith('[') and self.host.endswith(']')):
2183 2186 s += urllib.quote(self.host)
2184 2187 else:
2185 2188 s += self.host
2186 2189 if self.port:
2187 2190 s += ':' + urllib.quote(self.port)
2188 2191 if self.host:
2189 2192 s += '/'
2190 2193 if self.path:
2191 2194 # TODO: similar to the query string, we should not unescape the
2192 2195 # path when we store it, the path might contain '%2f' = '/',
2193 2196 # which we should *not* escape.
2194 2197 s += urllib.quote(self.path, safe=self._safepchars)
2195 2198 if self.query:
2196 2199 # we store the query in escaped form.
2197 2200 s += '?' + self.query
2198 2201 if self.fragment is not None:
2199 2202 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2200 2203 return s
2201 2204
2202 2205 def authinfo(self):
2203 2206 user, passwd = self.user, self.passwd
2204 2207 try:
2205 2208 self.user, self.passwd = None, None
2206 2209 s = str(self)
2207 2210 finally:
2208 2211 self.user, self.passwd = user, passwd
2209 2212 if not self.user:
2210 2213 return (s, None)
2211 2214 # authinfo[1] is passed to urllib2 password manager, and its
2212 2215 # URIs must not contain credentials. The host is passed in the
2213 2216 # URIs list because Python < 2.4.3 uses only that to search for
2214 2217 # a password.
2215 2218 return (s, (None, (s, self.host),
2216 2219 self.user, self.passwd or ''))
2217 2220
2218 2221 def isabs(self):
2219 2222 if self.scheme and self.scheme != 'file':
2220 2223 return True # remote URL
2221 2224 if hasdriveletter(self.path):
2222 2225 return True # absolute for our purposes - can't be joined()
2223 2226 if self.path.startswith(r'\\'):
2224 2227 return True # Windows UNC path
2225 2228 if self.path.startswith('/'):
2226 2229 return True # POSIX-style
2227 2230 return False
2228 2231
2229 2232 def localpath(self):
2230 2233 if self.scheme == 'file' or self.scheme == 'bundle':
2231 2234 path = self.path or '/'
2232 2235 # For Windows, we need to promote hosts containing drive
2233 2236 # letters to paths with drive letters.
2234 2237 if hasdriveletter(self._hostport):
2235 2238 path = self._hostport + '/' + self.path
2236 2239 elif (self.host is not None and self.path
2237 2240 and not hasdriveletter(path)):
2238 2241 path = '/' + path
2239 2242 return path
2240 2243 return self._origpath
2241 2244
2242 2245 def islocal(self):
2243 2246 '''whether localpath will return something that posixfile can open'''
2244 2247 return (not self.scheme or self.scheme == 'file'
2245 2248 or self.scheme == 'bundle')
2246 2249
2247 2250 def hasscheme(path):
2248 2251 return bool(url(path).scheme)
2249 2252
2250 2253 def hasdriveletter(path):
2251 2254 return path and path[1:2] == ':' and path[0:1].isalpha()
2252 2255
2253 2256 def urllocalpath(path):
2254 2257 return url(path, parsequery=False, parsefragment=False).localpath()
2255 2258
2256 2259 def hidepassword(u):
2257 2260 '''hide user credential in a url string'''
2258 2261 u = url(u)
2259 2262 if u.passwd:
2260 2263 u.passwd = '***'
2261 2264 return str(u)
2262 2265
2263 2266 def removeauth(u):
2264 2267 '''remove all authentication information from a url string'''
2265 2268 u = url(u)
2266 2269 u.user = u.passwd = None
2267 2270 return str(u)
2268 2271
2269 2272 def isatty(fd):
2270 2273 try:
2271 2274 return fd.isatty()
2272 2275 except AttributeError:
2273 2276 return False
2274 2277
2275 2278 timecount = unitcountfn(
2276 2279 (1, 1e3, _('%.0f s')),
2277 2280 (100, 1, _('%.1f s')),
2278 2281 (10, 1, _('%.2f s')),
2279 2282 (1, 1, _('%.3f s')),
2280 2283 (100, 0.001, _('%.1f ms')),
2281 2284 (10, 0.001, _('%.2f ms')),
2282 2285 (1, 0.001, _('%.3f ms')),
2283 2286 (100, 0.000001, _('%.1f us')),
2284 2287 (10, 0.000001, _('%.2f us')),
2285 2288 (1, 0.000001, _('%.3f us')),
2286 2289 (100, 0.000000001, _('%.1f ns')),
2287 2290 (10, 0.000000001, _('%.2f ns')),
2288 2291 (1, 0.000000001, _('%.3f ns')),
2289 2292 )
2290 2293
2291 2294 _timenesting = [0]
2292 2295
2293 2296 def timed(func):
2294 2297 '''Report the execution time of a function call to stderr.
2295 2298
2296 2299 During development, use as a decorator when you need to measure
2297 2300 the cost of a function, e.g. as follows:
2298 2301
2299 2302 @util.timed
2300 2303 def foo(a, b, c):
2301 2304 pass
2302 2305 '''
2303 2306
2304 2307 def wrapper(*args, **kwargs):
2305 2308 start = time.time()
2306 2309 indent = 2
2307 2310 _timenesting[0] += indent
2308 2311 try:
2309 2312 return func(*args, **kwargs)
2310 2313 finally:
2311 2314 elapsed = time.time() - start
2312 2315 _timenesting[0] -= indent
2313 2316 sys.stderr.write('%s%s: %s\n' %
2314 2317 (' ' * _timenesting[0], func.__name__,
2315 2318 timecount(elapsed)))
2316 2319 return wrapper
2317 2320
2318 2321 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2319 2322 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2320 2323
2321 2324 def sizetoint(s):
2322 2325 '''Convert a space specifier to a byte count.
2323 2326
2324 2327 >>> sizetoint('30')
2325 2328 30
2326 2329 >>> sizetoint('2.2kb')
2327 2330 2252
2328 2331 >>> sizetoint('6M')
2329 2332 6291456
2330 2333 '''
2331 2334 t = s.strip().lower()
2332 2335 try:
2333 2336 for k, u in _sizeunits:
2334 2337 if t.endswith(k):
2335 2338 return int(float(t[:-len(k)]) * u)
2336 2339 return int(t)
2337 2340 except ValueError:
2338 2341 raise error.ParseError(_("couldn't parse size: %s") % s)
2339 2342
2340 2343 class hooks(object):
2341 2344 '''A collection of hook functions that can be used to extend a
2342 2345 function's behavior. Hooks are called in lexicographic order,
2343 2346 based on the names of their sources.'''
2344 2347
2345 2348 def __init__(self):
2346 2349 self._hooks = []
2347 2350
2348 2351 def add(self, source, hook):
2349 2352 self._hooks.append((source, hook))
2350 2353
2351 2354 def __call__(self, *args):
2352 2355 self._hooks.sort(key=lambda x: x[0])
2353 2356 results = []
2354 2357 for source, hook in self._hooks:
2355 2358 results.append(hook(*args))
2356 2359 return results
2357 2360
2358 2361 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2359 2362 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2360 2363 Skips the 'skip' last entries. By default it will flush stdout first.
2361 2364 It can be used everywhere and do intentionally not require an ui object.
2362 2365 Not be used in production code but very convenient while developing.
2363 2366 '''
2364 2367 if otherf:
2365 2368 otherf.flush()
2366 2369 f.write('%s at:\n' % msg)
2367 2370 entries = [('%s:%s' % (fn, ln), func)
2368 2371 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2369 2372 if entries:
2370 2373 fnmax = max(len(entry[0]) for entry in entries)
2371 2374 for fnln, func in entries:
2372 2375 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2373 2376 f.flush()
2374 2377
2375 2378 class dirs(object):
2376 2379 '''a multiset of directory names from a dirstate or manifest'''
2377 2380
2378 2381 def __init__(self, map, skip=None):
2379 2382 self._dirs = {}
2380 2383 addpath = self.addpath
2381 2384 if safehasattr(map, 'iteritems') and skip is not None:
2382 2385 for f, s in map.iteritems():
2383 2386 if s[0] != skip:
2384 2387 addpath(f)
2385 2388 else:
2386 2389 for f in map:
2387 2390 addpath(f)
2388 2391
2389 2392 def addpath(self, path):
2390 2393 dirs = self._dirs
2391 2394 for base in finddirs(path):
2392 2395 if base in dirs:
2393 2396 dirs[base] += 1
2394 2397 return
2395 2398 dirs[base] = 1
2396 2399
2397 2400 def delpath(self, path):
2398 2401 dirs = self._dirs
2399 2402 for base in finddirs(path):
2400 2403 if dirs[base] > 1:
2401 2404 dirs[base] -= 1
2402 2405 return
2403 2406 del dirs[base]
2404 2407
2405 2408 def __iter__(self):
2406 2409 return self._dirs.iterkeys()
2407 2410
2408 2411 def __contains__(self, d):
2409 2412 return d in self._dirs
2410 2413
2411 2414 if safehasattr(parsers, 'dirs'):
2412 2415 dirs = parsers.dirs
2413 2416
2414 2417 def finddirs(path):
2415 2418 pos = path.rfind('/')
2416 2419 while pos != -1:
2417 2420 yield path[:pos]
2418 2421 pos = path.rfind('/', 0, pos)
2419 2422
2420 2423 # compression utility
2421 2424
2422 2425 class nocompress(object):
2423 2426 def compress(self, x):
2424 2427 return x
2425 2428 def flush(self):
2426 2429 return ""
2427 2430
2428 2431 compressors = {
2429 2432 None: nocompress,
2430 2433 # lambda to prevent early import
2431 2434 'BZ': lambda: bz2.BZ2Compressor(),
2432 2435 'GZ': lambda: zlib.compressobj(),
2433 2436 }
2434 2437 # also support the old form by courtesies
2435 2438 compressors['UN'] = compressors[None]
2436 2439
2437 2440 def _makedecompressor(decompcls):
2438 2441 def generator(f):
2439 2442 d = decompcls()
2440 2443 for chunk in filechunkiter(f):
2441 2444 yield d.decompress(chunk)
2442 2445 def func(fh):
2443 2446 return chunkbuffer(generator(fh))
2444 2447 return func
2445 2448
2446 2449 def _bz2():
2447 2450 d = bz2.BZ2Decompressor()
2448 2451 # Bzip2 stream start with BZ, but we stripped it.
2449 2452 # we put it back for good measure.
2450 2453 d.decompress('BZ')
2451 2454 return d
2452 2455
2453 2456 decompressors = {None: lambda fh: fh,
2454 2457 '_truncatedBZ': _makedecompressor(_bz2),
2455 2458 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2456 2459 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2457 2460 }
2458 2461 # also support the old form by courtesies
2459 2462 decompressors['UN'] = decompressors[None]
2460 2463
2461 2464 # convenient shortcut
2462 2465 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now