##// END OF EJS Templates
context: fix call to util.safehasattr
Idan Kamara -
r16610:f1745323 default
parent child Browse files
Show More
@@ -1,1275 +1,1275 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, short, hex, bin
9 9 from i18n import _
10 10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 11 import copies
12 12 import match as matchmod
13 13 import os, errno, stat
14 14
15 15 propertycache = util.propertycache
16 16
17 17 class changectx(object):
18 18 """A changecontext object makes access to data related to a particular
19 19 changeset convenient."""
20 20 def __init__(self, repo, changeid=''):
21 21 """changeid is a revision number, node, or tag"""
22 22 if changeid == '':
23 23 changeid = '.'
24 24 self._repo = repo
25 25
26 26 if isinstance(changeid, int):
27 27 self._rev = changeid
28 28 self._node = repo.changelog.node(changeid)
29 29 return
30 30 if changeid == '.':
31 31 self._node = repo.dirstate.p1()
32 32 self._rev = repo.changelog.rev(self._node)
33 33 return
34 34 if changeid == 'null':
35 35 self._node = nullid
36 36 self._rev = nullrev
37 37 return
38 38 if changeid == 'tip':
39 39 self._rev = len(repo.changelog) - 1
40 40 self._node = repo.changelog.node(self._rev)
41 41 return
42 42 if len(changeid) == 20:
43 43 try:
44 44 self._node = changeid
45 45 self._rev = repo.changelog.rev(changeid)
46 46 return
47 47 except LookupError:
48 48 pass
49 49
50 50 try:
51 51 r = int(changeid)
52 52 if str(r) != changeid:
53 53 raise ValueError
54 54 l = len(repo.changelog)
55 55 if r < 0:
56 56 r += l
57 57 if r < 0 or r >= l:
58 58 raise ValueError
59 59 self._rev = r
60 60 self._node = repo.changelog.node(r)
61 61 return
62 62 except (ValueError, OverflowError):
63 63 pass
64 64
65 65 if len(changeid) == 40:
66 66 try:
67 67 self._node = bin(changeid)
68 68 self._rev = repo.changelog.rev(self._node)
69 69 return
70 70 except (TypeError, LookupError):
71 71 pass
72 72
73 73 if changeid in repo._bookmarks:
74 74 self._node = repo._bookmarks[changeid]
75 75 self._rev = repo.changelog.rev(self._node)
76 76 return
77 77 if changeid in repo._tagscache.tags:
78 78 self._node = repo._tagscache.tags[changeid]
79 79 self._rev = repo.changelog.rev(self._node)
80 80 return
81 81 if changeid in repo.branchtags():
82 82 self._node = repo.branchtags()[changeid]
83 83 self._rev = repo.changelog.rev(self._node)
84 84 return
85 85
86 86 self._node = repo.changelog._partialmatch(changeid)
87 87 if self._node is not None:
88 88 self._rev = repo.changelog.rev(self._node)
89 89 return
90 90
91 91 # lookup failed
92 92 # check if it might have come from damaged dirstate
93 93 if changeid in repo.dirstate.parents():
94 94 raise error.Abort(_("working directory has unknown parent '%s'!")
95 95 % short(changeid))
96 96 try:
97 97 if len(changeid) == 20:
98 98 changeid = hex(changeid)
99 99 except TypeError:
100 100 pass
101 101 raise error.RepoLookupError(
102 102 _("unknown revision '%s'") % changeid)
103 103
104 104 def __str__(self):
105 105 return short(self.node())
106 106
107 107 def __int__(self):
108 108 return self.rev()
109 109
110 110 def __repr__(self):
111 111 return "<changectx %s>" % str(self)
112 112
113 113 def __hash__(self):
114 114 try:
115 115 return hash(self._rev)
116 116 except AttributeError:
117 117 return id(self)
118 118
119 119 def __eq__(self, other):
120 120 try:
121 121 return self._rev == other._rev
122 122 except AttributeError:
123 123 return False
124 124
125 125 def __ne__(self, other):
126 126 return not (self == other)
127 127
128 128 def __nonzero__(self):
129 129 return self._rev != nullrev
130 130
131 131 @propertycache
132 132 def _changeset(self):
133 133 return self._repo.changelog.read(self.rev())
134 134
135 135 @propertycache
136 136 def _manifest(self):
137 137 return self._repo.manifest.read(self._changeset[0])
138 138
139 139 @propertycache
140 140 def _manifestdelta(self):
141 141 return self._repo.manifest.readdelta(self._changeset[0])
142 142
143 143 @propertycache
144 144 def _parents(self):
145 145 p = self._repo.changelog.parentrevs(self._rev)
146 146 if p[1] == nullrev:
147 147 p = p[:-1]
148 148 return [changectx(self._repo, x) for x in p]
149 149
150 150 @propertycache
151 151 def substate(self):
152 152 return subrepo.state(self, self._repo.ui)
153 153
154 154 def __contains__(self, key):
155 155 return key in self._manifest
156 156
157 157 def __getitem__(self, key):
158 158 return self.filectx(key)
159 159
160 160 def __iter__(self):
161 161 for f in sorted(self._manifest):
162 162 yield f
163 163
164 164 def changeset(self):
165 165 return self._changeset
166 166 def manifest(self):
167 167 return self._manifest
168 168 def manifestnode(self):
169 169 return self._changeset[0]
170 170
171 171 def rev(self):
172 172 return self._rev
173 173 def node(self):
174 174 return self._node
175 175 def hex(self):
176 176 return hex(self._node)
177 177 def user(self):
178 178 return self._changeset[1]
179 179 def date(self):
180 180 return self._changeset[2]
181 181 def files(self):
182 182 return self._changeset[3]
183 183 def description(self):
184 184 return self._changeset[4]
185 185 def branch(self):
186 186 return encoding.tolocal(self._changeset[5].get("branch"))
187 187 def extra(self):
188 188 return self._changeset[5]
189 189 def tags(self):
190 190 return self._repo.nodetags(self._node)
191 191 def bookmarks(self):
192 192 return self._repo.nodebookmarks(self._node)
193 193 def phase(self):
194 194 if self._rev == -1:
195 195 return phases.public
196 196 if self._rev >= len(self._repo._phaserev):
197 197 # outdated cache
198 198 del self._repo._phaserev
199 199 return self._repo._phaserev[self._rev]
200 200 def phasestr(self):
201 201 return phases.phasenames[self.phase()]
202 202 def mutable(self):
203 203 return self.phase() > phases.public
204 204 def hidden(self):
205 205 return self._rev in self._repo.changelog.hiddenrevs
206 206
207 207 def parents(self):
208 208 """return contexts for each parent changeset"""
209 209 return self._parents
210 210
211 211 def p1(self):
212 212 return self._parents[0]
213 213
214 214 def p2(self):
215 215 if len(self._parents) == 2:
216 216 return self._parents[1]
217 217 return changectx(self._repo, -1)
218 218
219 219 def children(self):
220 220 """return contexts for each child changeset"""
221 221 c = self._repo.changelog.children(self._node)
222 222 return [changectx(self._repo, x) for x in c]
223 223
224 224 def ancestors(self):
225 225 for a in self._repo.changelog.ancestors(self._rev):
226 226 yield changectx(self._repo, a)
227 227
228 228 def descendants(self):
229 229 for d in self._repo.changelog.descendants(self._rev):
230 230 yield changectx(self._repo, d)
231 231
232 232 def _fileinfo(self, path):
233 233 if '_manifest' in self.__dict__:
234 234 try:
235 235 return self._manifest[path], self._manifest.flags(path)
236 236 except KeyError:
237 237 raise error.LookupError(self._node, path,
238 238 _('not found in manifest'))
239 239 if '_manifestdelta' in self.__dict__ or path in self.files():
240 240 if path in self._manifestdelta:
241 241 return self._manifestdelta[path], self._manifestdelta.flags(path)
242 242 node, flag = self._repo.manifest.find(self._changeset[0], path)
243 243 if not node:
244 244 raise error.LookupError(self._node, path,
245 245 _('not found in manifest'))
246 246
247 247 return node, flag
248 248
249 249 def filenode(self, path):
250 250 return self._fileinfo(path)[0]
251 251
252 252 def flags(self, path):
253 253 try:
254 254 return self._fileinfo(path)[1]
255 255 except error.LookupError:
256 256 return ''
257 257
258 258 def filectx(self, path, fileid=None, filelog=None):
259 259 """get a file context from this changeset"""
260 260 if fileid is None:
261 261 fileid = self.filenode(path)
262 262 return filectx(self._repo, path, fileid=fileid,
263 263 changectx=self, filelog=filelog)
264 264
265 265 def ancestor(self, c2):
266 266 """
267 267 return the ancestor context of self and c2
268 268 """
269 269 # deal with workingctxs
270 270 n2 = c2._node
271 271 if n2 is None:
272 272 n2 = c2._parents[0]._node
273 273 n = self._repo.changelog.ancestor(self._node, n2)
274 274 return changectx(self._repo, n)
275 275
276 276 def walk(self, match):
277 277 fset = set(match.files())
278 278 # for dirstate.walk, files=['.'] means "walk the whole tree".
279 279 # follow that here, too
280 280 fset.discard('.')
281 281 for fn in self:
282 282 if fn in fset:
283 283 # specified pattern is the exact name
284 284 fset.remove(fn)
285 285 if match(fn):
286 286 yield fn
287 287 for fn in sorted(fset):
288 288 if fn in self._dirs:
289 289 # specified pattern is a directory
290 290 continue
291 291 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
292 292 yield fn
293 293
294 294 def sub(self, path):
295 295 return subrepo.subrepo(self, path)
296 296
297 297 def match(self, pats=[], include=None, exclude=None, default='glob'):
298 298 r = self._repo
299 299 return matchmod.match(r.root, r.getcwd(), pats,
300 300 include, exclude, default,
301 301 auditor=r.auditor, ctx=self)
302 302
303 303 def diff(self, ctx2=None, match=None, **opts):
304 304 """Returns a diff generator for the given contexts and matcher"""
305 305 if ctx2 is None:
306 306 ctx2 = self.p1()
307 307 if ctx2 is not None and not isinstance(ctx2, changectx):
308 308 ctx2 = self._repo[ctx2]
309 309 diffopts = patch.diffopts(self._repo.ui, opts)
310 310 return patch.diff(self._repo, ctx2.node(), self.node(),
311 311 match=match, opts=diffopts)
312 312
313 313 @propertycache
314 314 def _dirs(self):
315 315 dirs = set()
316 316 for f in self._manifest:
317 317 pos = f.rfind('/')
318 318 while pos != -1:
319 319 f = f[:pos]
320 320 if f in dirs:
321 321 break # dirs already contains this and above
322 322 dirs.add(f)
323 323 pos = f.rfind('/')
324 324 return dirs
325 325
326 326 def dirs(self):
327 327 return self._dirs
328 328
329 329 class filectx(object):
330 330 """A filecontext object makes access to data related to a particular
331 331 filerevision convenient."""
332 332 def __init__(self, repo, path, changeid=None, fileid=None,
333 333 filelog=None, changectx=None):
334 334 """changeid can be a changeset revision, node, or tag.
335 335 fileid can be a file revision or node."""
336 336 self._repo = repo
337 337 self._path = path
338 338
339 339 assert (changeid is not None
340 340 or fileid is not None
341 341 or changectx is not None), \
342 342 ("bad args: changeid=%r, fileid=%r, changectx=%r"
343 343 % (changeid, fileid, changectx))
344 344
345 345 if filelog:
346 346 self._filelog = filelog
347 347
348 348 if changeid is not None:
349 349 self._changeid = changeid
350 350 if changectx is not None:
351 351 self._changectx = changectx
352 352 if fileid is not None:
353 353 self._fileid = fileid
354 354
355 355 @propertycache
356 356 def _changectx(self):
357 357 return changectx(self._repo, self._changeid)
358 358
359 359 @propertycache
360 360 def _filelog(self):
361 361 return self._repo.file(self._path)
362 362
363 363 @propertycache
364 364 def _changeid(self):
365 365 if '_changectx' in self.__dict__:
366 366 return self._changectx.rev()
367 367 else:
368 368 return self._filelog.linkrev(self._filerev)
369 369
370 370 @propertycache
371 371 def _filenode(self):
372 372 if '_fileid' in self.__dict__:
373 373 return self._filelog.lookup(self._fileid)
374 374 else:
375 375 return self._changectx.filenode(self._path)
376 376
377 377 @propertycache
378 378 def _filerev(self):
379 379 return self._filelog.rev(self._filenode)
380 380
381 381 @propertycache
382 382 def _repopath(self):
383 383 return self._path
384 384
385 385 def __nonzero__(self):
386 386 try:
387 387 self._filenode
388 388 return True
389 389 except error.LookupError:
390 390 # file is missing
391 391 return False
392 392
393 393 def __str__(self):
394 394 return "%s@%s" % (self.path(), short(self.node()))
395 395
396 396 def __repr__(self):
397 397 return "<filectx %s>" % str(self)
398 398
399 399 def __hash__(self):
400 400 try:
401 401 return hash((self._path, self._filenode))
402 402 except AttributeError:
403 403 return id(self)
404 404
405 405 def __eq__(self, other):
406 406 try:
407 407 return (self._path == other._path
408 408 and self._filenode == other._filenode)
409 409 except AttributeError:
410 410 return False
411 411
412 412 def __ne__(self, other):
413 413 return not (self == other)
414 414
415 415 def filectx(self, fileid):
416 416 '''opens an arbitrary revision of the file without
417 417 opening a new filelog'''
418 418 return filectx(self._repo, self._path, fileid=fileid,
419 419 filelog=self._filelog)
420 420
421 421 def filerev(self):
422 422 return self._filerev
423 423 def filenode(self):
424 424 return self._filenode
425 425 def flags(self):
426 426 return self._changectx.flags(self._path)
427 427 def filelog(self):
428 428 return self._filelog
429 429
430 430 def rev(self):
431 431 if '_changectx' in self.__dict__:
432 432 return self._changectx.rev()
433 433 if '_changeid' in self.__dict__:
434 434 return self._changectx.rev()
435 435 return self._filelog.linkrev(self._filerev)
436 436
437 437 def linkrev(self):
438 438 return self._filelog.linkrev(self._filerev)
439 439 def node(self):
440 440 return self._changectx.node()
441 441 def hex(self):
442 442 return hex(self.node())
443 443 def user(self):
444 444 return self._changectx.user()
445 445 def date(self):
446 446 return self._changectx.date()
447 447 def files(self):
448 448 return self._changectx.files()
449 449 def description(self):
450 450 return self._changectx.description()
451 451 def branch(self):
452 452 return self._changectx.branch()
453 453 def extra(self):
454 454 return self._changectx.extra()
455 455 def manifest(self):
456 456 return self._changectx.manifest()
457 457 def changectx(self):
458 458 return self._changectx
459 459
460 460 def data(self):
461 461 return self._filelog.read(self._filenode)
462 462 def path(self):
463 463 return self._path
464 464 def size(self):
465 465 return self._filelog.size(self._filerev)
466 466
467 467 def isbinary(self):
468 468 try:
469 469 return util.binary(self.data())
470 470 except IOError:
471 471 return False
472 472
473 473 def cmp(self, fctx):
474 474 """compare with other file context
475 475
476 476 returns True if different than fctx.
477 477 """
478 478 if (fctx._filerev is None
479 479 and (self._repo._encodefilterpats
480 480 # if file data starts with '\1\n', empty metadata block is
481 481 # prepended, which adds 4 bytes to filelog.size().
482 482 or self.size() - 4 == fctx.size())
483 483 or self.size() == fctx.size()):
484 484 return self._filelog.cmp(self._filenode, fctx.data())
485 485
486 486 return True
487 487
488 488 def renamed(self):
489 489 """check if file was actually renamed in this changeset revision
490 490
491 491 If rename logged in file revision, we report copy for changeset only
492 492 if file revisions linkrev points back to the changeset in question
493 493 or both changeset parents contain different file revisions.
494 494 """
495 495
496 496 renamed = self._filelog.renamed(self._filenode)
497 497 if not renamed:
498 498 return renamed
499 499
500 500 if self.rev() == self.linkrev():
501 501 return renamed
502 502
503 503 name = self.path()
504 504 fnode = self._filenode
505 505 for p in self._changectx.parents():
506 506 try:
507 507 if fnode == p.filenode(name):
508 508 return None
509 509 except error.LookupError:
510 510 pass
511 511 return renamed
512 512
513 513 def parents(self):
514 514 p = self._path
515 515 fl = self._filelog
516 516 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
517 517
518 518 r = self._filelog.renamed(self._filenode)
519 519 if r:
520 520 pl[0] = (r[0], r[1], None)
521 521
522 522 return [filectx(self._repo, p, fileid=n, filelog=l)
523 523 for p, n, l in pl if n != nullid]
524 524
525 525 def p1(self):
526 526 return self.parents()[0]
527 527
528 528 def p2(self):
529 529 p = self.parents()
530 530 if len(p) == 2:
531 531 return p[1]
532 532 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
533 533
534 534 def children(self):
535 535 # hard for renames
536 536 c = self._filelog.children(self._filenode)
537 537 return [filectx(self._repo, self._path, fileid=x,
538 538 filelog=self._filelog) for x in c]
539 539
540 540 def annotate(self, follow=False, linenumber=None, diffopts=None):
541 541 '''returns a list of tuples of (ctx, line) for each line
542 542 in the file, where ctx is the filectx of the node where
543 543 that line was last changed.
544 544 This returns tuples of ((ctx, linenumber), line) for each line,
545 545 if "linenumber" parameter is NOT "None".
546 546 In such tuples, linenumber means one at the first appearance
547 547 in the managed file.
548 548 To reduce annotation cost,
549 549 this returns fixed value(False is used) as linenumber,
550 550 if "linenumber" parameter is "False".'''
551 551
552 552 def decorate_compat(text, rev):
553 553 return ([rev] * len(text.splitlines()), text)
554 554
555 555 def without_linenumber(text, rev):
556 556 return ([(rev, False)] * len(text.splitlines()), text)
557 557
558 558 def with_linenumber(text, rev):
559 559 size = len(text.splitlines())
560 560 return ([(rev, i) for i in xrange(1, size + 1)], text)
561 561
562 562 decorate = (((linenumber is None) and decorate_compat) or
563 563 (linenumber and with_linenumber) or
564 564 without_linenumber)
565 565
566 566 def pair(parent, child):
567 567 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
568 568 refine=True)
569 569 for (a1, a2, b1, b2), t in blocks:
570 570 # Changed blocks ('!') or blocks made only of blank lines ('~')
571 571 # belong to the child.
572 572 if t == '=':
573 573 child[0][b1:b2] = parent[0][a1:a2]
574 574 return child
575 575
576 576 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
577 577 def getctx(path, fileid):
578 578 log = path == self._path and self._filelog or getlog(path)
579 579 return filectx(self._repo, path, fileid=fileid, filelog=log)
580 580 getctx = util.lrucachefunc(getctx)
581 581
582 582 def parents(f):
583 583 # we want to reuse filectx objects as much as possible
584 584 p = f._path
585 585 if f._filerev is None: # working dir
586 586 pl = [(n.path(), n.filerev()) for n in f.parents()]
587 587 else:
588 588 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
589 589
590 590 if follow:
591 591 r = f.renamed()
592 592 if r:
593 593 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
594 594
595 595 return [getctx(p, n) for p, n in pl if n != nullrev]
596 596
597 597 # use linkrev to find the first changeset where self appeared
598 598 if self.rev() != self.linkrev():
599 599 base = self.filectx(self.filerev())
600 600 else:
601 601 base = self
602 602
603 603 # This algorithm would prefer to be recursive, but Python is a
604 604 # bit recursion-hostile. Instead we do an iterative
605 605 # depth-first search.
606 606
607 607 visit = [base]
608 608 hist = {}
609 609 pcache = {}
610 610 needed = {base: 1}
611 611 while visit:
612 612 f = visit[-1]
613 613 if f not in pcache:
614 614 pcache[f] = parents(f)
615 615
616 616 ready = True
617 617 pl = pcache[f]
618 618 for p in pl:
619 619 if p not in hist:
620 620 ready = False
621 621 visit.append(p)
622 622 needed[p] = needed.get(p, 0) + 1
623 623 if ready:
624 624 visit.pop()
625 625 curr = decorate(f.data(), f)
626 626 for p in pl:
627 627 curr = pair(hist[p], curr)
628 628 if needed[p] == 1:
629 629 del hist[p]
630 630 else:
631 631 needed[p] -= 1
632 632
633 633 hist[f] = curr
634 634 pcache[f] = []
635 635
636 636 return zip(hist[base][0], hist[base][1].splitlines(True))
637 637
638 638 def ancestor(self, fc2, actx):
639 639 """
640 640 find the common ancestor file context, if any, of self, and fc2
641 641
642 642 actx must be the changectx of the common ancestor
643 643 of self's and fc2's respective changesets.
644 644 """
645 645
646 646 # the easy case: no (relevant) renames
647 647 if fc2.path() == self.path() and self.path() in actx:
648 648 return actx[self.path()]
649 649
650 650 # the next easiest cases: unambiguous predecessor (name trumps
651 651 # history)
652 652 if self.path() in actx and fc2.path() not in actx:
653 653 return actx[self.path()]
654 654 if fc2.path() in actx and self.path() not in actx:
655 655 return actx[fc2.path()]
656 656
657 657 # prime the ancestor cache for the working directory
658 658 acache = {}
659 659 for c in (self, fc2):
660 660 if c._filerev is None:
661 661 pl = [(n.path(), n.filenode()) for n in c.parents()]
662 662 acache[(c._path, None)] = pl
663 663
664 664 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
665 665 def parents(vertex):
666 666 if vertex in acache:
667 667 return acache[vertex]
668 668 f, n = vertex
669 669 if f not in flcache:
670 670 flcache[f] = self._repo.file(f)
671 671 fl = flcache[f]
672 672 pl = [(f, p) for p in fl.parents(n) if p != nullid]
673 673 re = fl.renamed(n)
674 674 if re:
675 675 pl.append(re)
676 676 acache[vertex] = pl
677 677 return pl
678 678
679 679 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
680 680 v = ancestor.ancestor(a, b, parents)
681 681 if v:
682 682 f, n = v
683 683 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
684 684
685 685 return None
686 686
687 687 def ancestors(self, followfirst=False):
688 688 visit = {}
689 689 c = self
690 690 cut = followfirst and 1 or None
691 691 while True:
692 692 for parent in c.parents()[:cut]:
693 693 visit[(parent.rev(), parent.node())] = parent
694 694 if not visit:
695 695 break
696 696 c = visit.pop(max(visit))
697 697 yield c
698 698
699 699 def copies(self, c2):
700 if not util.hasattr(self, "_copycache"):
700 if not util.safehasattr(self, "_copycache"):
701 701 self._copycache = {}
702 702 sc2 = str(c2)
703 703 if sc2 not in self._copycache:
704 704 self._copycache[sc2] = copies.pathcopies(c2)
705 705 return self._copycache[sc2]
706 706
707 707 class workingctx(changectx):
708 708 """A workingctx object makes access to data related to
709 709 the current working directory convenient.
710 710 date - any valid date string or (unixtime, offset), or None.
711 711 user - username string, or None.
712 712 extra - a dictionary of extra values, or None.
713 713 changes - a list of file lists as returned by localrepo.status()
714 714 or None to use the repository status.
715 715 """
716 716 def __init__(self, repo, text="", user=None, date=None, extra=None,
717 717 changes=None):
718 718 self._repo = repo
719 719 self._rev = None
720 720 self._node = None
721 721 self._text = text
722 722 if date:
723 723 self._date = util.parsedate(date)
724 724 if user:
725 725 self._user = user
726 726 if changes:
727 727 self._status = list(changes[:4])
728 728 self._unknown = changes[4]
729 729 self._ignored = changes[5]
730 730 self._clean = changes[6]
731 731 else:
732 732 self._unknown = None
733 733 self._ignored = None
734 734 self._clean = None
735 735
736 736 self._extra = {}
737 737 if extra:
738 738 self._extra = extra.copy()
739 739 if 'branch' not in self._extra:
740 740 try:
741 741 branch = encoding.fromlocal(self._repo.dirstate.branch())
742 742 except UnicodeDecodeError:
743 743 raise util.Abort(_('branch name not in UTF-8!'))
744 744 self._extra['branch'] = branch
745 745 if self._extra['branch'] == '':
746 746 self._extra['branch'] = 'default'
747 747
748 748 def __str__(self):
749 749 return str(self._parents[0]) + "+"
750 750
751 751 def __repr__(self):
752 752 return "<workingctx %s>" % str(self)
753 753
754 754 def __nonzero__(self):
755 755 return True
756 756
757 757 def __contains__(self, key):
758 758 return self._repo.dirstate[key] not in "?r"
759 759
760 760 def _buildflagfunc(self):
761 761 # Create a fallback function for getting file flags when the
762 762 # filesystem doesn't support them
763 763
764 764 copiesget = self._repo.dirstate.copies().get
765 765
766 766 if len(self._parents) < 2:
767 767 # when we have one parent, it's easy: copy from parent
768 768 man = self._parents[0].manifest()
769 769 def func(f):
770 770 f = copiesget(f, f)
771 771 return man.flags(f)
772 772 else:
773 773 # merges are tricky: we try to reconstruct the unstored
774 774 # result from the merge (issue1802)
775 775 p1, p2 = self._parents
776 776 pa = p1.ancestor(p2)
777 777 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
778 778
779 779 def func(f):
780 780 f = copiesget(f, f) # may be wrong for merges with copies
781 781 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
782 782 if fl1 == fl2:
783 783 return fl1
784 784 if fl1 == fla:
785 785 return fl2
786 786 if fl2 == fla:
787 787 return fl1
788 788 return '' # punt for conflicts
789 789
790 790 return func
791 791
792 792 @propertycache
793 793 def _flagfunc(self):
794 794 return self._repo.dirstate.flagfunc(self._buildflagfunc)
795 795
796 796 @propertycache
797 797 def _manifest(self):
798 798 """generate a manifest corresponding to the working directory"""
799 799
800 800 man = self._parents[0].manifest().copy()
801 801 if len(self._parents) > 1:
802 802 man2 = self.p2().manifest()
803 803 def getman(f):
804 804 if f in man:
805 805 return man
806 806 return man2
807 807 else:
808 808 getman = lambda f: man
809 809
810 810 copied = self._repo.dirstate.copies()
811 811 ff = self._flagfunc
812 812 modified, added, removed, deleted = self._status
813 813 for i, l in (("a", added), ("m", modified)):
814 814 for f in l:
815 815 orig = copied.get(f, f)
816 816 man[f] = getman(orig).get(orig, nullid) + i
817 817 try:
818 818 man.set(f, ff(f))
819 819 except OSError:
820 820 pass
821 821
822 822 for f in deleted + removed:
823 823 if f in man:
824 824 del man[f]
825 825
826 826 return man
827 827
828 828 def __iter__(self):
829 829 d = self._repo.dirstate
830 830 for f in d:
831 831 if d[f] != 'r':
832 832 yield f
833 833
834 834 @propertycache
835 835 def _status(self):
836 836 return self._repo.status()[:4]
837 837
838 838 @propertycache
839 839 def _user(self):
840 840 return self._repo.ui.username()
841 841
842 842 @propertycache
843 843 def _date(self):
844 844 return util.makedate()
845 845
846 846 @propertycache
847 847 def _parents(self):
848 848 p = self._repo.dirstate.parents()
849 849 if p[1] == nullid:
850 850 p = p[:-1]
851 851 self._parents = [changectx(self._repo, x) for x in p]
852 852 return self._parents
853 853
854 854 def status(self, ignored=False, clean=False, unknown=False):
855 855 """Explicit status query
856 856 Unless this method is used to query the working copy status, the
857 857 _status property will implicitly read the status using its default
858 858 arguments."""
859 859 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
860 860 self._unknown = self._ignored = self._clean = None
861 861 if unknown:
862 862 self._unknown = stat[4]
863 863 if ignored:
864 864 self._ignored = stat[5]
865 865 if clean:
866 866 self._clean = stat[6]
867 867 self._status = stat[:4]
868 868 return stat
869 869
870 870 def manifest(self):
871 871 return self._manifest
872 872 def user(self):
873 873 return self._user or self._repo.ui.username()
874 874 def date(self):
875 875 return self._date
876 876 def description(self):
877 877 return self._text
878 878 def files(self):
879 879 return sorted(self._status[0] + self._status[1] + self._status[2])
880 880
881 881 def modified(self):
882 882 return self._status[0]
883 883 def added(self):
884 884 return self._status[1]
885 885 def removed(self):
886 886 return self._status[2]
887 887 def deleted(self):
888 888 return self._status[3]
889 889 def unknown(self):
890 890 assert self._unknown is not None # must call status first
891 891 return self._unknown
892 892 def ignored(self):
893 893 assert self._ignored is not None # must call status first
894 894 return self._ignored
895 895 def clean(self):
896 896 assert self._clean is not None # must call status first
897 897 return self._clean
898 898 def branch(self):
899 899 return encoding.tolocal(self._extra['branch'])
900 900 def extra(self):
901 901 return self._extra
902 902
903 903 def tags(self):
904 904 t = []
905 905 for p in self.parents():
906 906 t.extend(p.tags())
907 907 return t
908 908
909 909 def bookmarks(self):
910 910 b = []
911 911 for p in self.parents():
912 912 b.extend(p.bookmarks())
913 913 return b
914 914
915 915 def phase(self):
916 916 phase = phases.draft # default phase to draft
917 917 for p in self.parents():
918 918 phase = max(phase, p.phase())
919 919 return phase
920 920
921 921 def hidden(self):
922 922 return False
923 923
924 924 def children(self):
925 925 return []
926 926
927 927 def flags(self, path):
928 928 if '_manifest' in self.__dict__:
929 929 try:
930 930 return self._manifest.flags(path)
931 931 except KeyError:
932 932 return ''
933 933
934 934 try:
935 935 return self._flagfunc(path)
936 936 except OSError:
937 937 return ''
938 938
939 939 def filectx(self, path, filelog=None):
940 940 """get a file context from the working directory"""
941 941 return workingfilectx(self._repo, path, workingctx=self,
942 942 filelog=filelog)
943 943
944 944 def ancestor(self, c2):
945 945 """return the ancestor context of self and c2"""
946 946 return self._parents[0].ancestor(c2) # punt on two parents for now
947 947
948 948 def walk(self, match):
949 949 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
950 950 True, False))
951 951
952 952 def dirty(self, missing=False, merge=True, branch=True):
953 953 "check whether a working directory is modified"
954 954 # check subrepos first
955 955 for s in self.substate:
956 956 if self.sub(s).dirty():
957 957 return True
958 958 # check current working dir
959 959 return ((merge and self.p2()) or
960 960 (branch and self.branch() != self.p1().branch()) or
961 961 self.modified() or self.added() or self.removed() or
962 962 (missing and self.deleted()))
963 963
964 964 def add(self, list, prefix=""):
965 965 join = lambda f: os.path.join(prefix, f)
966 966 wlock = self._repo.wlock()
967 967 ui, ds = self._repo.ui, self._repo.dirstate
968 968 try:
969 969 rejected = []
970 970 for f in list:
971 971 scmutil.checkportable(ui, join(f))
972 972 p = self._repo.wjoin(f)
973 973 try:
974 974 st = os.lstat(p)
975 975 except OSError:
976 976 ui.warn(_("%s does not exist!\n") % join(f))
977 977 rejected.append(f)
978 978 continue
979 979 if st.st_size > 10000000:
980 980 ui.warn(_("%s: up to %d MB of RAM may be required "
981 981 "to manage this file\n"
982 982 "(use 'hg revert %s' to cancel the "
983 983 "pending addition)\n")
984 984 % (f, 3 * st.st_size // 1000000, join(f)))
985 985 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
986 986 ui.warn(_("%s not added: only files and symlinks "
987 987 "supported currently\n") % join(f))
988 988 rejected.append(p)
989 989 elif ds[f] in 'amn':
990 990 ui.warn(_("%s already tracked!\n") % join(f))
991 991 elif ds[f] == 'r':
992 992 ds.normallookup(f)
993 993 else:
994 994 ds.add(f)
995 995 return rejected
996 996 finally:
997 997 wlock.release()
998 998
999 999 def forget(self, files, prefix=""):
1000 1000 join = lambda f: os.path.join(prefix, f)
1001 1001 wlock = self._repo.wlock()
1002 1002 try:
1003 1003 rejected = []
1004 1004 for f in files:
1005 1005 if f not in self._repo.dirstate:
1006 1006 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1007 1007 rejected.append(f)
1008 1008 elif self._repo.dirstate[f] != 'a':
1009 1009 self._repo.dirstate.remove(f)
1010 1010 else:
1011 1011 self._repo.dirstate.drop(f)
1012 1012 return rejected
1013 1013 finally:
1014 1014 wlock.release()
1015 1015
1016 1016 def ancestors(self):
1017 1017 for a in self._repo.changelog.ancestors(
1018 1018 *[p.rev() for p in self._parents]):
1019 1019 yield changectx(self._repo, a)
1020 1020
1021 1021 def undelete(self, list):
1022 1022 pctxs = self.parents()
1023 1023 wlock = self._repo.wlock()
1024 1024 try:
1025 1025 for f in list:
1026 1026 if self._repo.dirstate[f] != 'r':
1027 1027 self._repo.ui.warn(_("%s not removed!\n") % f)
1028 1028 else:
1029 1029 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1030 1030 t = fctx.data()
1031 1031 self._repo.wwrite(f, t, fctx.flags())
1032 1032 self._repo.dirstate.normal(f)
1033 1033 finally:
1034 1034 wlock.release()
1035 1035
1036 1036 def copy(self, source, dest):
1037 1037 p = self._repo.wjoin(dest)
1038 1038 if not os.path.lexists(p):
1039 1039 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1040 1040 elif not (os.path.isfile(p) or os.path.islink(p)):
1041 1041 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1042 1042 "symbolic link\n") % dest)
1043 1043 else:
1044 1044 wlock = self._repo.wlock()
1045 1045 try:
1046 1046 if self._repo.dirstate[dest] in '?r':
1047 1047 self._repo.dirstate.add(dest)
1048 1048 self._repo.dirstate.copy(source, dest)
1049 1049 finally:
1050 1050 wlock.release()
1051 1051
1052 1052 def dirs(self):
1053 1053 return self._repo.dirstate.dirs()
1054 1054
1055 1055 class workingfilectx(filectx):
1056 1056 """A workingfilectx object makes access to data related to a particular
1057 1057 file in the working directory convenient."""
1058 1058 def __init__(self, repo, path, filelog=None, workingctx=None):
1059 1059 """changeid can be a changeset revision, node, or tag.
1060 1060 fileid can be a file revision or node."""
1061 1061 self._repo = repo
1062 1062 self._path = path
1063 1063 self._changeid = None
1064 1064 self._filerev = self._filenode = None
1065 1065
1066 1066 if filelog:
1067 1067 self._filelog = filelog
1068 1068 if workingctx:
1069 1069 self._changectx = workingctx
1070 1070
1071 1071 @propertycache
1072 1072 def _changectx(self):
1073 1073 return workingctx(self._repo)
1074 1074
1075 1075 def __nonzero__(self):
1076 1076 return True
1077 1077
1078 1078 def __str__(self):
1079 1079 return "%s@%s" % (self.path(), self._changectx)
1080 1080
1081 1081 def __repr__(self):
1082 1082 return "<workingfilectx %s>" % str(self)
1083 1083
1084 1084 def data(self):
1085 1085 return self._repo.wread(self._path)
1086 1086 def renamed(self):
1087 1087 rp = self._repo.dirstate.copied(self._path)
1088 1088 if not rp:
1089 1089 return None
1090 1090 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1091 1091
1092 1092 def parents(self):
1093 1093 '''return parent filectxs, following copies if necessary'''
1094 1094 def filenode(ctx, path):
1095 1095 return ctx._manifest.get(path, nullid)
1096 1096
1097 1097 path = self._path
1098 1098 fl = self._filelog
1099 1099 pcl = self._changectx._parents
1100 1100 renamed = self.renamed()
1101 1101
1102 1102 if renamed:
1103 1103 pl = [renamed + (None,)]
1104 1104 else:
1105 1105 pl = [(path, filenode(pcl[0], path), fl)]
1106 1106
1107 1107 for pc in pcl[1:]:
1108 1108 pl.append((path, filenode(pc, path), fl))
1109 1109
1110 1110 return [filectx(self._repo, p, fileid=n, filelog=l)
1111 1111 for p, n, l in pl if n != nullid]
1112 1112
1113 1113 def children(self):
1114 1114 return []
1115 1115
1116 1116 def size(self):
1117 1117 return os.lstat(self._repo.wjoin(self._path)).st_size
1118 1118 def date(self):
1119 1119 t, tz = self._changectx.date()
1120 1120 try:
1121 1121 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1122 1122 except OSError, err:
1123 1123 if err.errno != errno.ENOENT:
1124 1124 raise
1125 1125 return (t, tz)
1126 1126
1127 1127 def cmp(self, fctx):
1128 1128 """compare with other file context
1129 1129
1130 1130 returns True if different than fctx.
1131 1131 """
1132 1132 # fctx should be a filectx (not a wfctx)
1133 1133 # invert comparison to reuse the same code path
1134 1134 return fctx.cmp(self)
1135 1135
1136 1136 class memctx(object):
1137 1137 """Use memctx to perform in-memory commits via localrepo.commitctx().
1138 1138
1139 1139 Revision information is supplied at initialization time while
1140 1140 related files data and is made available through a callback
1141 1141 mechanism. 'repo' is the current localrepo, 'parents' is a
1142 1142 sequence of two parent revisions identifiers (pass None for every
1143 1143 missing parent), 'text' is the commit message and 'files' lists
1144 1144 names of files touched by the revision (normalized and relative to
1145 1145 repository root).
1146 1146
1147 1147 filectxfn(repo, memctx, path) is a callable receiving the
1148 1148 repository, the current memctx object and the normalized path of
1149 1149 requested file, relative to repository root. It is fired by the
1150 1150 commit function for every file in 'files', but calls order is
1151 1151 undefined. If the file is available in the revision being
1152 1152 committed (updated or added), filectxfn returns a memfilectx
1153 1153 object. If the file was removed, filectxfn raises an
1154 1154 IOError. Moved files are represented by marking the source file
1155 1155 removed and the new file added with copy information (see
1156 1156 memfilectx).
1157 1157
1158 1158 user receives the committer name and defaults to current
1159 1159 repository username, date is the commit date in any format
1160 1160 supported by util.parsedate() and defaults to current date, extra
1161 1161 is a dictionary of metadata or is left empty.
1162 1162 """
1163 1163 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1164 1164 date=None, extra=None):
1165 1165 self._repo = repo
1166 1166 self._rev = None
1167 1167 self._node = None
1168 1168 self._text = text
1169 1169 self._date = date and util.parsedate(date) or util.makedate()
1170 1170 self._user = user
1171 1171 parents = [(p or nullid) for p in parents]
1172 1172 p1, p2 = parents
1173 1173 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1174 1174 files = sorted(set(files))
1175 1175 self._status = [files, [], [], [], []]
1176 1176 self._filectxfn = filectxfn
1177 1177
1178 1178 self._extra = extra and extra.copy() or {}
1179 1179 if self._extra.get('branch', '') == '':
1180 1180 self._extra['branch'] = 'default'
1181 1181
1182 1182 def __str__(self):
1183 1183 return str(self._parents[0]) + "+"
1184 1184
1185 1185 def __int__(self):
1186 1186 return self._rev
1187 1187
1188 1188 def __nonzero__(self):
1189 1189 return True
1190 1190
1191 1191 def __getitem__(self, key):
1192 1192 return self.filectx(key)
1193 1193
1194 1194 def p1(self):
1195 1195 return self._parents[0]
1196 1196 def p2(self):
1197 1197 return self._parents[1]
1198 1198
1199 1199 def user(self):
1200 1200 return self._user or self._repo.ui.username()
1201 1201 def date(self):
1202 1202 return self._date
1203 1203 def description(self):
1204 1204 return self._text
1205 1205 def files(self):
1206 1206 return self.modified()
1207 1207 def modified(self):
1208 1208 return self._status[0]
1209 1209 def added(self):
1210 1210 return self._status[1]
1211 1211 def removed(self):
1212 1212 return self._status[2]
1213 1213 def deleted(self):
1214 1214 return self._status[3]
1215 1215 def unknown(self):
1216 1216 return self._status[4]
1217 1217 def ignored(self):
1218 1218 return self._status[5]
1219 1219 def clean(self):
1220 1220 return self._status[6]
1221 1221 def branch(self):
1222 1222 return encoding.tolocal(self._extra['branch'])
1223 1223 def extra(self):
1224 1224 return self._extra
1225 1225 def flags(self, f):
1226 1226 return self[f].flags()
1227 1227
1228 1228 def parents(self):
1229 1229 """return contexts for each parent changeset"""
1230 1230 return self._parents
1231 1231
1232 1232 def filectx(self, path, filelog=None):
1233 1233 """get a file context from the working directory"""
1234 1234 return self._filectxfn(self._repo, self, path)
1235 1235
1236 1236 def commit(self):
1237 1237 """commit context to the repo"""
1238 1238 return self._repo.commitctx(self)
1239 1239
1240 1240 class memfilectx(object):
1241 1241 """memfilectx represents an in-memory file to commit.
1242 1242
1243 1243 See memctx for more details.
1244 1244 """
1245 1245 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1246 1246 """
1247 1247 path is the normalized file path relative to repository root.
1248 1248 data is the file content as a string.
1249 1249 islink is True if the file is a symbolic link.
1250 1250 isexec is True if the file is executable.
1251 1251 copied is the source file path if current file was copied in the
1252 1252 revision being committed, or None."""
1253 1253 self._path = path
1254 1254 self._data = data
1255 1255 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1256 1256 self._copied = None
1257 1257 if copied:
1258 1258 self._copied = (copied, nullid)
1259 1259
1260 1260 def __nonzero__(self):
1261 1261 return True
1262 1262 def __str__(self):
1263 1263 return "%s@%s" % (self.path(), self._changectx)
1264 1264 def path(self):
1265 1265 return self._path
1266 1266 def data(self):
1267 1267 return self._data
1268 1268 def flags(self):
1269 1269 return self._flags
1270 1270 def isexec(self):
1271 1271 return 'x' in self._flags
1272 1272 def islink(self):
1273 1273 return 'l' in self._flags
1274 1274 def renamed(self):
1275 1275 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now