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