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