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