##// END OF EJS Templates
workingctx: add explicit status method, add ignored and fix clean...
Steve Borho -
r11098:380ab78d default
parent child Browse files
Show More
@@ -1,933 +1,946 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)
568 568
569 569 self._extra = {}
570 570 if extra:
571 571 self._extra = extra.copy()
572 572 if 'branch' not in self._extra:
573 573 branch = self._repo.dirstate.branch()
574 574 try:
575 575 branch = branch.decode('UTF-8').encode('UTF-8')
576 576 except UnicodeDecodeError:
577 577 raise util.Abort(_('branch name not in UTF-8!'))
578 578 self._extra['branch'] = branch
579 579 if self._extra['branch'] == '':
580 580 self._extra['branch'] = 'default'
581 581
582 582 def __str__(self):
583 583 return str(self._parents[0]) + "+"
584 584
585 585 def __nonzero__(self):
586 586 return True
587 587
588 588 def __contains__(self, key):
589 589 return self._repo.dirstate[key] not in "?r"
590 590
591 591 @propertycache
592 592 def _manifest(self):
593 593 """generate a manifest corresponding to the working directory"""
594 594
595 595 man = self._parents[0].manifest().copy()
596 596 copied = self._repo.dirstate.copies()
597 597 if len(self._parents) > 1:
598 598 man2 = self.p2().manifest()
599 599 def getman(f):
600 600 if f in man:
601 601 return man
602 602 return man2
603 603 else:
604 604 getman = lambda f: man
605 605 def cf(f):
606 606 f = copied.get(f, f)
607 607 return getman(f).flags(f)
608 608 ff = self._repo.dirstate.flagfunc(cf)
609 609 modified, added, removed, deleted, unknown = self._status[:5]
610 610 for i, l in (("a", added), ("m", modified), ("u", unknown)):
611 611 for f in l:
612 612 orig = copied.get(f, f)
613 613 man[f] = getman(orig).get(orig, nullid) + i
614 614 try:
615 615 man.set(f, ff(f))
616 616 except OSError:
617 617 pass
618 618
619 619 for f in deleted + removed:
620 620 if f in man:
621 621 del man[f]
622 622
623 623 return man
624 624
625 625 @propertycache
626 626 def _status(self):
627 627 return self._repo.status(unknown=True)
628 628
629 629 @propertycache
630 630 def _user(self):
631 631 return self._repo.ui.username()
632 632
633 633 @propertycache
634 634 def _date(self):
635 635 return util.makedate()
636 636
637 637 @propertycache
638 638 def _parents(self):
639 639 p = self._repo.dirstate.parents()
640 640 if p[1] == nullid:
641 641 p = p[:-1]
642 642 self._parents = [changectx(self._repo, x) for x in p]
643 643 return self._parents
644 644
645 def status(self, ignored=False, clean=False, unknown=False):
646 """Explicit status query
647 Unless this method is used to query the working copy status, the
648 _status property will implicitly read the status using its default
649 arguments."""
650 self._status = self._repo.status(ignored=ignored, clean=clean,
651 unknown=unknown)
652 return self._status
653
645 654 def manifest(self):
646 655 return self._manifest
647 656 def user(self):
648 657 return self._user or self._repo.ui.username()
649 658 def date(self):
650 659 return self._date
651 660 def description(self):
652 661 return self._text
653 662 def files(self):
654 663 return sorted(self._status[0] + self._status[1] + self._status[2])
655 664
656 665 def modified(self):
657 666 return self._status[0]
658 667 def added(self):
659 668 return self._status[1]
660 669 def removed(self):
661 670 return self._status[2]
662 671 def deleted(self):
663 672 return self._status[3]
664 673 def unknown(self):
665 674 return self._status[4]
675 def ignored(self):
676 return self._status[5]
666 677 def clean(self):
667 return self._status[5]
678 return self._status[6]
668 679 def branch(self):
669 680 return self._extra['branch']
670 681 def extra(self):
671 682 return self._extra
672 683
673 684 def tags(self):
674 685 t = []
675 686 [t.extend(p.tags()) for p in self.parents()]
676 687 return t
677 688
678 689 def children(self):
679 690 return []
680 691
681 692 def flags(self, path):
682 693 if '_manifest' in self.__dict__:
683 694 try:
684 695 return self._manifest.flags(path)
685 696 except KeyError:
686 697 return ''
687 698
688 699 orig = self._repo.dirstate.copies().get(path, path)
689 700
690 701 def findflag(ctx):
691 702 mnode = ctx.changeset()[0]
692 703 node, flag = self._repo.manifest.find(mnode, orig)
693 704 ff = self._repo.dirstate.flagfunc(lambda x: flag or None)
694 705 try:
695 706 return ff(path)
696 707 except OSError:
697 708 pass
698 709
699 710 flag = findflag(self._parents[0])
700 711 if flag is None and len(self.parents()) > 1:
701 712 flag = findflag(self._parents[1])
702 713 if flag is None or self._repo.dirstate[path] == 'r':
703 714 return ''
704 715 return flag
705 716
706 717 def filectx(self, path, filelog=None):
707 718 """get a file context from the working directory"""
708 719 return workingfilectx(self._repo, path, workingctx=self,
709 720 filelog=filelog)
710 721
711 722 def ancestor(self, c2):
712 723 """return the ancestor context of self and c2"""
713 724 return self._parents[0].ancestor(c2) # punt on two parents for now
714 725
715 726 def walk(self, match):
716 727 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
717 728 True, False))
718 729
719 730 def dirty(self, missing=False):
720 731 "check whether a working directory is modified"
721 732
722 733 return (self.p2() or self.branch() != self.p1().branch() or
723 734 self.modified() or self.added() or self.removed() or
724 735 (missing and self.deleted()))
725 736
726 737 class workingfilectx(filectx):
727 738 """A workingfilectx object makes access to data related to a particular
728 739 file in the working directory convenient."""
729 740 def __init__(self, repo, path, filelog=None, workingctx=None):
730 741 """changeid can be a changeset revision, node, or tag.
731 742 fileid can be a file revision or node."""
732 743 self._repo = repo
733 744 self._path = path
734 745 self._changeid = None
735 746 self._filerev = self._filenode = None
736 747
737 748 if filelog:
738 749 self._filelog = filelog
739 750 if workingctx:
740 751 self._changectx = workingctx
741 752
742 753 @propertycache
743 754 def _changectx(self):
744 755 return workingctx(self._repo)
745 756
746 757 def __nonzero__(self):
747 758 return True
748 759
749 760 def __str__(self):
750 761 return "%s@%s" % (self.path(), self._changectx)
751 762
752 763 def data(self):
753 764 return self._repo.wread(self._path)
754 765 def renamed(self):
755 766 rp = self._repo.dirstate.copied(self._path)
756 767 if not rp:
757 768 return None
758 769 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
759 770
760 771 def parents(self):
761 772 '''return parent filectxs, following copies if necessary'''
762 773 def filenode(ctx, path):
763 774 return ctx._manifest.get(path, nullid)
764 775
765 776 path = self._path
766 777 fl = self._filelog
767 778 pcl = self._changectx._parents
768 779 renamed = self.renamed()
769 780
770 781 if renamed:
771 782 pl = [renamed + (None,)]
772 783 else:
773 784 pl = [(path, filenode(pcl[0], path), fl)]
774 785
775 786 for pc in pcl[1:]:
776 787 pl.append((path, filenode(pc, path), fl))
777 788
778 789 return [filectx(self._repo, p, fileid=n, filelog=l)
779 790 for p, n, l in pl if n != nullid]
780 791
781 792 def children(self):
782 793 return []
783 794
784 795 def size(self):
785 796 return os.stat(self._repo.wjoin(self._path)).st_size
786 797 def date(self):
787 798 t, tz = self._changectx.date()
788 799 try:
789 800 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
790 801 except OSError, err:
791 802 if err.errno != errno.ENOENT:
792 803 raise
793 804 return (t, tz)
794 805
795 806 def cmp(self, text):
796 807 return self._repo.wread(self._path) == text
797 808
798 809 class memctx(object):
799 810 """Use memctx to perform in-memory commits via localrepo.commitctx().
800 811
801 812 Revision information is supplied at initialization time while
802 813 related files data and is made available through a callback
803 814 mechanism. 'repo' is the current localrepo, 'parents' is a
804 815 sequence of two parent revisions identifiers (pass None for every
805 816 missing parent), 'text' is the commit message and 'files' lists
806 817 names of files touched by the revision (normalized and relative to
807 818 repository root).
808 819
809 820 filectxfn(repo, memctx, path) is a callable receiving the
810 821 repository, the current memctx object and the normalized path of
811 822 requested file, relative to repository root. It is fired by the
812 823 commit function for every file in 'files', but calls order is
813 824 undefined. If the file is available in the revision being
814 825 committed (updated or added), filectxfn returns a memfilectx
815 826 object. If the file was removed, filectxfn raises an
816 827 IOError. Moved files are represented by marking the source file
817 828 removed and the new file added with copy information (see
818 829 memfilectx).
819 830
820 831 user receives the committer name and defaults to current
821 832 repository username, date is the commit date in any format
822 833 supported by util.parsedate() and defaults to current date, extra
823 834 is a dictionary of metadata or is left empty.
824 835 """
825 836 def __init__(self, repo, parents, text, files, filectxfn, user=None,
826 837 date=None, extra=None):
827 838 self._repo = repo
828 839 self._rev = None
829 840 self._node = None
830 841 self._text = text
831 842 self._date = date and util.parsedate(date) or util.makedate()
832 843 self._user = user
833 844 parents = [(p or nullid) for p in parents]
834 845 p1, p2 = parents
835 846 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
836 847 files = sorted(set(files))
837 848 self._status = [files, [], [], [], []]
838 849 self._filectxfn = filectxfn
839 850
840 851 self._extra = extra and extra.copy() or {}
841 852 if 'branch' not in self._extra:
842 853 self._extra['branch'] = 'default'
843 854 elif self._extra.get('branch') == '':
844 855 self._extra['branch'] = 'default'
845 856
846 857 def __str__(self):
847 858 return str(self._parents[0]) + "+"
848 859
849 860 def __int__(self):
850 861 return self._rev
851 862
852 863 def __nonzero__(self):
853 864 return True
854 865
855 866 def __getitem__(self, key):
856 867 return self.filectx(key)
857 868
858 869 def p1(self):
859 870 return self._parents[0]
860 871 def p2(self):
861 872 return self._parents[1]
862 873
863 874 def user(self):
864 875 return self._user or self._repo.ui.username()
865 876 def date(self):
866 877 return self._date
867 878 def description(self):
868 879 return self._text
869 880 def files(self):
870 881 return self.modified()
871 882 def modified(self):
872 883 return self._status[0]
873 884 def added(self):
874 885 return self._status[1]
875 886 def removed(self):
876 887 return self._status[2]
877 888 def deleted(self):
878 889 return self._status[3]
879 890 def unknown(self):
880 891 return self._status[4]
892 def ignored(self):
893 return self._status[5]
881 894 def clean(self):
882 return self._status[5]
895 return self._status[6]
883 896 def branch(self):
884 897 return self._extra['branch']
885 898 def extra(self):
886 899 return self._extra
887 900 def flags(self, f):
888 901 return self[f].flags()
889 902
890 903 def parents(self):
891 904 """return contexts for each parent changeset"""
892 905 return self._parents
893 906
894 907 def filectx(self, path, filelog=None):
895 908 """get a file context from the working directory"""
896 909 return self._filectxfn(self._repo, self, path)
897 910
898 911 class memfilectx(object):
899 912 """memfilectx represents an in-memory file to commit.
900 913
901 914 See memctx for more details.
902 915 """
903 916 def __init__(self, path, data, islink, isexec, copied):
904 917 """
905 918 path is the normalized file path relative to repository root.
906 919 data is the file content as a string.
907 920 islink is True if the file is a symbolic link.
908 921 isexec is True if the file is executable.
909 922 copied is the source file path if current file was copied in the
910 923 revision being committed, or None."""
911 924 self._path = path
912 925 self._data = data
913 926 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
914 927 self._copied = None
915 928 if copied:
916 929 self._copied = (copied, nullid)
917 930
918 931 def __nonzero__(self):
919 932 return True
920 933 def __str__(self):
921 934 return "%s@%s" % (self.path(), self._changectx)
922 935 def path(self):
923 936 return self._path
924 937 def data(self):
925 938 return self._data
926 939 def flags(self):
927 940 return self._flags
928 941 def isexec(self):
929 942 return 'x' in self._flags
930 943 def islink(self):
931 944 return 'l' in self._flags
932 945 def renamed(self):
933 946 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now