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