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