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