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