##// END OF EJS Templates
workingctx: overload bookmarks() to return parents' bookmarks...
Kevin Bullock -
r13476:b85a09f3 stable
parent child Browse files
Show More
@@ -1,1110 +1,1116
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 553 seen = set()
554 554 visit = [self]
555 555 while visit:
556 556 parents = visit.pop(0).parents()
557 557 if len(parents) > 1 and parents[1].rev() > parents[0].rev():
558 558 # make sure we return ancestors in reverse revision order
559 559 parents = reversed(parents)
560 560 for parent in parents:
561 561 s = str(parent)
562 562 if s not in seen:
563 563 visit.append(parent)
564 564 seen.add(s)
565 565 yield parent
566 566
567 567 class workingctx(changectx):
568 568 """A workingctx object makes access to data related to
569 569 the current working directory convenient.
570 570 date - any valid date string or (unixtime, offset), or None.
571 571 user - username string, or None.
572 572 extra - a dictionary of extra values, or None.
573 573 changes - a list of file lists as returned by localrepo.status()
574 574 or None to use the repository status.
575 575 """
576 576 def __init__(self, repo, text="", user=None, date=None, extra=None,
577 577 changes=None):
578 578 self._repo = repo
579 579 self._rev = None
580 580 self._node = None
581 581 self._text = text
582 582 if date:
583 583 self._date = util.parsedate(date)
584 584 if user:
585 585 self._user = user
586 586 if changes:
587 587 self._status = list(changes[:4])
588 588 self._unknown = changes[4]
589 589 self._ignored = changes[5]
590 590 self._clean = changes[6]
591 591 else:
592 592 self._unknown = None
593 593 self._ignored = None
594 594 self._clean = None
595 595
596 596 self._extra = {}
597 597 if extra:
598 598 self._extra = extra.copy()
599 599 if 'branch' not in self._extra:
600 600 try:
601 601 branch = encoding.fromlocal(self._repo.dirstate.branch())
602 602 except UnicodeDecodeError:
603 603 raise util.Abort(_('branch name not in UTF-8!'))
604 604 self._extra['branch'] = branch
605 605 if self._extra['branch'] == '':
606 606 self._extra['branch'] = 'default'
607 607
608 608 def __str__(self):
609 609 return str(self._parents[0]) + "+"
610 610
611 611 def __repr__(self):
612 612 return "<workingctx %s>" % str(self)
613 613
614 614 def __nonzero__(self):
615 615 return True
616 616
617 617 def __contains__(self, key):
618 618 return self._repo.dirstate[key] not in "?r"
619 619
620 620 @propertycache
621 621 def _manifest(self):
622 622 """generate a manifest corresponding to the working directory"""
623 623
624 624 if self._unknown is None:
625 625 self.status(unknown=True)
626 626
627 627 man = self._parents[0].manifest().copy()
628 628 copied = self._repo.dirstate.copies()
629 629 if len(self._parents) > 1:
630 630 man2 = self.p2().manifest()
631 631 def getman(f):
632 632 if f in man:
633 633 return man
634 634 return man2
635 635 else:
636 636 getman = lambda f: man
637 637 def cf(f):
638 638 f = copied.get(f, f)
639 639 return getman(f).flags(f)
640 640 ff = self._repo.dirstate.flagfunc(cf)
641 641 modified, added, removed, deleted = self._status
642 642 unknown = self._unknown
643 643 for i, l in (("a", added), ("m", modified), ("u", unknown)):
644 644 for f in l:
645 645 orig = copied.get(f, f)
646 646 man[f] = getman(orig).get(orig, nullid) + i
647 647 try:
648 648 man.set(f, ff(f))
649 649 except OSError:
650 650 pass
651 651
652 652 for f in deleted + removed:
653 653 if f in man:
654 654 del man[f]
655 655
656 656 return man
657 657
658 658 @propertycache
659 659 def _status(self):
660 660 return self._repo.status()[:4]
661 661
662 662 @propertycache
663 663 def _user(self):
664 664 return self._repo.ui.username()
665 665
666 666 @propertycache
667 667 def _date(self):
668 668 return util.makedate()
669 669
670 670 @propertycache
671 671 def _parents(self):
672 672 p = self._repo.dirstate.parents()
673 673 if p[1] == nullid:
674 674 p = p[:-1]
675 675 self._parents = [changectx(self._repo, x) for x in p]
676 676 return self._parents
677 677
678 678 def status(self, ignored=False, clean=False, unknown=False):
679 679 """Explicit status query
680 680 Unless this method is used to query the working copy status, the
681 681 _status property will implicitly read the status using its default
682 682 arguments."""
683 683 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
684 684 self._unknown = self._ignored = self._clean = None
685 685 if unknown:
686 686 self._unknown = stat[4]
687 687 if ignored:
688 688 self._ignored = stat[5]
689 689 if clean:
690 690 self._clean = stat[6]
691 691 self._status = stat[:4]
692 692 return stat
693 693
694 694 def manifest(self):
695 695 return self._manifest
696 696 def user(self):
697 697 return self._user or self._repo.ui.username()
698 698 def date(self):
699 699 return self._date
700 700 def description(self):
701 701 return self._text
702 702 def files(self):
703 703 return sorted(self._status[0] + self._status[1] + self._status[2])
704 704
705 705 def modified(self):
706 706 return self._status[0]
707 707 def added(self):
708 708 return self._status[1]
709 709 def removed(self):
710 710 return self._status[2]
711 711 def deleted(self):
712 712 return self._status[3]
713 713 def unknown(self):
714 714 assert self._unknown is not None # must call status first
715 715 return self._unknown
716 716 def ignored(self):
717 717 assert self._ignored is not None # must call status first
718 718 return self._ignored
719 719 def clean(self):
720 720 assert self._clean is not None # must call status first
721 721 return self._clean
722 722 def branch(self):
723 723 return encoding.tolocal(self._extra['branch'])
724 724 def extra(self):
725 725 return self._extra
726 726
727 727 def tags(self):
728 728 t = []
729 729 for p in self.parents():
730 730 t.extend(p.tags())
731 731 return t
732 732
733 def bookmarks(self):
734 b = []
735 for p in self.parents():
736 b.extend(p.bookmarks())
737 return b
738
733 739 def children(self):
734 740 return []
735 741
736 742 def flags(self, path):
737 743 if '_manifest' in self.__dict__:
738 744 try:
739 745 return self._manifest.flags(path)
740 746 except KeyError:
741 747 return ''
742 748
743 749 orig = self._repo.dirstate.copies().get(path, path)
744 750
745 751 def findflag(ctx):
746 752 mnode = ctx.changeset()[0]
747 753 node, flag = self._repo.manifest.find(mnode, orig)
748 754 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
749 755 try:
750 756 return ff(path)
751 757 except OSError:
752 758 pass
753 759
754 760 flag = findflag(self._parents[0])
755 761 if flag is None and len(self.parents()) > 1:
756 762 flag = findflag(self._parents[1])
757 763 if flag is None or self._repo.dirstate[path] == 'r':
758 764 return ''
759 765 return flag
760 766
761 767 def filectx(self, path, filelog=None):
762 768 """get a file context from the working directory"""
763 769 return workingfilectx(self._repo, path, workingctx=self,
764 770 filelog=filelog)
765 771
766 772 def ancestor(self, c2):
767 773 """return the ancestor context of self and c2"""
768 774 return self._parents[0].ancestor(c2) # punt on two parents for now
769 775
770 776 def walk(self, match):
771 777 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
772 778 True, False))
773 779
774 780 def dirty(self, missing=False):
775 781 "check whether a working directory is modified"
776 782 # check subrepos first
777 783 for s in self.substate:
778 784 if self.sub(s).dirty():
779 785 return True
780 786 # check current working dir
781 787 return (self.p2() or self.branch() != self.p1().branch() or
782 788 self.modified() or self.added() or self.removed() or
783 789 (missing and self.deleted()))
784 790
785 791 def add(self, list, prefix=""):
786 792 join = lambda f: os.path.join(prefix, f)
787 793 wlock = self._repo.wlock()
788 794 ui, ds = self._repo.ui, self._repo.dirstate
789 795 try:
790 796 rejected = []
791 797 for f in list:
792 798 p = self._repo.wjoin(f)
793 799 try:
794 800 st = os.lstat(p)
795 801 except:
796 802 ui.warn(_("%s does not exist!\n") % join(f))
797 803 rejected.append(f)
798 804 continue
799 805 if st.st_size > 10000000:
800 806 ui.warn(_("%s: up to %d MB of RAM may be required "
801 807 "to manage this file\n"
802 808 "(use 'hg revert %s' to cancel the "
803 809 "pending addition)\n")
804 810 % (f, 3 * st.st_size // 1000000, join(f)))
805 811 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
806 812 ui.warn(_("%s not added: only files and symlinks "
807 813 "supported currently\n") % join(f))
808 814 rejected.append(p)
809 815 elif ds[f] in 'amn':
810 816 ui.warn(_("%s already tracked!\n") % join(f))
811 817 elif ds[f] == 'r':
812 818 ds.normallookup(f)
813 819 else:
814 820 ds.add(f)
815 821 return rejected
816 822 finally:
817 823 wlock.release()
818 824
819 825 def forget(self, list):
820 826 wlock = self._repo.wlock()
821 827 try:
822 828 for f in list:
823 829 if self._repo.dirstate[f] != 'a':
824 830 self._repo.ui.warn(_("%s not added!\n") % f)
825 831 else:
826 832 self._repo.dirstate.forget(f)
827 833 finally:
828 834 wlock.release()
829 835
830 836 def ancestors(self):
831 837 for a in self._repo.changelog.ancestors(
832 838 *[p.rev() for p in self._parents]):
833 839 yield changectx(self._repo, a)
834 840
835 841 def remove(self, list, unlink=False):
836 842 if unlink:
837 843 for f in list:
838 844 try:
839 845 util.unlinkpath(self._repo.wjoin(f))
840 846 except OSError, inst:
841 847 if inst.errno != errno.ENOENT:
842 848 raise
843 849 wlock = self._repo.wlock()
844 850 try:
845 851 for f in list:
846 852 if unlink and os.path.lexists(self._repo.wjoin(f)):
847 853 self._repo.ui.warn(_("%s still exists!\n") % f)
848 854 elif self._repo.dirstate[f] == 'a':
849 855 self._repo.dirstate.forget(f)
850 856 elif f not in self._repo.dirstate:
851 857 self._repo.ui.warn(_("%s not tracked!\n") % f)
852 858 else:
853 859 self._repo.dirstate.remove(f)
854 860 finally:
855 861 wlock.release()
856 862
857 863 def undelete(self, list):
858 864 pctxs = self.parents()
859 865 wlock = self._repo.wlock()
860 866 try:
861 867 for f in list:
862 868 if self._repo.dirstate[f] != 'r':
863 869 self._repo.ui.warn(_("%s not removed!\n") % f)
864 870 else:
865 871 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
866 872 t = fctx.data()
867 873 self._repo.wwrite(f, t, fctx.flags())
868 874 self._repo.dirstate.normal(f)
869 875 finally:
870 876 wlock.release()
871 877
872 878 def copy(self, source, dest):
873 879 p = self._repo.wjoin(dest)
874 880 if not os.path.lexists(p):
875 881 self._repo.ui.warn(_("%s does not exist!\n") % dest)
876 882 elif not (os.path.isfile(p) or os.path.islink(p)):
877 883 self._repo.ui.warn(_("copy failed: %s is not a file or a "
878 884 "symbolic link\n") % dest)
879 885 else:
880 886 wlock = self._repo.wlock()
881 887 try:
882 888 if self._repo.dirstate[dest] in '?r':
883 889 self._repo.dirstate.add(dest)
884 890 self._repo.dirstate.copy(source, dest)
885 891 finally:
886 892 wlock.release()
887 893
888 894 class workingfilectx(filectx):
889 895 """A workingfilectx object makes access to data related to a particular
890 896 file in the working directory convenient."""
891 897 def __init__(self, repo, path, filelog=None, workingctx=None):
892 898 """changeid can be a changeset revision, node, or tag.
893 899 fileid can be a file revision or node."""
894 900 self._repo = repo
895 901 self._path = path
896 902 self._changeid = None
897 903 self._filerev = self._filenode = None
898 904
899 905 if filelog:
900 906 self._filelog = filelog
901 907 if workingctx:
902 908 self._changectx = workingctx
903 909
904 910 @propertycache
905 911 def _changectx(self):
906 912 return workingctx(self._repo)
907 913
908 914 def __nonzero__(self):
909 915 return True
910 916
911 917 def __str__(self):
912 918 return "%s@%s" % (self.path(), self._changectx)
913 919
914 920 def __repr__(self):
915 921 return "<workingfilectx %s>" % str(self)
916 922
917 923 def data(self):
918 924 return self._repo.wread(self._path)
919 925 def renamed(self):
920 926 rp = self._repo.dirstate.copied(self._path)
921 927 if not rp:
922 928 return None
923 929 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
924 930
925 931 def parents(self):
926 932 '''return parent filectxs, following copies if necessary'''
927 933 def filenode(ctx, path):
928 934 return ctx._manifest.get(path, nullid)
929 935
930 936 path = self._path
931 937 fl = self._filelog
932 938 pcl = self._changectx._parents
933 939 renamed = self.renamed()
934 940
935 941 if renamed:
936 942 pl = [renamed + (None,)]
937 943 else:
938 944 pl = [(path, filenode(pcl[0], path), fl)]
939 945
940 946 for pc in pcl[1:]:
941 947 pl.append((path, filenode(pc, path), fl))
942 948
943 949 return [filectx(self._repo, p, fileid=n, filelog=l)
944 950 for p, n, l in pl if n != nullid]
945 951
946 952 def children(self):
947 953 return []
948 954
949 955 def size(self):
950 956 return os.lstat(self._repo.wjoin(self._path)).st_size
951 957 def date(self):
952 958 t, tz = self._changectx.date()
953 959 try:
954 960 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
955 961 except OSError, err:
956 962 if err.errno != errno.ENOENT:
957 963 raise
958 964 return (t, tz)
959 965
960 966 def cmp(self, fctx):
961 967 """compare with other file context
962 968
963 969 returns True if different than fctx.
964 970 """
965 971 # fctx should be a filectx (not a wfctx)
966 972 # invert comparison to reuse the same code path
967 973 return fctx.cmp(self)
968 974
969 975 class memctx(object):
970 976 """Use memctx to perform in-memory commits via localrepo.commitctx().
971 977
972 978 Revision information is supplied at initialization time while
973 979 related files data and is made available through a callback
974 980 mechanism. 'repo' is the current localrepo, 'parents' is a
975 981 sequence of two parent revisions identifiers (pass None for every
976 982 missing parent), 'text' is the commit message and 'files' lists
977 983 names of files touched by the revision (normalized and relative to
978 984 repository root).
979 985
980 986 filectxfn(repo, memctx, path) is a callable receiving the
981 987 repository, the current memctx object and the normalized path of
982 988 requested file, relative to repository root. It is fired by the
983 989 commit function for every file in 'files', but calls order is
984 990 undefined. If the file is available in the revision being
985 991 committed (updated or added), filectxfn returns a memfilectx
986 992 object. If the file was removed, filectxfn raises an
987 993 IOError. Moved files are represented by marking the source file
988 994 removed and the new file added with copy information (see
989 995 memfilectx).
990 996
991 997 user receives the committer name and defaults to current
992 998 repository username, date is the commit date in any format
993 999 supported by util.parsedate() and defaults to current date, extra
994 1000 is a dictionary of metadata or is left empty.
995 1001 """
996 1002 def __init__(self, repo, parents, text, files, filectxfn, user=None,
997 1003 date=None, extra=None):
998 1004 self._repo = repo
999 1005 self._rev = None
1000 1006 self._node = None
1001 1007 self._text = text
1002 1008 self._date = date and util.parsedate(date) or util.makedate()
1003 1009 self._user = user
1004 1010 parents = [(p or nullid) for p in parents]
1005 1011 p1, p2 = parents
1006 1012 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1007 1013 files = sorted(set(files))
1008 1014 self._status = [files, [], [], [], []]
1009 1015 self._filectxfn = filectxfn
1010 1016
1011 1017 self._extra = extra and extra.copy() or {}
1012 1018 if 'branch' not in self._extra:
1013 1019 self._extra['branch'] = 'default'
1014 1020 elif self._extra.get('branch') == '':
1015 1021 self._extra['branch'] = 'default'
1016 1022
1017 1023 def __str__(self):
1018 1024 return str(self._parents[0]) + "+"
1019 1025
1020 1026 def __int__(self):
1021 1027 return self._rev
1022 1028
1023 1029 def __nonzero__(self):
1024 1030 return True
1025 1031
1026 1032 def __getitem__(self, key):
1027 1033 return self.filectx(key)
1028 1034
1029 1035 def p1(self):
1030 1036 return self._parents[0]
1031 1037 def p2(self):
1032 1038 return self._parents[1]
1033 1039
1034 1040 def user(self):
1035 1041 return self._user or self._repo.ui.username()
1036 1042 def date(self):
1037 1043 return self._date
1038 1044 def description(self):
1039 1045 return self._text
1040 1046 def files(self):
1041 1047 return self.modified()
1042 1048 def modified(self):
1043 1049 return self._status[0]
1044 1050 def added(self):
1045 1051 return self._status[1]
1046 1052 def removed(self):
1047 1053 return self._status[2]
1048 1054 def deleted(self):
1049 1055 return self._status[3]
1050 1056 def unknown(self):
1051 1057 return self._status[4]
1052 1058 def ignored(self):
1053 1059 return self._status[5]
1054 1060 def clean(self):
1055 1061 return self._status[6]
1056 1062 def branch(self):
1057 1063 return encoding.tolocal(self._extra['branch'])
1058 1064 def extra(self):
1059 1065 return self._extra
1060 1066 def flags(self, f):
1061 1067 return self[f].flags()
1062 1068
1063 1069 def parents(self):
1064 1070 """return contexts for each parent changeset"""
1065 1071 return self._parents
1066 1072
1067 1073 def filectx(self, path, filelog=None):
1068 1074 """get a file context from the working directory"""
1069 1075 return self._filectxfn(self._repo, self, path)
1070 1076
1071 1077 def commit(self):
1072 1078 """commit context to the repo"""
1073 1079 return self._repo.commitctx(self)
1074 1080
1075 1081 class memfilectx(object):
1076 1082 """memfilectx represents an in-memory file to commit.
1077 1083
1078 1084 See memctx for more details.
1079 1085 """
1080 1086 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1081 1087 """
1082 1088 path is the normalized file path relative to repository root.
1083 1089 data is the file content as a string.
1084 1090 islink is True if the file is a symbolic link.
1085 1091 isexec is True if the file is executable.
1086 1092 copied is the source file path if current file was copied in the
1087 1093 revision being committed, or None."""
1088 1094 self._path = path
1089 1095 self._data = data
1090 1096 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1091 1097 self._copied = None
1092 1098 if copied:
1093 1099 self._copied = (copied, nullid)
1094 1100
1095 1101 def __nonzero__(self):
1096 1102 return True
1097 1103 def __str__(self):
1098 1104 return "%s@%s" % (self.path(), self._changectx)
1099 1105 def path(self):
1100 1106 return self._path
1101 1107 def data(self):
1102 1108 return self._data
1103 1109 def flags(self):
1104 1110 return self._flags
1105 1111 def isexec(self):
1106 1112 return 'x' in self._flags
1107 1113 def islink(self):
1108 1114 return 'l' in self._flags
1109 1115 def renamed(self):
1110 1116 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now