##// END OF EJS Templates
context: use asserts for ignored and clean exceptions
Matt Mackall -
r11100:83968ae4 default
parent child Browse files
Show More
@@ -1,957 +1,955 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, bdiff, error, util, subrepo, patch
11 11 import os, errno
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)
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 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
118 118 def parents(self):
119 119 """return contexts for each parent changeset"""
120 120 return self._parents
121 121
122 122 def p1(self):
123 123 return self._parents[0]
124 124
125 125 def p2(self):
126 126 if len(self._parents) == 2:
127 127 return self._parents[1]
128 128 return changectx(self._repo, -1)
129 129
130 130 def children(self):
131 131 """return contexts for each child changeset"""
132 132 c = self._repo.changelog.children(self._node)
133 133 return [changectx(self._repo, x) for x in c]
134 134
135 135 def ancestors(self):
136 136 for a in self._repo.changelog.ancestors(self._rev):
137 137 yield changectx(self._repo, a)
138 138
139 139 def descendants(self):
140 140 for d in self._repo.changelog.descendants(self._rev):
141 141 yield changectx(self._repo, d)
142 142
143 143 def _fileinfo(self, path):
144 144 if '_manifest' in self.__dict__:
145 145 try:
146 146 return self._manifest[path], self._manifest.flags(path)
147 147 except KeyError:
148 148 raise error.LookupError(self._node, path,
149 149 _('not found in manifest'))
150 150 if '_manifestdelta' in self.__dict__ or path in self.files():
151 151 if path in self._manifestdelta:
152 152 return self._manifestdelta[path], self._manifestdelta.flags(path)
153 153 node, flag = self._repo.manifest.find(self._changeset[0], path)
154 154 if not node:
155 155 raise error.LookupError(self._node, path,
156 156 _('not found in manifest'))
157 157
158 158 return node, flag
159 159
160 160 def filenode(self, path):
161 161 return self._fileinfo(path)[0]
162 162
163 163 def flags(self, path):
164 164 try:
165 165 return self._fileinfo(path)[1]
166 166 except error.LookupError:
167 167 return ''
168 168
169 169 def filectx(self, path, fileid=None, filelog=None):
170 170 """get a file context from this changeset"""
171 171 if fileid is None:
172 172 fileid = self.filenode(path)
173 173 return filectx(self._repo, path, fileid=fileid,
174 174 changectx=self, filelog=filelog)
175 175
176 176 def ancestor(self, c2):
177 177 """
178 178 return the ancestor context of self and c2
179 179 """
180 180 # deal with workingctxs
181 181 n2 = c2._node
182 182 if n2 == None:
183 183 n2 = c2._parents[0]._node
184 184 n = self._repo.changelog.ancestor(self._node, n2)
185 185 return changectx(self._repo, n)
186 186
187 187 def walk(self, match):
188 188 fset = set(match.files())
189 189 # for dirstate.walk, files=['.'] means "walk the whole tree".
190 190 # follow that here, too
191 191 fset.discard('.')
192 192 for fn in self:
193 193 for ffn in fset:
194 194 # match if the file is the exact name or a directory
195 195 if ffn == fn or fn.startswith("%s/" % ffn):
196 196 fset.remove(ffn)
197 197 break
198 198 if match(fn):
199 199 yield fn
200 200 for fn in sorted(fset):
201 201 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
202 202 yield fn
203 203
204 204 def sub(self, path):
205 205 return subrepo.subrepo(self, path)
206 206
207 207 def diff(self, ctx2=None, match=None):
208 208 """Returns a diff generator for the given contexts and matcher"""
209 209 if ctx2 is None:
210 210 ctx2 = self.p1()
211 211 if ctx2 is not None and not isinstance(ctx2, changectx):
212 212 ctx2 = self._repo[ctx2]
213 213 return patch.diff(self._repo, ctx2.node(), self.node(), match=match)
214 214
215 215 class filectx(object):
216 216 """A filecontext object makes access to data related to a particular
217 217 filerevision convenient."""
218 218 def __init__(self, repo, path, changeid=None, fileid=None,
219 219 filelog=None, changectx=None):
220 220 """changeid can be a changeset revision, node, or tag.
221 221 fileid can be a file revision or node."""
222 222 self._repo = repo
223 223 self._path = path
224 224
225 225 assert (changeid is not None
226 226 or fileid is not None
227 227 or changectx is not None), \
228 228 ("bad args: changeid=%r, fileid=%r, changectx=%r"
229 229 % (changeid, fileid, changectx))
230 230
231 231 if filelog:
232 232 self._filelog = filelog
233 233
234 234 if changeid is not None:
235 235 self._changeid = changeid
236 236 if changectx is not None:
237 237 self._changectx = changectx
238 238 if fileid is not None:
239 239 self._fileid = fileid
240 240
241 241 @propertycache
242 242 def _changectx(self):
243 243 return changectx(self._repo, self._changeid)
244 244
245 245 @propertycache
246 246 def _filelog(self):
247 247 return self._repo.file(self._path)
248 248
249 249 @propertycache
250 250 def _changeid(self):
251 251 if '_changectx' in self.__dict__:
252 252 return self._changectx.rev()
253 253 else:
254 254 return self._filelog.linkrev(self._filerev)
255 255
256 256 @propertycache
257 257 def _filenode(self):
258 258 if '_fileid' in self.__dict__:
259 259 return self._filelog.lookup(self._fileid)
260 260 else:
261 261 return self._changectx.filenode(self._path)
262 262
263 263 @propertycache
264 264 def _filerev(self):
265 265 return self._filelog.rev(self._filenode)
266 266
267 267 @propertycache
268 268 def _repopath(self):
269 269 return self._path
270 270
271 271 def __nonzero__(self):
272 272 try:
273 273 self._filenode
274 274 return True
275 275 except error.LookupError:
276 276 # file is missing
277 277 return False
278 278
279 279 def __str__(self):
280 280 return "%s@%s" % (self.path(), short(self.node()))
281 281
282 282 def __repr__(self):
283 283 return "<filectx %s>" % str(self)
284 284
285 285 def __hash__(self):
286 286 try:
287 287 return hash((self._path, self._filenode))
288 288 except AttributeError:
289 289 return id(self)
290 290
291 291 def __eq__(self, other):
292 292 try:
293 293 return (self._path == other._path
294 294 and self._filenode == other._filenode)
295 295 except AttributeError:
296 296 return False
297 297
298 298 def __ne__(self, other):
299 299 return not (self == other)
300 300
301 301 def filectx(self, fileid):
302 302 '''opens an arbitrary revision of the file without
303 303 opening a new filelog'''
304 304 return filectx(self._repo, self._path, fileid=fileid,
305 305 filelog=self._filelog)
306 306
307 307 def filerev(self):
308 308 return self._filerev
309 309 def filenode(self):
310 310 return self._filenode
311 311 def flags(self):
312 312 return self._changectx.flags(self._path)
313 313 def filelog(self):
314 314 return self._filelog
315 315
316 316 def rev(self):
317 317 if '_changectx' in self.__dict__:
318 318 return self._changectx.rev()
319 319 if '_changeid' in self.__dict__:
320 320 return self._changectx.rev()
321 321 return self._filelog.linkrev(self._filerev)
322 322
323 323 def linkrev(self):
324 324 return self._filelog.linkrev(self._filerev)
325 325 def node(self):
326 326 return self._changectx.node()
327 327 def hex(self):
328 328 return hex(self.node())
329 329 def user(self):
330 330 return self._changectx.user()
331 331 def date(self):
332 332 return self._changectx.date()
333 333 def files(self):
334 334 return self._changectx.files()
335 335 def description(self):
336 336 return self._changectx.description()
337 337 def branch(self):
338 338 return self._changectx.branch()
339 339 def extra(self):
340 340 return self._changectx.extra()
341 341 def manifest(self):
342 342 return self._changectx.manifest()
343 343 def changectx(self):
344 344 return self._changectx
345 345
346 346 def data(self):
347 347 return self._filelog.read(self._filenode)
348 348 def path(self):
349 349 return self._path
350 350 def size(self):
351 351 return self._filelog.size(self._filerev)
352 352
353 353 def cmp(self, text):
354 354 return self._filelog.cmp(self._filenode, text)
355 355
356 356 def renamed(self):
357 357 """check if file was actually renamed in this changeset revision
358 358
359 359 If rename logged in file revision, we report copy for changeset only
360 360 if file revisions linkrev points back to the changeset in question
361 361 or both changeset parents contain different file revisions.
362 362 """
363 363
364 364 renamed = self._filelog.renamed(self._filenode)
365 365 if not renamed:
366 366 return renamed
367 367
368 368 if self.rev() == self.linkrev():
369 369 return renamed
370 370
371 371 name = self.path()
372 372 fnode = self._filenode
373 373 for p in self._changectx.parents():
374 374 try:
375 375 if fnode == p.filenode(name):
376 376 return None
377 377 except error.LookupError:
378 378 pass
379 379 return renamed
380 380
381 381 def parents(self):
382 382 p = self._path
383 383 fl = self._filelog
384 384 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
385 385
386 386 r = self._filelog.renamed(self._filenode)
387 387 if r:
388 388 pl[0] = (r[0], r[1], None)
389 389
390 390 return [filectx(self._repo, p, fileid=n, filelog=l)
391 391 for p, n, l in pl if n != nullid]
392 392
393 393 def children(self):
394 394 # hard for renames
395 395 c = self._filelog.children(self._filenode)
396 396 return [filectx(self._repo, self._path, fileid=x,
397 397 filelog=self._filelog) for x in c]
398 398
399 399 def annotate(self, follow=False, linenumber=None):
400 400 '''returns a list of tuples of (ctx, line) for each line
401 401 in the file, where ctx is the filectx of the node where
402 402 that line was last changed.
403 403 This returns tuples of ((ctx, linenumber), line) for each line,
404 404 if "linenumber" parameter is NOT "None".
405 405 In such tuples, linenumber means one at the first appearance
406 406 in the managed file.
407 407 To reduce annotation cost,
408 408 this returns fixed value(False is used) as linenumber,
409 409 if "linenumber" parameter is "False".'''
410 410
411 411 def decorate_compat(text, rev):
412 412 return ([rev] * len(text.splitlines()), text)
413 413
414 414 def without_linenumber(text, rev):
415 415 return ([(rev, False)] * len(text.splitlines()), text)
416 416
417 417 def with_linenumber(text, rev):
418 418 size = len(text.splitlines())
419 419 return ([(rev, i) for i in xrange(1, size + 1)], text)
420 420
421 421 decorate = (((linenumber is None) and decorate_compat) or
422 422 (linenumber and with_linenumber) or
423 423 without_linenumber)
424 424
425 425 def pair(parent, child):
426 426 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
427 427 child[0][b1:b2] = parent[0][a1:a2]
428 428 return child
429 429
430 430 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
431 431 def getctx(path, fileid):
432 432 log = path == self._path and self._filelog or getlog(path)
433 433 return filectx(self._repo, path, fileid=fileid, filelog=log)
434 434 getctx = util.lrucachefunc(getctx)
435 435
436 436 def parents(f):
437 437 # we want to reuse filectx objects as much as possible
438 438 p = f._path
439 439 if f._filerev is None: # working dir
440 440 pl = [(n.path(), n.filerev()) for n in f.parents()]
441 441 else:
442 442 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
443 443
444 444 if follow:
445 445 r = f.renamed()
446 446 if r:
447 447 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
448 448
449 449 return [getctx(p, n) for p, n in pl if n != nullrev]
450 450
451 451 # use linkrev to find the first changeset where self appeared
452 452 if self.rev() != self.linkrev():
453 453 base = self.filectx(self.filerev())
454 454 else:
455 455 base = self
456 456
457 457 # find all ancestors
458 458 needed = {base: 1}
459 459 visit = [base]
460 460 files = [base._path]
461 461 while visit:
462 462 f = visit.pop(0)
463 463 for p in parents(f):
464 464 if p not in needed:
465 465 needed[p] = 1
466 466 visit.append(p)
467 467 if p._path not in files:
468 468 files.append(p._path)
469 469 else:
470 470 # count how many times we'll use this
471 471 needed[p] += 1
472 472
473 473 # sort by revision (per file) which is a topological order
474 474 visit = []
475 475 for f in files:
476 476 visit.extend(n for n in needed if n._path == f)
477 477
478 478 hist = {}
479 479 for f in sorted(visit, key=lambda x: x.rev()):
480 480 curr = decorate(f.data(), f)
481 481 for p in parents(f):
482 482 curr = pair(hist[p], curr)
483 483 # trim the history of unneeded revs
484 484 needed[p] -= 1
485 485 if not needed[p]:
486 486 del hist[p]
487 487 hist[f] = curr
488 488
489 489 return zip(hist[f][0], hist[f][1].splitlines(True))
490 490
491 491 def ancestor(self, fc2):
492 492 """
493 493 find the common ancestor file context, if any, of self, and fc2
494 494 """
495 495
496 496 actx = self.changectx().ancestor(fc2.changectx())
497 497
498 498 # the trivial case: changesets are unrelated, files must be too
499 499 if not actx:
500 500 return None
501 501
502 502 # the easy case: no (relevant) renames
503 503 if fc2.path() == self.path() and self.path() in actx:
504 504 return actx[self.path()]
505 505 acache = {}
506 506
507 507 # prime the ancestor cache for the working directory
508 508 for c in (self, fc2):
509 509 if c._filerev is None:
510 510 pl = [(n.path(), n.filenode()) for n in c.parents()]
511 511 acache[(c._path, None)] = pl
512 512
513 513 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
514 514 def parents(vertex):
515 515 if vertex in acache:
516 516 return acache[vertex]
517 517 f, n = vertex
518 518 if f not in flcache:
519 519 flcache[f] = self._repo.file(f)
520 520 fl = flcache[f]
521 521 pl = [(f, p) for p in fl.parents(n) if p != nullid]
522 522 re = fl.renamed(n)
523 523 if re:
524 524 pl.append(re)
525 525 acache[vertex] = pl
526 526 return pl
527 527
528 528 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
529 529 v = ancestor.ancestor(a, b, parents)
530 530 if v:
531 531 f, n = v
532 532 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
533 533
534 534 return None
535 535
536 536 def ancestors(self):
537 537 seen = set(str(self))
538 538 visit = [self]
539 539 while visit:
540 540 for parent in visit.pop(0).parents():
541 541 s = str(parent)
542 542 if s not in seen:
543 543 visit.append(parent)
544 544 seen.add(s)
545 545 yield parent
546 546
547 547 class workingctx(changectx):
548 548 """A workingctx object makes access to data related to
549 549 the current working directory convenient.
550 550 date - any valid date string or (unixtime, offset), or None.
551 551 user - username string, or None.
552 552 extra - a dictionary of extra values, or None.
553 553 changes - a list of file lists as returned by localrepo.status()
554 554 or None to use the repository status.
555 555 """
556 556 def __init__(self, repo, text="", user=None, date=None, extra=None,
557 557 changes=None):
558 558 self._repo = repo
559 559 self._rev = None
560 560 self._node = None
561 561 self._text = text
562 562 if date:
563 563 self._date = util.parsedate(date)
564 564 if user:
565 565 self._user = user
566 566 if changes:
567 567 self._status = list(changes[:5])
568 568 self._ignored = changes[5]
569 569 self._clean = changes[6]
570 570 else:
571 571 self._ignored = None
572 572 self._clean = None
573 573
574 574 self._extra = {}
575 575 if extra:
576 576 self._extra = extra.copy()
577 577 if 'branch' not in self._extra:
578 578 branch = self._repo.dirstate.branch()
579 579 try:
580 580 branch = branch.decode('UTF-8').encode('UTF-8')
581 581 except UnicodeDecodeError:
582 582 raise util.Abort(_('branch name not in UTF-8!'))
583 583 self._extra['branch'] = branch
584 584 if self._extra['branch'] == '':
585 585 self._extra['branch'] = 'default'
586 586
587 587 def __str__(self):
588 588 return str(self._parents[0]) + "+"
589 589
590 590 def __nonzero__(self):
591 591 return True
592 592
593 593 def __contains__(self, key):
594 594 return self._repo.dirstate[key] not in "?r"
595 595
596 596 @propertycache
597 597 def _manifest(self):
598 598 """generate a manifest corresponding to the working directory"""
599 599
600 600 man = self._parents[0].manifest().copy()
601 601 copied = self._repo.dirstate.copies()
602 602 if len(self._parents) > 1:
603 603 man2 = self.p2().manifest()
604 604 def getman(f):
605 605 if f in man:
606 606 return man
607 607 return man2
608 608 else:
609 609 getman = lambda f: man
610 610 def cf(f):
611 611 f = copied.get(f, f)
612 612 return getman(f).flags(f)
613 613 ff = self._repo.dirstate.flagfunc(cf)
614 614 modified, added, removed, deleted, unknown = self._status[:5]
615 615 for i, l in (("a", added), ("m", modified), ("u", unknown)):
616 616 for f in l:
617 617 orig = copied.get(f, f)
618 618 man[f] = getman(orig).get(orig, nullid) + i
619 619 try:
620 620 man.set(f, ff(f))
621 621 except OSError:
622 622 pass
623 623
624 624 for f in deleted + removed:
625 625 if f in man:
626 626 del man[f]
627 627
628 628 return man
629 629
630 630 @propertycache
631 631 def _status(self):
632 632 return self._repo.status(unknown=True)[:5]
633 633
634 634 @propertycache
635 635 def _user(self):
636 636 return self._repo.ui.username()
637 637
638 638 @propertycache
639 639 def _date(self):
640 640 return util.makedate()
641 641
642 642 @propertycache
643 643 def _parents(self):
644 644 p = self._repo.dirstate.parents()
645 645 if p[1] == nullid:
646 646 p = p[:-1]
647 647 self._parents = [changectx(self._repo, x) for x in p]
648 648 return self._parents
649 649
650 650 def status(self, ignored=False, clean=False, unknown=False):
651 651 """Explicit status query
652 652 Unless this method is used to query the working copy status, the
653 653 _status property will implicitly read the status using its default
654 654 arguments."""
655 655 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
656 656 self._ignored = ignored and stat[5] or None
657 657 self._clean = clean and stat[6] or None
658 658 self._status = stat[:5]
659 659 return self._status
660 660
661 661 def manifest(self):
662 662 return self._manifest
663 663 def user(self):
664 664 return self._user or self._repo.ui.username()
665 665 def date(self):
666 666 return self._date
667 667 def description(self):
668 668 return self._text
669 669 def files(self):
670 670 return sorted(self._status[0] + self._status[1] + self._status[2])
671 671
672 672 def modified(self):
673 673 return self._status[0]
674 674 def added(self):
675 675 return self._status[1]
676 676 def removed(self):
677 677 return self._status[2]
678 678 def deleted(self):
679 679 return self._status[3]
680 680 def unknown(self):
681 681 return self._status[4]
682 682 def ignored(self):
683 if self._ignored is None:
684 raise util.Abort(_("Ignored files requested without prior query\n"))
683 assert self._ignored is not None # must call status first
685 684 return self._ignored
686 685 def clean(self):
687 if self._clean is None:
688 raise util.Abort(_("Clean files requested without prior query\n"))
686 assert self._clean is not None # must call status first
689 687 return self._clean
690 688 def branch(self):
691 689 return self._extra['branch']
692 690 def extra(self):
693 691 return self._extra
694 692
695 693 def tags(self):
696 694 t = []
697 695 [t.extend(p.tags()) for p in self.parents()]
698 696 return t
699 697
700 698 def children(self):
701 699 return []
702 700
703 701 def flags(self, path):
704 702 if '_manifest' in self.__dict__:
705 703 try:
706 704 return self._manifest.flags(path)
707 705 except KeyError:
708 706 return ''
709 707
710 708 orig = self._repo.dirstate.copies().get(path, path)
711 709
712 710 def findflag(ctx):
713 711 mnode = ctx.changeset()[0]
714 712 node, flag = self._repo.manifest.find(mnode, orig)
715 713 ff = self._repo.dirstate.flagfunc(lambda x: flag or None)
716 714 try:
717 715 return ff(path)
718 716 except OSError:
719 717 pass
720 718
721 719 flag = findflag(self._parents[0])
722 720 if flag is None and len(self.parents()) > 1:
723 721 flag = findflag(self._parents[1])
724 722 if flag is None or self._repo.dirstate[path] == 'r':
725 723 return ''
726 724 return flag
727 725
728 726 def filectx(self, path, filelog=None):
729 727 """get a file context from the working directory"""
730 728 return workingfilectx(self._repo, path, workingctx=self,
731 729 filelog=filelog)
732 730
733 731 def ancestor(self, c2):
734 732 """return the ancestor context of self and c2"""
735 733 return self._parents[0].ancestor(c2) # punt on two parents for now
736 734
737 735 def walk(self, match):
738 736 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
739 737 True, False))
740 738
741 739 def dirty(self, missing=False):
742 740 "check whether a working directory is modified"
743 741
744 742 return (self.p2() or self.branch() != self.p1().branch() or
745 743 self.modified() or self.added() or self.removed() or
746 744 (missing and self.deleted()))
747 745
748 746 class workingfilectx(filectx):
749 747 """A workingfilectx object makes access to data related to a particular
750 748 file in the working directory convenient."""
751 749 def __init__(self, repo, path, filelog=None, workingctx=None):
752 750 """changeid can be a changeset revision, node, or tag.
753 751 fileid can be a file revision or node."""
754 752 self._repo = repo
755 753 self._path = path
756 754 self._changeid = None
757 755 self._filerev = self._filenode = None
758 756
759 757 if filelog:
760 758 self._filelog = filelog
761 759 if workingctx:
762 760 self._changectx = workingctx
763 761
764 762 @propertycache
765 763 def _changectx(self):
766 764 return workingctx(self._repo)
767 765
768 766 def __nonzero__(self):
769 767 return True
770 768
771 769 def __str__(self):
772 770 return "%s@%s" % (self.path(), self._changectx)
773 771
774 772 def data(self):
775 773 return self._repo.wread(self._path)
776 774 def renamed(self):
777 775 rp = self._repo.dirstate.copied(self._path)
778 776 if not rp:
779 777 return None
780 778 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
781 779
782 780 def parents(self):
783 781 '''return parent filectxs, following copies if necessary'''
784 782 def filenode(ctx, path):
785 783 return ctx._manifest.get(path, nullid)
786 784
787 785 path = self._path
788 786 fl = self._filelog
789 787 pcl = self._changectx._parents
790 788 renamed = self.renamed()
791 789
792 790 if renamed:
793 791 pl = [renamed + (None,)]
794 792 else:
795 793 pl = [(path, filenode(pcl[0], path), fl)]
796 794
797 795 for pc in pcl[1:]:
798 796 pl.append((path, filenode(pc, path), fl))
799 797
800 798 return [filectx(self._repo, p, fileid=n, filelog=l)
801 799 for p, n, l in pl if n != nullid]
802 800
803 801 def children(self):
804 802 return []
805 803
806 804 def size(self):
807 805 return os.stat(self._repo.wjoin(self._path)).st_size
808 806 def date(self):
809 807 t, tz = self._changectx.date()
810 808 try:
811 809 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
812 810 except OSError, err:
813 811 if err.errno != errno.ENOENT:
814 812 raise
815 813 return (t, tz)
816 814
817 815 def cmp(self, text):
818 816 return self._repo.wread(self._path) == text
819 817
820 818 class memctx(object):
821 819 """Use memctx to perform in-memory commits via localrepo.commitctx().
822 820
823 821 Revision information is supplied at initialization time while
824 822 related files data and is made available through a callback
825 823 mechanism. 'repo' is the current localrepo, 'parents' is a
826 824 sequence of two parent revisions identifiers (pass None for every
827 825 missing parent), 'text' is the commit message and 'files' lists
828 826 names of files touched by the revision (normalized and relative to
829 827 repository root).
830 828
831 829 filectxfn(repo, memctx, path) is a callable receiving the
832 830 repository, the current memctx object and the normalized path of
833 831 requested file, relative to repository root. It is fired by the
834 832 commit function for every file in 'files', but calls order is
835 833 undefined. If the file is available in the revision being
836 834 committed (updated or added), filectxfn returns a memfilectx
837 835 object. If the file was removed, filectxfn raises an
838 836 IOError. Moved files are represented by marking the source file
839 837 removed and the new file added with copy information (see
840 838 memfilectx).
841 839
842 840 user receives the committer name and defaults to current
843 841 repository username, date is the commit date in any format
844 842 supported by util.parsedate() and defaults to current date, extra
845 843 is a dictionary of metadata or is left empty.
846 844 """
847 845 def __init__(self, repo, parents, text, files, filectxfn, user=None,
848 846 date=None, extra=None):
849 847 self._repo = repo
850 848 self._rev = None
851 849 self._node = None
852 850 self._text = text
853 851 self._date = date and util.parsedate(date) or util.makedate()
854 852 self._user = user
855 853 parents = [(p or nullid) for p in parents]
856 854 p1, p2 = parents
857 855 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
858 856 files = sorted(set(files))
859 857 self._status = [files, [], [], [], []]
860 858 self._filectxfn = filectxfn
861 859
862 860 self._extra = extra and extra.copy() or {}
863 861 if 'branch' not in self._extra:
864 862 self._extra['branch'] = 'default'
865 863 elif self._extra.get('branch') == '':
866 864 self._extra['branch'] = 'default'
867 865
868 866 def __str__(self):
869 867 return str(self._parents[0]) + "+"
870 868
871 869 def __int__(self):
872 870 return self._rev
873 871
874 872 def __nonzero__(self):
875 873 return True
876 874
877 875 def __getitem__(self, key):
878 876 return self.filectx(key)
879 877
880 878 def p1(self):
881 879 return self._parents[0]
882 880 def p2(self):
883 881 return self._parents[1]
884 882
885 883 def user(self):
886 884 return self._user or self._repo.ui.username()
887 885 def date(self):
888 886 return self._date
889 887 def description(self):
890 888 return self._text
891 889 def files(self):
892 890 return self.modified()
893 891 def modified(self):
894 892 return self._status[0]
895 893 def added(self):
896 894 return self._status[1]
897 895 def removed(self):
898 896 return self._status[2]
899 897 def deleted(self):
900 898 return self._status[3]
901 899 def unknown(self):
902 900 return self._status[4]
903 901 def ignored(self):
904 902 return self._status[5]
905 903 def clean(self):
906 904 return self._status[6]
907 905 def branch(self):
908 906 return self._extra['branch']
909 907 def extra(self):
910 908 return self._extra
911 909 def flags(self, f):
912 910 return self[f].flags()
913 911
914 912 def parents(self):
915 913 """return contexts for each parent changeset"""
916 914 return self._parents
917 915
918 916 def filectx(self, path, filelog=None):
919 917 """get a file context from the working directory"""
920 918 return self._filectxfn(self._repo, self, path)
921 919
922 920 class memfilectx(object):
923 921 """memfilectx represents an in-memory file to commit.
924 922
925 923 See memctx for more details.
926 924 """
927 925 def __init__(self, path, data, islink, isexec, copied):
928 926 """
929 927 path is the normalized file path relative to repository root.
930 928 data is the file content as a string.
931 929 islink is True if the file is a symbolic link.
932 930 isexec is True if the file is executable.
933 931 copied is the source file path if current file was copied in the
934 932 revision being committed, or None."""
935 933 self._path = path
936 934 self._data = data
937 935 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
938 936 self._copied = None
939 937 if copied:
940 938 self._copied = (copied, nullid)
941 939
942 940 def __nonzero__(self):
943 941 return True
944 942 def __str__(self):
945 943 return "%s@%s" % (self.path(), self._changectx)
946 944 def path(self):
947 945 return self._path
948 946 def data(self):
949 947 return self._data
950 948 def flags(self):
951 949 return self._flags
952 950 def isexec(self):
953 951 return 'x' in self._flags
954 952 def islink(self):
955 953 return 'l' in self._flags
956 954 def renamed(self):
957 955 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now