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