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