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