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