##// END OF EJS Templates
context: use descriptors to speed up lazy attributes
Dirkjan Ochtman -
r7368:595ba253 default
parent child Browse files
Show More
@@ -1,799 +1,806 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import nullid, nullrev, short, hex
9 9 from i18n import _
10 10 import ancestor, bdiff, revlog, util, os, errno
11 11
12 class propertycache(object):
13 def __init__(self, func):
14 self.func = func
15 self.name = func.__name__
16 def __get__(self, obj, type=None):
17 result = self.func(obj)
18 setattr(obj, self.name, result)
19 return result
20
12 21 class changectx(object):
13 22 """A changecontext object makes access to data related to a particular
14 23 changeset convenient."""
15 24 def __init__(self, repo, changeid=''):
16 25 """changeid is a revision number, node, or tag"""
17 26 if changeid == '':
18 27 changeid = '.'
19 28 self._repo = repo
20 29 if isinstance(changeid, (long, int)):
21 30 self._rev = changeid
22 31 self._node = self._repo.changelog.node(changeid)
23 32 else:
24 33 self._node = self._repo.lookup(changeid)
25 34 self._rev = self._repo.changelog.rev(self._node)
26 35
27 36 def __str__(self):
28 37 return short(self.node())
29 38
30 39 def __int__(self):
31 40 return self.rev()
32 41
33 42 def __repr__(self):
34 43 return "<changectx %s>" % str(self)
35 44
36 45 def __hash__(self):
37 46 try:
38 47 return hash(self._rev)
39 48 except AttributeError:
40 49 return id(self)
41 50
42 51 def __eq__(self, other):
43 52 try:
44 53 return self._rev == other._rev
45 54 except AttributeError:
46 55 return False
47 56
48 57 def __ne__(self, other):
49 58 return not (self == other)
50 59
51 60 def __nonzero__(self):
52 61 return self._rev != nullrev
53 62
54 def __getattr__(self, name):
55 if name == '_changeset':
56 self._changeset = self._repo.changelog.read(self.node())
57 return self._changeset
58 elif name == '_manifest':
59 self._manifest = self._repo.manifest.read(self._changeset[0])
60 return self._manifest
61 elif name == '_manifestdelta':
62 md = self._repo.manifest.readdelta(self._changeset[0])
63 self._manifestdelta = md
64 return self._manifestdelta
65 elif name == '_parents':
66 p = self._repo.changelog.parentrevs(self._rev)
67 if p[1] == nullrev:
68 p = p[:-1]
69 self._parents = [changectx(self._repo, x) for x in p]
70 return self._parents
71 else:
72 raise AttributeError(name)
63 def _changeset(self):
64 return self._repo.changelog.read(self.node())
65 _changeset = propertycache(_changeset)
66
67 def _manifest(self):
68 return self._repo.manifest.read(self._changeset[0])
69 _manifest = propertycache(_manifest)
70
71 def _manifestdelta(self):
72 return self._repo.manifest.readdelta(self._changeset[0])
73 _manifestdelta = propertycache(_manifestdelta)
74
75 def _parents(self):
76 p = self._repo.changelog.parentrevs(self._rev)
77 if p[1] == nullrev:
78 p = p[:-1]
79 return [changectx(self._repo, x) for x in p]
80 _parents = propertycache(_parents)
73 81
74 82 def __contains__(self, key):
75 83 return key in self._manifest
76 84
77 85 def __getitem__(self, key):
78 86 return self.filectx(key)
79 87
80 88 def __iter__(self):
81 89 for f in util.sort(self._manifest):
82 90 yield f
83 91
84 92 def changeset(self): return self._changeset
85 93 def manifest(self): return self._manifest
86 94
87 95 def rev(self): return self._rev
88 96 def node(self): return self._node
89 97 def hex(self): return hex(self._node)
90 98 def user(self): return self._changeset[1]
91 99 def date(self): return self._changeset[2]
92 100 def files(self): return self._changeset[3]
93 101 def description(self): return self._changeset[4]
94 102 def branch(self): return self._changeset[5].get("branch")
95 103 def extra(self): return self._changeset[5]
96 104 def tags(self): return self._repo.nodetags(self._node)
97 105
98 106 def parents(self):
99 107 """return contexts for each parent changeset"""
100 108 return self._parents
101 109
102 110 def children(self):
103 111 """return contexts for each child changeset"""
104 112 c = self._repo.changelog.children(self._node)
105 113 return [changectx(self._repo, x) for x in c]
106 114
107 115 def ancestors(self):
108 116 for a in self._repo.changelog.ancestors(self._rev):
109 117 yield changectx(self._repo, a)
110 118
111 119 def descendants(self):
112 120 for d in self._repo.changelog.descendants(self._rev):
113 121 yield changectx(self._repo, d)
114 122
115 123 def _fileinfo(self, path):
116 124 if '_manifest' in self.__dict__:
117 125 try:
118 126 return self._manifest[path], self._manifest.flags(path)
119 127 except KeyError:
120 128 raise revlog.LookupError(self._node, path,
121 129 _('not found in manifest'))
122 130 if '_manifestdelta' in self.__dict__ or path in self.files():
123 131 if path in self._manifestdelta:
124 132 return self._manifestdelta[path], self._manifestdelta.flags(path)
125 133 node, flag = self._repo.manifest.find(self._changeset[0], path)
126 134 if not node:
127 135 raise revlog.LookupError(self._node, path,
128 136 _('not found in manifest'))
129 137
130 138 return node, flag
131 139
132 140 def filenode(self, path):
133 141 return self._fileinfo(path)[0]
134 142
135 143 def flags(self, path):
136 144 try:
137 145 return self._fileinfo(path)[1]
138 146 except revlog.LookupError:
139 147 return ''
140 148
141 149 def filectx(self, path, fileid=None, filelog=None):
142 150 """get a file context from this changeset"""
143 151 if fileid is None:
144 152 fileid = self.filenode(path)
145 153 return filectx(self._repo, path, fileid=fileid,
146 154 changectx=self, filelog=filelog)
147 155
148 156 def ancestor(self, c2):
149 157 """
150 158 return the ancestor context of self and c2
151 159 """
152 160 n = self._repo.changelog.ancestor(self._node, c2._node)
153 161 return changectx(self._repo, n)
154 162
155 163 def walk(self, match):
156 164 fdict = dict.fromkeys(match.files())
157 165 # for dirstate.walk, files=['.'] means "walk the whole tree".
158 166 # follow that here, too
159 167 fdict.pop('.', None)
160 168 for fn in self:
161 169 for ffn in fdict:
162 170 # match if the file is the exact name or a directory
163 171 if ffn == fn or fn.startswith("%s/" % ffn):
164 172 del fdict[ffn]
165 173 break
166 174 if match(fn):
167 175 yield fn
168 176 for fn in util.sort(fdict):
169 177 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
170 178 yield fn
171 179
172 180 class filectx(object):
173 181 """A filecontext object makes access to data related to a particular
174 182 filerevision convenient."""
175 183 def __init__(self, repo, path, changeid=None, fileid=None,
176 184 filelog=None, changectx=None):
177 185 """changeid can be a changeset revision, node, or tag.
178 186 fileid can be a file revision or node."""
179 187 self._repo = repo
180 188 self._path = path
181 189
182 190 assert (changeid is not None
183 191 or fileid is not None
184 192 or changectx is not None)
185 193
186 194 if filelog:
187 195 self._filelog = filelog
188 196
189 197 if changeid is not None:
190 198 self._changeid = changeid
191 199 if changectx is not None:
192 200 self._changectx = changectx
193 201 if fileid is not None:
194 202 self._fileid = fileid
195 203
196 def __getattr__(self, name):
197 if name == '_changectx':
198 self._changectx = changectx(self._repo, self._changeid)
199 return self._changectx
200 elif name == '_filelog':
201 self._filelog = self._repo.file(self._path)
202 return self._filelog
203 elif name == '_changeid':
204 if '_changectx' in self.__dict__:
205 self._changeid = self._changectx.rev()
206 else:
207 self._changeid = self._filelog.linkrev(self._filerev)
208 return self._changeid
209 elif name == '_filenode':
210 if '_fileid' in self.__dict__:
211 self._filenode = self._filelog.lookup(self._fileid)
212 else:
213 self._filenode = self._changectx.filenode(self._path)
214 return self._filenode
215 elif name == '_filerev':
216 self._filerev = self._filelog.rev(self._filenode)
217 return self._filerev
218 elif name == '_repopath':
219 self._repopath = self._path
220 return self._repopath
204 def _changectx(self):
205 return changectx(self._repo, self._changeid)
206 _changectx = propertycache(_changectx)
207
208 def _filelog(self):
209 return self._repo.file(self._path)
210 _filelog = propertycache(_filelog)
211
212 def _changeid(self):
213 if '_changectx' in self.__dict__:
214 return self._changectx.rev()
221 215 else:
222 raise AttributeError(name)
216 return self._filelog.linkrev(self._filerev)
217 _changeid = propertycache(_changeid)
218
219 def _filenode(self):
220 if '_fileid' in self.__dict__:
221 return self._filelog.lookup(self._fileid)
222 else:
223 return self._changectx.filenode(self._path)
224 _filenode = propertycache(_filenode)
225
226 def _filerev(self):
227 return self._filelog.rev(self._filenode)
228 _filerev = propertycache(_filerev)
229
230 def _repopath(self):
231 return self._path
232 _repopath = propertycache(_repopath)
223 233
224 234 def __nonzero__(self):
225 235 try:
226 236 n = self._filenode
227 237 return True
228 238 except revlog.LookupError:
229 239 # file is missing
230 240 return False
231 241
232 242 def __str__(self):
233 243 return "%s@%s" % (self.path(), short(self.node()))
234 244
235 245 def __repr__(self):
236 246 return "<filectx %s>" % str(self)
237 247
238 248 def __hash__(self):
239 249 try:
240 250 return hash((self._path, self._fileid))
241 251 except AttributeError:
242 252 return id(self)
243 253
244 254 def __eq__(self, other):
245 255 try:
246 256 return (self._path == other._path
247 257 and self._fileid == other._fileid)
248 258 except AttributeError:
249 259 return False
250 260
251 261 def __ne__(self, other):
252 262 return not (self == other)
253 263
254 264 def filectx(self, fileid):
255 265 '''opens an arbitrary revision of the file without
256 266 opening a new filelog'''
257 267 return filectx(self._repo, self._path, fileid=fileid,
258 268 filelog=self._filelog)
259 269
260 270 def filerev(self): return self._filerev
261 271 def filenode(self): return self._filenode
262 272 def flags(self): return self._changectx.flags(self._path)
263 273 def filelog(self): return self._filelog
264 274
265 275 def rev(self):
266 276 if '_changectx' in self.__dict__:
267 277 return self._changectx.rev()
268 278 if '_changeid' in self.__dict__:
269 279 return self._changectx.rev()
270 280 return self._filelog.linkrev(self._filerev)
271 281
272 282 def linkrev(self): return self._filelog.linkrev(self._filerev)
273 283 def node(self): return self._changectx.node()
274 284 def user(self): return self._changectx.user()
275 285 def date(self): return self._changectx.date()
276 286 def files(self): return self._changectx.files()
277 287 def description(self): return self._changectx.description()
278 288 def branch(self): return self._changectx.branch()
279 289 def manifest(self): return self._changectx.manifest()
280 290 def changectx(self): return self._changectx
281 291
282 292 def data(self): return self._filelog.read(self._filenode)
283 293 def path(self): return self._path
284 294 def size(self): return self._filelog.size(self._filerev)
285 295
286 296 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
287 297
288 298 def renamed(self):
289 299 """check if file was actually renamed in this changeset revision
290 300
291 301 If rename logged in file revision, we report copy for changeset only
292 302 if file revisions linkrev points back to the changeset in question
293 303 or both changeset parents contain different file revisions.
294 304 """
295 305
296 306 renamed = self._filelog.renamed(self._filenode)
297 307 if not renamed:
298 308 return renamed
299 309
300 310 if self.rev() == self.linkrev():
301 311 return renamed
302 312
303 313 name = self.path()
304 314 fnode = self._filenode
305 315 for p in self._changectx.parents():
306 316 try:
307 317 if fnode == p.filenode(name):
308 318 return None
309 319 except revlog.LookupError:
310 320 pass
311 321 return renamed
312 322
313 323 def parents(self):
314 324 p = self._path
315 325 fl = self._filelog
316 326 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
317 327
318 328 r = self._filelog.renamed(self._filenode)
319 329 if r:
320 330 pl[0] = (r[0], r[1], None)
321 331
322 332 return [filectx(self._repo, p, fileid=n, filelog=l)
323 333 for p,n,l in pl if n != nullid]
324 334
325 335 def children(self):
326 336 # hard for renames
327 337 c = self._filelog.children(self._filenode)
328 338 return [filectx(self._repo, self._path, fileid=x,
329 339 filelog=self._filelog) for x in c]
330 340
331 341 def annotate(self, follow=False, linenumber=None):
332 342 '''returns a list of tuples of (ctx, line) for each line
333 343 in the file, where ctx is the filectx of the node where
334 344 that line was last changed.
335 345 This returns tuples of ((ctx, linenumber), line) for each line,
336 346 if "linenumber" parameter is NOT "None".
337 347 In such tuples, linenumber means one at the first appearance
338 348 in the managed file.
339 349 To reduce annotation cost,
340 350 this returns fixed value(False is used) as linenumber,
341 351 if "linenumber" parameter is "False".'''
342 352
343 353 def decorate_compat(text, rev):
344 354 return ([rev] * len(text.splitlines()), text)
345 355
346 356 def without_linenumber(text, rev):
347 357 return ([(rev, False)] * len(text.splitlines()), text)
348 358
349 359 def with_linenumber(text, rev):
350 360 size = len(text.splitlines())
351 361 return ([(rev, i) for i in xrange(1, size + 1)], text)
352 362
353 363 decorate = (((linenumber is None) and decorate_compat) or
354 364 (linenumber and with_linenumber) or
355 365 without_linenumber)
356 366
357 367 def pair(parent, child):
358 368 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
359 369 child[0][b1:b2] = parent[0][a1:a2]
360 370 return child
361 371
362 372 getlog = util.cachefunc(lambda x: self._repo.file(x))
363 373 def getctx(path, fileid):
364 374 log = path == self._path and self._filelog or getlog(path)
365 375 return filectx(self._repo, path, fileid=fileid, filelog=log)
366 376 getctx = util.cachefunc(getctx)
367 377
368 378 def parents(f):
369 379 # we want to reuse filectx objects as much as possible
370 380 p = f._path
371 381 if f._filerev is None: # working dir
372 382 pl = [(n.path(), n.filerev()) for n in f.parents()]
373 383 else:
374 384 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
375 385
376 386 if follow:
377 387 r = f.renamed()
378 388 if r:
379 389 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
380 390
381 391 return [getctx(p, n) for p, n in pl if n != nullrev]
382 392
383 393 # use linkrev to find the first changeset where self appeared
384 394 if self.rev() != self.linkrev():
385 395 base = self.filectx(self.filerev())
386 396 else:
387 397 base = self
388 398
389 399 # find all ancestors
390 400 needed = {base: 1}
391 401 visit = [base]
392 402 files = [base._path]
393 403 while visit:
394 404 f = visit.pop(0)
395 405 for p in parents(f):
396 406 if p not in needed:
397 407 needed[p] = 1
398 408 visit.append(p)
399 409 if p._path not in files:
400 410 files.append(p._path)
401 411 else:
402 412 # count how many times we'll use this
403 413 needed[p] += 1
404 414
405 415 # sort by revision (per file) which is a topological order
406 416 visit = []
407 417 for f in files:
408 418 fn = [(n.rev(), n) for n in needed if n._path == f]
409 419 visit.extend(fn)
410 420
411 421 hist = {}
412 422 for r, f in util.sort(visit):
413 423 curr = decorate(f.data(), f)
414 424 for p in parents(f):
415 425 if p != nullid:
416 426 curr = pair(hist[p], curr)
417 427 # trim the history of unneeded revs
418 428 needed[p] -= 1
419 429 if not needed[p]:
420 430 del hist[p]
421 431 hist[f] = curr
422 432
423 433 return zip(hist[f][0], hist[f][1].splitlines(1))
424 434
425 435 def ancestor(self, fc2):
426 436 """
427 437 find the common ancestor file context, if any, of self, and fc2
428 438 """
429 439
430 440 acache = {}
431 441
432 442 # prime the ancestor cache for the working directory
433 443 for c in (self, fc2):
434 444 if c._filerev == None:
435 445 pl = [(n.path(), n.filenode()) for n in c.parents()]
436 446 acache[(c._path, None)] = pl
437 447
438 448 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
439 449 def parents(vertex):
440 450 if vertex in acache:
441 451 return acache[vertex]
442 452 f, n = vertex
443 453 if f not in flcache:
444 454 flcache[f] = self._repo.file(f)
445 455 fl = flcache[f]
446 456 pl = [(f, p) for p in fl.parents(n) if p != nullid]
447 457 re = fl.renamed(n)
448 458 if re:
449 459 pl.append(re)
450 460 acache[vertex] = pl
451 461 return pl
452 462
453 463 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
454 464 v = ancestor.ancestor(a, b, parents)
455 465 if v:
456 466 f, n = v
457 467 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
458 468
459 469 return None
460 470
461 471 class workingctx(changectx):
462 472 """A workingctx object makes access to data related to
463 473 the current working directory convenient.
464 474 parents - a pair of parent nodeids, or None to use the dirstate.
465 475 date - any valid date string or (unixtime, offset), or None.
466 476 user - username string, or None.
467 477 extra - a dictionary of extra values, or None.
468 478 changes - a list of file lists as returned by localrepo.status()
469 479 or None to use the repository status.
470 480 """
471 481 def __init__(self, repo, parents=None, text="", user=None, date=None,
472 482 extra=None, changes=None):
473 483 self._repo = repo
474 484 self._rev = None
475 485 self._node = None
476 486 self._text = text
477 487 if date:
478 488 self._date = util.parsedate(date)
479 489 if user:
480 490 self._user = user
481 491 if parents:
482 492 self._parents = [changectx(self._repo, p) for p in parents]
483 493 if changes:
484 494 self._status = list(changes)
485 495
486 496 self._extra = {}
487 497 if extra:
488 498 self._extra = extra.copy()
489 499 if 'branch' not in self._extra:
490 500 branch = self._repo.dirstate.branch()
491 501 try:
492 502 branch = branch.decode('UTF-8').encode('UTF-8')
493 503 except UnicodeDecodeError:
494 504 raise util.Abort(_('branch name not in UTF-8!'))
495 505 self._extra['branch'] = branch
496 506 if self._extra['branch'] == '':
497 507 self._extra['branch'] = 'default'
498 508
499 509 def __str__(self):
500 510 return str(self._parents[0]) + "+"
501 511
502 512 def __nonzero__(self):
503 513 return True
504 514
505 515 def __contains__(self, key):
506 516 return self._dirstate[key] not in "?r"
507 517
508 def __getattr__(self, name):
509 if name == '_status':
510 self._status = self._repo.status(unknown=True)
511 return self._status
512 elif name == '_user':
513 self._user = self._repo.ui.username()
514 return self._user
515 elif name == '_date':
516 self._date = util.makedate()
517 return self._date
518 if name == '_manifest':
519 self._buildmanifest()
520 return self._manifest
521 elif name == '_parents':
522 p = self._repo.dirstate.parents()
523 if p[1] == nullid:
524 p = p[:-1]
525 self._parents = [changectx(self._repo, x) for x in p]
526 return self._parents
527 else:
528 raise AttributeError(name)
529
530 def _buildmanifest(self):
518 def _manifest(self):
531 519 """generate a manifest corresponding to the working directory"""
532 520
533 521 man = self._parents[0].manifest().copy()
534 522 copied = self._repo.dirstate.copies()
535 523 cf = lambda x: man.flags(copied.get(x, x))
536 524 ff = self._repo.dirstate.flagfunc(cf)
537 525 modified, added, removed, deleted, unknown = self._status[:5]
538 526 for i, l in (("a", added), ("m", modified), ("u", unknown)):
539 527 for f in l:
540 528 man[f] = man.get(copied.get(f, f), nullid) + i
541 529 try:
542 530 man.set(f, ff(f))
543 531 except OSError:
544 532 pass
545 533
546 534 for f in deleted + removed:
547 535 if f in man:
548 536 del man[f]
549 537
550 self._manifest = man
538 return man
539 _manifest = propertycache(_manifest)
540
541 def _status(self):
542 return self._repo.status(unknown=True)
543 _status = propertycache(_status)
544
545 def _user(self):
546 return self._repo.ui.username()
547 _user = propertycache(_user)
548
549 def _date(self):
550 return util.makedate()
551 _date = propertycache(_date)
552
553 def _parents(self):
554 p = self._repo.dirstate.parents()
555 if p[1] == nullid:
556 p = p[:-1]
557 self._parents = [changectx(self._repo, x) for x in p]
558 return self._parents
559 _parents = propertycache(_parents)
551 560
552 561 def manifest(self): return self._manifest
553 562
554 563 def user(self): return self._user or self._repo.ui.username()
555 564 def date(self): return self._date
556 565 def description(self): return self._text
557 566 def files(self):
558 567 return util.sort(self._status[0] + self._status[1] + self._status[2])
559 568
560 569 def modified(self): return self._status[0]
561 570 def added(self): return self._status[1]
562 571 def removed(self): return self._status[2]
563 572 def deleted(self): return self._status[3]
564 573 def unknown(self): return self._status[4]
565 574 def clean(self): return self._status[5]
566 575 def branch(self): return self._extra['branch']
567 576 def extra(self): return self._extra
568 577
569 578 def tags(self):
570 579 t = []
571 580 [t.extend(p.tags()) for p in self.parents()]
572 581 return t
573 582
574 583 def children(self):
575 584 return []
576 585
577 586 def flags(self, path):
578 587 if '_manifest' in self.__dict__:
579 588 try:
580 589 return self._manifest.flags(path)
581 590 except KeyError:
582 591 return ''
583 592
584 593 pnode = self._parents[0].changeset()[0]
585 594 orig = self._repo.dirstate.copies().get(path, path)
586 595 node, flag = self._repo.manifest.find(pnode, orig)
587 596 try:
588 597 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
589 598 return ff(path)
590 599 except OSError:
591 600 pass
592 601
593 602 if not node or path in self.deleted() or path in self.removed():
594 603 return ''
595 604 return flag
596 605
597 606 def filectx(self, path, filelog=None):
598 607 """get a file context from the working directory"""
599 608 return workingfilectx(self._repo, path, workingctx=self,
600 609 filelog=filelog)
601 610
602 611 def ancestor(self, c2):
603 612 """return the ancestor context of self and c2"""
604 613 return self._parents[0].ancestor(c2) # punt on two parents for now
605 614
606 615 def walk(self, match):
607 616 return util.sort(self._repo.dirstate.walk(match, True, False).keys())
608 617
609 618 class workingfilectx(filectx):
610 619 """A workingfilectx object makes access to data related to a particular
611 620 file in the working directory convenient."""
612 621 def __init__(self, repo, path, filelog=None, workingctx=None):
613 622 """changeid can be a changeset revision, node, or tag.
614 623 fileid can be a file revision or node."""
615 624 self._repo = repo
616 625 self._path = path
617 626 self._changeid = None
618 627 self._filerev = self._filenode = None
619 628
620 629 if filelog:
621 630 self._filelog = filelog
622 631 if workingctx:
623 632 self._changectx = workingctx
624 633
625 def __getattr__(self, name):
626 if name == '_changectx':
627 self._changectx = workingctx(self._repo)
628 return self._changectx
629 elif name == '_repopath':
630 self._repopath = (self._repo.dirstate.copied(self._path)
631 or self._path)
632 return self._repopath
633 elif name == '_filelog':
634 self._filelog = self._repo.file(self._repopath)
635 return self._filelog
636 else:
637 raise AttributeError(name)
634 def _changectx(self):
635 return workingctx(self._repo)
636 _changectx = propertycache(_changectx)
637
638 def _repopath(self):
639 return self._repo.dirstate.copied(self._path) or self._path
640 _repopath = propertycache(_repopath)
641
642 def _filelog(self):
643 return self._repo.file(self._repopath)
644 _filelog = propertycache(_filelog)
638 645
639 646 def __nonzero__(self):
640 647 return True
641 648
642 649 def __str__(self):
643 650 return "%s@%s" % (self.path(), self._changectx)
644 651
645 652 def filectx(self, fileid):
646 653 '''opens an arbitrary revision of the file without
647 654 opening a new filelog'''
648 655 return filectx(self._repo, self._repopath, fileid=fileid,
649 656 filelog=self._filelog)
650 657
651 658 def rev(self):
652 659 if '_changectx' in self.__dict__:
653 660 return self._changectx.rev()
654 661 return self._filelog.linkrev(self._filerev)
655 662
656 663 def data(self): return self._repo.wread(self._path)
657 664 def renamed(self):
658 665 rp = self._repopath
659 666 if rp == self._path:
660 667 return None
661 668 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
662 669
663 670 def parents(self):
664 671 '''return parent filectxs, following copies if necessary'''
665 672 p = self._path
666 673 rp = self._repopath
667 674 pcl = self._changectx._parents
668 675 fl = self._filelog
669 676 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
670 677 if len(pcl) > 1:
671 678 if rp != p:
672 679 fl = None
673 680 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
674 681
675 682 return [filectx(self._repo, p, fileid=n, filelog=l)
676 683 for p,n,l in pl if n != nullid]
677 684
678 685 def children(self):
679 686 return []
680 687
681 688 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
682 689 def date(self):
683 690 t, tz = self._changectx.date()
684 691 try:
685 692 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
686 693 except OSError, err:
687 694 if err.errno != errno.ENOENT: raise
688 695 return (t, tz)
689 696
690 697 def cmp(self, text): return self._repo.wread(self._path) == text
691 698
692 699 class memctx(object):
693 700 """Use memctx to perform in-memory commits via localrepo.commitctx().
694 701
695 702 Revision information is supplied at initialization time while
696 703 related files data and is made available through a callback
697 704 mechanism. 'repo' is the current localrepo, 'parents' is a
698 705 sequence of two parent revisions identifiers (pass None for every
699 706 missing parent), 'text' is the commit message and 'files' lists
700 707 names of files touched by the revision (normalized and relative to
701 708 repository root).
702 709
703 710 filectxfn(repo, memctx, path) is a callable receiving the
704 711 repository, the current memctx object and the normalized path of
705 712 requested file, relative to repository root. It is fired by the
706 713 commit function for every file in 'files', but calls order is
707 714 undefined. If the file is available in the revision being
708 715 committed (updated or added), filectxfn returns a memfilectx
709 716 object. If the file was removed, filectxfn raises an
710 717 IOError. Moved files are represented by marking the source file
711 718 removed and the new file added with copy information (see
712 719 memfilectx).
713 720
714 721 user receives the committer name and defaults to current
715 722 repository username, date is the commit date in any format
716 723 supported by util.parsedate() and defaults to current date, extra
717 724 is a dictionary of metadata or is left empty.
718 725 """
719 726 def __init__(self, repo, parents, text, files, filectxfn, user=None,
720 727 date=None, extra=None):
721 728 self._repo = repo
722 729 self._rev = None
723 730 self._node = None
724 731 self._text = text
725 732 self._date = date and util.parsedate(date) or util.makedate()
726 733 self._user = user
727 734 parents = [(p or nullid) for p in parents]
728 735 p1, p2 = parents
729 736 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
730 737 files = util.sort(list(files))
731 738 self._status = [files, [], [], [], []]
732 739 self._filectxfn = filectxfn
733 740
734 741 self._extra = extra and extra.copy() or {}
735 742 if 'branch' not in self._extra:
736 743 self._extra['branch'] = 'default'
737 744 elif self._extra.get('branch') == '':
738 745 self._extra['branch'] = 'default'
739 746
740 747 def __str__(self):
741 748 return str(self._parents[0]) + "+"
742 749
743 750 def __int__(self):
744 751 return self._rev
745 752
746 753 def __nonzero__(self):
747 754 return True
748 755
749 756 def user(self): return self._user or self._repo.ui.username()
750 757 def date(self): return self._date
751 758 def description(self): return self._text
752 759 def files(self): return self.modified()
753 760 def modified(self): return self._status[0]
754 761 def added(self): return self._status[1]
755 762 def removed(self): return self._status[2]
756 763 def deleted(self): return self._status[3]
757 764 def unknown(self): return self._status[4]
758 765 def clean(self): return self._status[5]
759 766 def branch(self): return self._extra['branch']
760 767 def extra(self): return self._extra
761 768 def flags(self, f): return self[f].flags()
762 769
763 770 def parents(self):
764 771 """return contexts for each parent changeset"""
765 772 return self._parents
766 773
767 774 def filectx(self, path, filelog=None):
768 775 """get a file context from the working directory"""
769 776 return self._filectxfn(self._repo, self, path)
770 777
771 778 class memfilectx(object):
772 779 """memfilectx represents an in-memory file to commit.
773 780
774 781 See memctx for more details.
775 782 """
776 783 def __init__(self, path, data, islink, isexec, copied):
777 784 """
778 785 path is the normalized file path relative to repository root.
779 786 data is the file content as a string.
780 787 islink is True if the file is a symbolic link.
781 788 isexec is True if the file is executable.
782 789 copied is the source file path if current file was copied in the
783 790 revision being committed, or None."""
784 791 self._path = path
785 792 self._data = data
786 793 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
787 794 self._copied = None
788 795 if copied:
789 796 self._copied = (copied, nullid)
790 797
791 798 def __nonzero__(self): return True
792 799 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
793 800 def path(self): return self._path
794 801 def data(self): return self._data
795 802 def flags(self): return self._flags
796 803 def isexec(self): return 'x' in self._flags
797 804 def islink(self): return 'l' in self._flags
798 805 def renamed(self): return self._copied
799 806
General Comments 0
You need to be logged in to leave comments. Login now