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