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