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