##// END OF EJS Templates
context: narrow down filter special case in filectx.cmp()...
Christian Ebert -
r12731:95514b58 default
parent child Browse files
Show More
@@ -1,1092 +1,1093 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, stat
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, self._repo.ui)
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 %s') % 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, **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, fctx):
356 356 """compare with other file context
357 357
358 358 returns True if different than fctx.
359 359 """
360 if not self._repo._encodefilterpats and self.size() != fctx.size():
361 return True
360 if (fctx._filerev is None and self._repo._encodefilterpats
361 or self.size() == fctx.size()):
362 return self._filelog.cmp(self._filenode, fctx.data())
362 363
363 return self._filelog.cmp(self._filenode, fctx.data())
364 return True
364 365
365 366 def renamed(self):
366 367 """check if file was actually renamed in this changeset revision
367 368
368 369 If rename logged in file revision, we report copy for changeset only
369 370 if file revisions linkrev points back to the changeset in question
370 371 or both changeset parents contain different file revisions.
371 372 """
372 373
373 374 renamed = self._filelog.renamed(self._filenode)
374 375 if not renamed:
375 376 return renamed
376 377
377 378 if self.rev() == self.linkrev():
378 379 return renamed
379 380
380 381 name = self.path()
381 382 fnode = self._filenode
382 383 for p in self._changectx.parents():
383 384 try:
384 385 if fnode == p.filenode(name):
385 386 return None
386 387 except error.LookupError:
387 388 pass
388 389 return renamed
389 390
390 391 def parents(self):
391 392 p = self._path
392 393 fl = self._filelog
393 394 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
394 395
395 396 r = self._filelog.renamed(self._filenode)
396 397 if r:
397 398 pl[0] = (r[0], r[1], None)
398 399
399 400 return [filectx(self._repo, p, fileid=n, filelog=l)
400 401 for p, n, l in pl if n != nullid]
401 402
402 403 def children(self):
403 404 # hard for renames
404 405 c = self._filelog.children(self._filenode)
405 406 return [filectx(self._repo, self._path, fileid=x,
406 407 filelog=self._filelog) for x in c]
407 408
408 409 def annotate(self, follow=False, linenumber=None):
409 410 '''returns a list of tuples of (ctx, line) for each line
410 411 in the file, where ctx is the filectx of the node where
411 412 that line was last changed.
412 413 This returns tuples of ((ctx, linenumber), line) for each line,
413 414 if "linenumber" parameter is NOT "None".
414 415 In such tuples, linenumber means one at the first appearance
415 416 in the managed file.
416 417 To reduce annotation cost,
417 418 this returns fixed value(False is used) as linenumber,
418 419 if "linenumber" parameter is "False".'''
419 420
420 421 def decorate_compat(text, rev):
421 422 return ([rev] * len(text.splitlines()), text)
422 423
423 424 def without_linenumber(text, rev):
424 425 return ([(rev, False)] * len(text.splitlines()), text)
425 426
426 427 def with_linenumber(text, rev):
427 428 size = len(text.splitlines())
428 429 return ([(rev, i) for i in xrange(1, size + 1)], text)
429 430
430 431 decorate = (((linenumber is None) and decorate_compat) or
431 432 (linenumber and with_linenumber) or
432 433 without_linenumber)
433 434
434 435 def pair(parent, child):
435 436 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
436 437 child[0][b1:b2] = parent[0][a1:a2]
437 438 return child
438 439
439 440 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
440 441 def getctx(path, fileid):
441 442 log = path == self._path and self._filelog or getlog(path)
442 443 return filectx(self._repo, path, fileid=fileid, filelog=log)
443 444 getctx = util.lrucachefunc(getctx)
444 445
445 446 def parents(f):
446 447 # we want to reuse filectx objects as much as possible
447 448 p = f._path
448 449 if f._filerev is None: # working dir
449 450 pl = [(n.path(), n.filerev()) for n in f.parents()]
450 451 else:
451 452 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
452 453
453 454 if follow:
454 455 r = f.renamed()
455 456 if r:
456 457 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
457 458
458 459 return [getctx(p, n) for p, n in pl if n != nullrev]
459 460
460 461 # use linkrev to find the first changeset where self appeared
461 462 if self.rev() != self.linkrev():
462 463 base = self.filectx(self.filerev())
463 464 else:
464 465 base = self
465 466
466 467 # find all ancestors
467 468 needed = {base: 1}
468 469 visit = [base]
469 470 files = [base._path]
470 471 while visit:
471 472 f = visit.pop(0)
472 473 for p in parents(f):
473 474 if p not in needed:
474 475 needed[p] = 1
475 476 visit.append(p)
476 477 if p._path not in files:
477 478 files.append(p._path)
478 479 else:
479 480 # count how many times we'll use this
480 481 needed[p] += 1
481 482
482 483 # sort by revision (per file) which is a topological order
483 484 visit = []
484 485 for f in files:
485 486 visit.extend(n for n in needed if n._path == f)
486 487
487 488 hist = {}
488 489 for f in sorted(visit, key=lambda x: x.rev()):
489 490 curr = decorate(f.data(), f)
490 491 for p in parents(f):
491 492 curr = pair(hist[p], curr)
492 493 # trim the history of unneeded revs
493 494 needed[p] -= 1
494 495 if not needed[p]:
495 496 del hist[p]
496 497 hist[f] = curr
497 498
498 499 return zip(hist[f][0], hist[f][1].splitlines(True))
499 500
500 501 def ancestor(self, fc2, actx=None):
501 502 """
502 503 find the common ancestor file context, if any, of self, and fc2
503 504
504 505 If actx is given, it must be the changectx of the common ancestor
505 506 of self's and fc2's respective changesets.
506 507 """
507 508
508 509 if actx is None:
509 510 actx = self.changectx().ancestor(fc2.changectx())
510 511
511 512 # the trivial case: changesets are unrelated, files must be too
512 513 if not actx:
513 514 return None
514 515
515 516 # the easy case: no (relevant) renames
516 517 if fc2.path() == self.path() and self.path() in actx:
517 518 return actx[self.path()]
518 519 acache = {}
519 520
520 521 # prime the ancestor cache for the working directory
521 522 for c in (self, fc2):
522 523 if c._filerev is None:
523 524 pl = [(n.path(), n.filenode()) for n in c.parents()]
524 525 acache[(c._path, None)] = pl
525 526
526 527 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
527 528 def parents(vertex):
528 529 if vertex in acache:
529 530 return acache[vertex]
530 531 f, n = vertex
531 532 if f not in flcache:
532 533 flcache[f] = self._repo.file(f)
533 534 fl = flcache[f]
534 535 pl = [(f, p) for p in fl.parents(n) if p != nullid]
535 536 re = fl.renamed(n)
536 537 if re:
537 538 pl.append(re)
538 539 acache[vertex] = pl
539 540 return pl
540 541
541 542 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
542 543 v = ancestor.ancestor(a, b, parents)
543 544 if v:
544 545 f, n = v
545 546 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
546 547
547 548 return None
548 549
549 550 def ancestors(self):
550 551 seen = set(str(self))
551 552 visit = [self]
552 553 while visit:
553 554 for parent in visit.pop(0).parents():
554 555 s = str(parent)
555 556 if s not in seen:
556 557 visit.append(parent)
557 558 seen.add(s)
558 559 yield parent
559 560
560 561 class workingctx(changectx):
561 562 """A workingctx object makes access to data related to
562 563 the current working directory convenient.
563 564 date - any valid date string or (unixtime, offset), or None.
564 565 user - username string, or None.
565 566 extra - a dictionary of extra values, or None.
566 567 changes - a list of file lists as returned by localrepo.status()
567 568 or None to use the repository status.
568 569 """
569 570 def __init__(self, repo, text="", user=None, date=None, extra=None,
570 571 changes=None):
571 572 self._repo = repo
572 573 self._rev = None
573 574 self._node = None
574 575 self._text = text
575 576 if date:
576 577 self._date = util.parsedate(date)
577 578 if user:
578 579 self._user = user
579 580 if changes:
580 581 self._status = list(changes[:4])
581 582 self._unknown = changes[4]
582 583 self._ignored = changes[5]
583 584 self._clean = changes[6]
584 585 else:
585 586 self._unknown = None
586 587 self._ignored = None
587 588 self._clean = None
588 589
589 590 self._extra = {}
590 591 if extra:
591 592 self._extra = extra.copy()
592 593 if 'branch' not in self._extra:
593 594 branch = self._repo.dirstate.branch()
594 595 try:
595 596 branch = branch.decode('UTF-8').encode('UTF-8')
596 597 except UnicodeDecodeError:
597 598 raise util.Abort(_('branch name not in UTF-8!'))
598 599 self._extra['branch'] = branch
599 600 if self._extra['branch'] == '':
600 601 self._extra['branch'] = 'default'
601 602
602 603 def __str__(self):
603 604 return str(self._parents[0]) + "+"
604 605
605 606 def __nonzero__(self):
606 607 return True
607 608
608 609 def __contains__(self, key):
609 610 return self._repo.dirstate[key] not in "?r"
610 611
611 612 @propertycache
612 613 def _manifest(self):
613 614 """generate a manifest corresponding to the working directory"""
614 615
615 616 if self._unknown is None:
616 617 self.status(unknown=True)
617 618
618 619 man = self._parents[0].manifest().copy()
619 620 copied = self._repo.dirstate.copies()
620 621 if len(self._parents) > 1:
621 622 man2 = self.p2().manifest()
622 623 def getman(f):
623 624 if f in man:
624 625 return man
625 626 return man2
626 627 else:
627 628 getman = lambda f: man
628 629 def cf(f):
629 630 f = copied.get(f, f)
630 631 return getman(f).flags(f)
631 632 ff = self._repo.dirstate.flagfunc(cf)
632 633 modified, added, removed, deleted = self._status
633 634 unknown = self._unknown
634 635 for i, l in (("a", added), ("m", modified), ("u", unknown)):
635 636 for f in l:
636 637 orig = copied.get(f, f)
637 638 man[f] = getman(orig).get(orig, nullid) + i
638 639 try:
639 640 man.set(f, ff(f))
640 641 except OSError:
641 642 pass
642 643
643 644 for f in deleted + removed:
644 645 if f in man:
645 646 del man[f]
646 647
647 648 return man
648 649
649 650 @propertycache
650 651 def _status(self):
651 652 return self._repo.status()[:4]
652 653
653 654 @propertycache
654 655 def _user(self):
655 656 return self._repo.ui.username()
656 657
657 658 @propertycache
658 659 def _date(self):
659 660 return util.makedate()
660 661
661 662 @propertycache
662 663 def _parents(self):
663 664 p = self._repo.dirstate.parents()
664 665 if p[1] == nullid:
665 666 p = p[:-1]
666 667 self._parents = [changectx(self._repo, x) for x in p]
667 668 return self._parents
668 669
669 670 def status(self, ignored=False, clean=False, unknown=False):
670 671 """Explicit status query
671 672 Unless this method is used to query the working copy status, the
672 673 _status property will implicitly read the status using its default
673 674 arguments."""
674 675 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
675 676 self._unknown = self._ignored = self._clean = None
676 677 if unknown:
677 678 self._unknown = stat[4]
678 679 if ignored:
679 680 self._ignored = stat[5]
680 681 if clean:
681 682 self._clean = stat[6]
682 683 self._status = stat[:4]
683 684 return stat
684 685
685 686 def manifest(self):
686 687 return self._manifest
687 688 def user(self):
688 689 return self._user or self._repo.ui.username()
689 690 def date(self):
690 691 return self._date
691 692 def description(self):
692 693 return self._text
693 694 def files(self):
694 695 return sorted(self._status[0] + self._status[1] + self._status[2])
695 696
696 697 def modified(self):
697 698 return self._status[0]
698 699 def added(self):
699 700 return self._status[1]
700 701 def removed(self):
701 702 return self._status[2]
702 703 def deleted(self):
703 704 return self._status[3]
704 705 def unknown(self):
705 706 assert self._unknown is not None # must call status first
706 707 return self._unknown
707 708 def ignored(self):
708 709 assert self._ignored is not None # must call status first
709 710 return self._ignored
710 711 def clean(self):
711 712 assert self._clean is not None # must call status first
712 713 return self._clean
713 714 def branch(self):
714 715 return self._extra['branch']
715 716 def extra(self):
716 717 return self._extra
717 718
718 719 def tags(self):
719 720 t = []
720 721 [t.extend(p.tags()) for p in self.parents()]
721 722 return t
722 723
723 724 def children(self):
724 725 return []
725 726
726 727 def flags(self, path):
727 728 if '_manifest' in self.__dict__:
728 729 try:
729 730 return self._manifest.flags(path)
730 731 except KeyError:
731 732 return ''
732 733
733 734 orig = self._repo.dirstate.copies().get(path, path)
734 735
735 736 def findflag(ctx):
736 737 mnode = ctx.changeset()[0]
737 738 node, flag = self._repo.manifest.find(mnode, orig)
738 739 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
739 740 try:
740 741 return ff(path)
741 742 except OSError:
742 743 pass
743 744
744 745 flag = findflag(self._parents[0])
745 746 if flag is None and len(self.parents()) > 1:
746 747 flag = findflag(self._parents[1])
747 748 if flag is None or self._repo.dirstate[path] == 'r':
748 749 return ''
749 750 return flag
750 751
751 752 def filectx(self, path, filelog=None):
752 753 """get a file context from the working directory"""
753 754 return workingfilectx(self._repo, path, workingctx=self,
754 755 filelog=filelog)
755 756
756 757 def ancestor(self, c2):
757 758 """return the ancestor context of self and c2"""
758 759 return self._parents[0].ancestor(c2) # punt on two parents for now
759 760
760 761 def walk(self, match):
761 762 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
762 763 True, False))
763 764
764 765 def dirty(self, missing=False):
765 766 "check whether a working directory is modified"
766 767 # check subrepos first
767 768 for s in self.substate:
768 769 if self.sub(s).dirty():
769 770 return True
770 771 # check current working dir
771 772 return (self.p2() or self.branch() != self.p1().branch() or
772 773 self.modified() or self.added() or self.removed() or
773 774 (missing and self.deleted()))
774 775
775 776 def add(self, list, prefix=""):
776 777 join = lambda f: os.path.join(prefix, f)
777 778 wlock = self._repo.wlock()
778 779 ui, ds = self._repo.ui, self._repo.dirstate
779 780 try:
780 781 rejected = []
781 782 for f in list:
782 783 p = self._repo.wjoin(f)
783 784 try:
784 785 st = os.lstat(p)
785 786 except:
786 787 ui.warn(_("%s does not exist!\n") % join(f))
787 788 rejected.append(f)
788 789 continue
789 790 if st.st_size > 10000000:
790 791 ui.warn(_("%s: up to %d MB of RAM may be required "
791 792 "to manage this file\n"
792 793 "(use 'hg revert %s' to cancel the "
793 794 "pending addition)\n")
794 795 % (f, 3 * st.st_size // 1000000, join(f)))
795 796 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
796 797 ui.warn(_("%s not added: only files and symlinks "
797 798 "supported currently\n") % join(f))
798 799 rejected.append(p)
799 800 elif ds[f] in 'amn':
800 801 ui.warn(_("%s already tracked!\n") % join(f))
801 802 elif ds[f] == 'r':
802 803 ds.normallookup(f)
803 804 else:
804 805 ds.add(f)
805 806 return rejected
806 807 finally:
807 808 wlock.release()
808 809
809 810 def forget(self, list):
810 811 wlock = self._repo.wlock()
811 812 try:
812 813 for f in list:
813 814 if self._repo.dirstate[f] != 'a':
814 815 self._repo.ui.warn(_("%s not added!\n") % f)
815 816 else:
816 817 self._repo.dirstate.forget(f)
817 818 finally:
818 819 wlock.release()
819 820
820 821 def remove(self, list, unlink=False):
821 822 if unlink:
822 823 for f in list:
823 824 try:
824 825 util.unlink(self._repo.wjoin(f))
825 826 except OSError, inst:
826 827 if inst.errno != errno.ENOENT:
827 828 raise
828 829 wlock = self._repo.wlock()
829 830 try:
830 831 for f in list:
831 832 if unlink and os.path.lexists(self._repo.wjoin(f)):
832 833 self._repo.ui.warn(_("%s still exists!\n") % f)
833 834 elif self._repo.dirstate[f] == 'a':
834 835 self._repo.dirstate.forget(f)
835 836 elif f not in self._repo.dirstate:
836 837 self._repo.ui.warn(_("%s not tracked!\n") % f)
837 838 else:
838 839 self._repo.dirstate.remove(f)
839 840 finally:
840 841 wlock.release()
841 842
842 843 def undelete(self, list):
843 844 pctxs = self.parents()
844 845 wlock = self._repo.wlock()
845 846 try:
846 847 for f in list:
847 848 if self._repo.dirstate[f] != 'r':
848 849 self._repo.ui.warn(_("%s not removed!\n") % f)
849 850 else:
850 851 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
851 852 t = fctx.data()
852 853 self._repo.wwrite(f, t, fctx.flags())
853 854 self._repo.dirstate.normal(f)
854 855 finally:
855 856 wlock.release()
856 857
857 858 def copy(self, source, dest):
858 859 p = self._repo.wjoin(dest)
859 860 if not os.path.lexists(p):
860 861 self._repo.ui.warn(_("%s does not exist!\n") % dest)
861 862 elif not (os.path.isfile(p) or os.path.islink(p)):
862 863 self._repo.ui.warn(_("copy failed: %s is not a file or a "
863 864 "symbolic link\n") % dest)
864 865 else:
865 866 wlock = self._repo.wlock()
866 867 try:
867 868 if self._repo.dirstate[dest] in '?r':
868 869 self._repo.dirstate.add(dest)
869 870 self._repo.dirstate.copy(source, dest)
870 871 finally:
871 872 wlock.release()
872 873
873 874 class workingfilectx(filectx):
874 875 """A workingfilectx object makes access to data related to a particular
875 876 file in the working directory convenient."""
876 877 def __init__(self, repo, path, filelog=None, workingctx=None):
877 878 """changeid can be a changeset revision, node, or tag.
878 879 fileid can be a file revision or node."""
879 880 self._repo = repo
880 881 self._path = path
881 882 self._changeid = None
882 883 self._filerev = self._filenode = None
883 884
884 885 if filelog:
885 886 self._filelog = filelog
886 887 if workingctx:
887 888 self._changectx = workingctx
888 889
889 890 @propertycache
890 891 def _changectx(self):
891 892 return workingctx(self._repo)
892 893
893 894 def __nonzero__(self):
894 895 return True
895 896
896 897 def __str__(self):
897 898 return "%s@%s" % (self.path(), self._changectx)
898 899
899 900 def data(self):
900 901 return self._repo.wread(self._path)
901 902 def renamed(self):
902 903 rp = self._repo.dirstate.copied(self._path)
903 904 if not rp:
904 905 return None
905 906 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
906 907
907 908 def parents(self):
908 909 '''return parent filectxs, following copies if necessary'''
909 910 def filenode(ctx, path):
910 911 return ctx._manifest.get(path, nullid)
911 912
912 913 path = self._path
913 914 fl = self._filelog
914 915 pcl = self._changectx._parents
915 916 renamed = self.renamed()
916 917
917 918 if renamed:
918 919 pl = [renamed + (None,)]
919 920 else:
920 921 pl = [(path, filenode(pcl[0], path), fl)]
921 922
922 923 for pc in pcl[1:]:
923 924 pl.append((path, filenode(pc, path), fl))
924 925
925 926 return [filectx(self._repo, p, fileid=n, filelog=l)
926 927 for p, n, l in pl if n != nullid]
927 928
928 929 def children(self):
929 930 return []
930 931
931 932 def size(self):
932 933 return os.lstat(self._repo.wjoin(self._path)).st_size
933 934 def date(self):
934 935 t, tz = self._changectx.date()
935 936 try:
936 937 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
937 938 except OSError, err:
938 939 if err.errno != errno.ENOENT:
939 940 raise
940 941 return (t, tz)
941 942
942 943 def cmp(self, fctx):
943 944 """compare with other file context
944 945
945 946 returns True if different than fctx.
946 947 """
947 948 # fctx should be a filectx (not a wfctx)
948 949 # invert comparison to reuse the same code path
949 950 return fctx.cmp(self)
950 951
951 952 class memctx(object):
952 953 """Use memctx to perform in-memory commits via localrepo.commitctx().
953 954
954 955 Revision information is supplied at initialization time while
955 956 related files data and is made available through a callback
956 957 mechanism. 'repo' is the current localrepo, 'parents' is a
957 958 sequence of two parent revisions identifiers (pass None for every
958 959 missing parent), 'text' is the commit message and 'files' lists
959 960 names of files touched by the revision (normalized and relative to
960 961 repository root).
961 962
962 963 filectxfn(repo, memctx, path) is a callable receiving the
963 964 repository, the current memctx object and the normalized path of
964 965 requested file, relative to repository root. It is fired by the
965 966 commit function for every file in 'files', but calls order is
966 967 undefined. If the file is available in the revision being
967 968 committed (updated or added), filectxfn returns a memfilectx
968 969 object. If the file was removed, filectxfn raises an
969 970 IOError. Moved files are represented by marking the source file
970 971 removed and the new file added with copy information (see
971 972 memfilectx).
972 973
973 974 user receives the committer name and defaults to current
974 975 repository username, date is the commit date in any format
975 976 supported by util.parsedate() and defaults to current date, extra
976 977 is a dictionary of metadata or is left empty.
977 978 """
978 979 def __init__(self, repo, parents, text, files, filectxfn, user=None,
979 980 date=None, extra=None):
980 981 self._repo = repo
981 982 self._rev = None
982 983 self._node = None
983 984 self._text = text
984 985 self._date = date and util.parsedate(date) or util.makedate()
985 986 self._user = user
986 987 parents = [(p or nullid) for p in parents]
987 988 p1, p2 = parents
988 989 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
989 990 files = sorted(set(files))
990 991 self._status = [files, [], [], [], []]
991 992 self._filectxfn = filectxfn
992 993
993 994 self._extra = extra and extra.copy() or {}
994 995 if 'branch' not in self._extra:
995 996 self._extra['branch'] = 'default'
996 997 elif self._extra.get('branch') == '':
997 998 self._extra['branch'] = 'default'
998 999
999 1000 def __str__(self):
1000 1001 return str(self._parents[0]) + "+"
1001 1002
1002 1003 def __int__(self):
1003 1004 return self._rev
1004 1005
1005 1006 def __nonzero__(self):
1006 1007 return True
1007 1008
1008 1009 def __getitem__(self, key):
1009 1010 return self.filectx(key)
1010 1011
1011 1012 def p1(self):
1012 1013 return self._parents[0]
1013 1014 def p2(self):
1014 1015 return self._parents[1]
1015 1016
1016 1017 def user(self):
1017 1018 return self._user or self._repo.ui.username()
1018 1019 def date(self):
1019 1020 return self._date
1020 1021 def description(self):
1021 1022 return self._text
1022 1023 def files(self):
1023 1024 return self.modified()
1024 1025 def modified(self):
1025 1026 return self._status[0]
1026 1027 def added(self):
1027 1028 return self._status[1]
1028 1029 def removed(self):
1029 1030 return self._status[2]
1030 1031 def deleted(self):
1031 1032 return self._status[3]
1032 1033 def unknown(self):
1033 1034 return self._status[4]
1034 1035 def ignored(self):
1035 1036 return self._status[5]
1036 1037 def clean(self):
1037 1038 return self._status[6]
1038 1039 def branch(self):
1039 1040 return self._extra['branch']
1040 1041 def extra(self):
1041 1042 return self._extra
1042 1043 def flags(self, f):
1043 1044 return self[f].flags()
1044 1045
1045 1046 def parents(self):
1046 1047 """return contexts for each parent changeset"""
1047 1048 return self._parents
1048 1049
1049 1050 def filectx(self, path, filelog=None):
1050 1051 """get a file context from the working directory"""
1051 1052 return self._filectxfn(self._repo, self, path)
1052 1053
1053 1054 def commit(self):
1054 1055 """commit context to the repo"""
1055 1056 return self._repo.commitctx(self)
1056 1057
1057 1058 class memfilectx(object):
1058 1059 """memfilectx represents an in-memory file to commit.
1059 1060
1060 1061 See memctx for more details.
1061 1062 """
1062 1063 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1063 1064 """
1064 1065 path is the normalized file path relative to repository root.
1065 1066 data is the file content as a string.
1066 1067 islink is True if the file is a symbolic link.
1067 1068 isexec is True if the file is executable.
1068 1069 copied is the source file path if current file was copied in the
1069 1070 revision being committed, or None."""
1070 1071 self._path = path
1071 1072 self._data = data
1072 1073 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1073 1074 self._copied = None
1074 1075 if copied:
1075 1076 self._copied = (copied, nullid)
1076 1077
1077 1078 def __nonzero__(self):
1078 1079 return True
1079 1080 def __str__(self):
1080 1081 return "%s@%s" % (self.path(), self._changectx)
1081 1082 def path(self):
1082 1083 return self._path
1083 1084 def data(self):
1084 1085 return self._data
1085 1086 def flags(self):
1086 1087 return self._flags
1087 1088 def isexec(self):
1088 1089 return 'x' in self._flags
1089 1090 def islink(self):
1090 1091 return 'l' in self._flags
1091 1092 def renamed(self):
1092 1093 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now