##// END OF EJS Templates
context: use rev for changelog lookup...
Matt Mackall -
r16377:f8ce254e default
parent child Browse files
Show More
@@ -1,1266 +1,1266
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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import match as matchmod
11 import match as matchmod
12 import os, errno, stat
12 import os, errno, stat
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15
15
16 class changectx(object):
16 class changectx(object):
17 """A changecontext object makes access to data related to a particular
17 """A changecontext object makes access to data related to a particular
18 changeset convenient."""
18 changeset convenient."""
19 def __init__(self, repo, changeid=''):
19 def __init__(self, repo, changeid=''):
20 """changeid is a revision number, node, or tag"""
20 """changeid is a revision number, node, or tag"""
21 if changeid == '':
21 if changeid == '':
22 changeid = '.'
22 changeid = '.'
23 self._repo = repo
23 self._repo = repo
24
24
25 if isinstance(changeid, int):
25 if isinstance(changeid, int):
26 self._rev = changeid
26 self._rev = changeid
27 self._node = repo.changelog.node(changeid)
27 self._node = repo.changelog.node(changeid)
28 return
28 return
29 if changeid == '.':
29 if changeid == '.':
30 self._node = repo.dirstate.p1()
30 self._node = repo.dirstate.p1()
31 self._rev = repo.changelog.rev(self._node)
31 self._rev = repo.changelog.rev(self._node)
32 return
32 return
33 if changeid == 'null':
33 if changeid == 'null':
34 self._node = nullid
34 self._node = nullid
35 self._rev = nullrev
35 self._rev = nullrev
36 return
36 return
37 if changeid == 'tip':
37 if changeid == 'tip':
38 self._rev = len(repo.changelog) - 1
38 self._rev = len(repo.changelog) - 1
39 self._node = repo.changelog.node(self._rev)
39 self._node = repo.changelog.node(self._rev)
40 return
40 return
41 if len(changeid) == 20:
41 if len(changeid) == 20:
42 try:
42 try:
43 self._node = changeid
43 self._node = changeid
44 self._rev = repo.changelog.rev(changeid)
44 self._rev = repo.changelog.rev(changeid)
45 return
45 return
46 except LookupError:
46 except LookupError:
47 pass
47 pass
48
48
49 try:
49 try:
50 r = int(changeid)
50 r = int(changeid)
51 if str(r) != changeid:
51 if str(r) != changeid:
52 raise ValueError
52 raise ValueError
53 l = len(repo.changelog)
53 l = len(repo.changelog)
54 if r < 0:
54 if r < 0:
55 r += l
55 r += l
56 if r < 0 or r >= l:
56 if r < 0 or r >= l:
57 raise ValueError
57 raise ValueError
58 self._rev = r
58 self._rev = r
59 self._node = repo.changelog.node(r)
59 self._node = repo.changelog.node(r)
60 return
60 return
61 except (ValueError, OverflowError):
61 except (ValueError, OverflowError):
62 pass
62 pass
63
63
64 if len(changeid) == 40:
64 if len(changeid) == 40:
65 try:
65 try:
66 self._node = bin(changeid)
66 self._node = bin(changeid)
67 self._rev = repo.changelog.rev(self._node)
67 self._rev = repo.changelog.rev(self._node)
68 return
68 return
69 except (TypeError, LookupError):
69 except (TypeError, LookupError):
70 pass
70 pass
71
71
72 if changeid in repo._bookmarks:
72 if changeid in repo._bookmarks:
73 self._node = repo._bookmarks[changeid]
73 self._node = repo._bookmarks[changeid]
74 self._rev = repo.changelog.rev(self._node)
74 self._rev = repo.changelog.rev(self._node)
75 return
75 return
76 if changeid in repo._tagscache.tags:
76 if changeid in repo._tagscache.tags:
77 self._node = repo._tagscache.tags[changeid]
77 self._node = repo._tagscache.tags[changeid]
78 self._rev = repo.changelog.rev(self._node)
78 self._rev = repo.changelog.rev(self._node)
79 return
79 return
80 if changeid in repo.branchtags():
80 if changeid in repo.branchtags():
81 self._node = repo.branchtags()[changeid]
81 self._node = repo.branchtags()[changeid]
82 self._rev = repo.changelog.rev(self._node)
82 self._rev = repo.changelog.rev(self._node)
83 return
83 return
84
84
85 self._node = repo.changelog._partialmatch(changeid)
85 self._node = repo.changelog._partialmatch(changeid)
86 if self._node is not None:
86 if self._node is not None:
87 self._rev = repo.changelog.rev(self._node)
87 self._rev = repo.changelog.rev(self._node)
88 return
88 return
89
89
90 # lookup failed
90 # lookup failed
91 # check if it might have come from damaged dirstate
91 # check if it might have come from damaged dirstate
92 if changeid in repo.dirstate.parents():
92 if changeid in repo.dirstate.parents():
93 raise error.Abort(_("working directory has unknown parent '%s'!")
93 raise error.Abort(_("working directory has unknown parent '%s'!")
94 % short(changeid))
94 % short(changeid))
95 try:
95 try:
96 if len(changeid) == 20:
96 if len(changeid) == 20:
97 changeid = hex(changeid)
97 changeid = hex(changeid)
98 except TypeError:
98 except TypeError:
99 pass
99 pass
100 raise error.RepoLookupError(
100 raise error.RepoLookupError(
101 _("unknown revision '%s'") % changeid)
101 _("unknown revision '%s'") % changeid)
102
102
103 def __str__(self):
103 def __str__(self):
104 return short(self.node())
104 return short(self.node())
105
105
106 def __int__(self):
106 def __int__(self):
107 return self.rev()
107 return self.rev()
108
108
109 def __repr__(self):
109 def __repr__(self):
110 return "<changectx %s>" % str(self)
110 return "<changectx %s>" % str(self)
111
111
112 def __hash__(self):
112 def __hash__(self):
113 try:
113 try:
114 return hash(self._rev)
114 return hash(self._rev)
115 except AttributeError:
115 except AttributeError:
116 return id(self)
116 return id(self)
117
117
118 def __eq__(self, other):
118 def __eq__(self, other):
119 try:
119 try:
120 return self._rev == other._rev
120 return self._rev == other._rev
121 except AttributeError:
121 except AttributeError:
122 return False
122 return False
123
123
124 def __ne__(self, other):
124 def __ne__(self, other):
125 return not (self == other)
125 return not (self == other)
126
126
127 def __nonzero__(self):
127 def __nonzero__(self):
128 return self._rev != nullrev
128 return self._rev != nullrev
129
129
130 @propertycache
130 @propertycache
131 def _changeset(self):
131 def _changeset(self):
132 return self._repo.changelog.read(self.node())
132 return self._repo.changelog.read(self.rev())
133
133
134 @propertycache
134 @propertycache
135 def _manifest(self):
135 def _manifest(self):
136 return self._repo.manifest.read(self._changeset[0])
136 return self._repo.manifest.read(self._changeset[0])
137
137
138 @propertycache
138 @propertycache
139 def _manifestdelta(self):
139 def _manifestdelta(self):
140 return self._repo.manifest.readdelta(self._changeset[0])
140 return self._repo.manifest.readdelta(self._changeset[0])
141
141
142 @propertycache
142 @propertycache
143 def _parents(self):
143 def _parents(self):
144 p = self._repo.changelog.parentrevs(self._rev)
144 p = self._repo.changelog.parentrevs(self._rev)
145 if p[1] == nullrev:
145 if p[1] == nullrev:
146 p = p[:-1]
146 p = p[:-1]
147 return [changectx(self._repo, x) for x in p]
147 return [changectx(self._repo, x) for x in p]
148
148
149 @propertycache
149 @propertycache
150 def substate(self):
150 def substate(self):
151 return subrepo.state(self, self._repo.ui)
151 return subrepo.state(self, self._repo.ui)
152
152
153 def __contains__(self, key):
153 def __contains__(self, key):
154 return key in self._manifest
154 return key in self._manifest
155
155
156 def __getitem__(self, key):
156 def __getitem__(self, key):
157 return self.filectx(key)
157 return self.filectx(key)
158
158
159 def __iter__(self):
159 def __iter__(self):
160 for f in sorted(self._manifest):
160 for f in sorted(self._manifest):
161 yield f
161 yield f
162
162
163 def changeset(self):
163 def changeset(self):
164 return self._changeset
164 return self._changeset
165 def manifest(self):
165 def manifest(self):
166 return self._manifest
166 return self._manifest
167 def manifestnode(self):
167 def manifestnode(self):
168 return self._changeset[0]
168 return self._changeset[0]
169
169
170 def rev(self):
170 def rev(self):
171 return self._rev
171 return self._rev
172 def node(self):
172 def node(self):
173 return self._node
173 return self._node
174 def hex(self):
174 def hex(self):
175 return hex(self._node)
175 return hex(self._node)
176 def user(self):
176 def user(self):
177 return self._changeset[1]
177 return self._changeset[1]
178 def date(self):
178 def date(self):
179 return self._changeset[2]
179 return self._changeset[2]
180 def files(self):
180 def files(self):
181 return self._changeset[3]
181 return self._changeset[3]
182 def description(self):
182 def description(self):
183 return self._changeset[4]
183 return self._changeset[4]
184 def branch(self):
184 def branch(self):
185 return encoding.tolocal(self._changeset[5].get("branch"))
185 return encoding.tolocal(self._changeset[5].get("branch"))
186 def extra(self):
186 def extra(self):
187 return self._changeset[5]
187 return self._changeset[5]
188 def tags(self):
188 def tags(self):
189 return self._repo.nodetags(self._node)
189 return self._repo.nodetags(self._node)
190 def bookmarks(self):
190 def bookmarks(self):
191 return self._repo.nodebookmarks(self._node)
191 return self._repo.nodebookmarks(self._node)
192 def phase(self):
192 def phase(self):
193 if self._rev == -1:
193 if self._rev == -1:
194 return phases.public
194 return phases.public
195 if self._rev >= len(self._repo._phaserev):
195 if self._rev >= len(self._repo._phaserev):
196 # outdated cache
196 # outdated cache
197 del self._repo._phaserev
197 del self._repo._phaserev
198 return self._repo._phaserev[self._rev]
198 return self._repo._phaserev[self._rev]
199 def phasestr(self):
199 def phasestr(self):
200 return phases.phasenames[self.phase()]
200 return phases.phasenames[self.phase()]
201 def mutable(self):
201 def mutable(self):
202 return self.phase() > phases.public
202 return self.phase() > phases.public
203 def hidden(self):
203 def hidden(self):
204 return self._rev in self._repo.changelog.hiddenrevs
204 return self._rev in self._repo.changelog.hiddenrevs
205
205
206 def parents(self):
206 def parents(self):
207 """return contexts for each parent changeset"""
207 """return contexts for each parent changeset"""
208 return self._parents
208 return self._parents
209
209
210 def p1(self):
210 def p1(self):
211 return self._parents[0]
211 return self._parents[0]
212
212
213 def p2(self):
213 def p2(self):
214 if len(self._parents) == 2:
214 if len(self._parents) == 2:
215 return self._parents[1]
215 return self._parents[1]
216 return changectx(self._repo, -1)
216 return changectx(self._repo, -1)
217
217
218 def children(self):
218 def children(self):
219 """return contexts for each child changeset"""
219 """return contexts for each child changeset"""
220 c = self._repo.changelog.children(self._node)
220 c = self._repo.changelog.children(self._node)
221 return [changectx(self._repo, x) for x in c]
221 return [changectx(self._repo, x) for x in c]
222
222
223 def ancestors(self):
223 def ancestors(self):
224 for a in self._repo.changelog.ancestors(self._rev):
224 for a in self._repo.changelog.ancestors(self._rev):
225 yield changectx(self._repo, a)
225 yield changectx(self._repo, a)
226
226
227 def descendants(self):
227 def descendants(self):
228 for d in self._repo.changelog.descendants(self._rev):
228 for d in self._repo.changelog.descendants(self._rev):
229 yield changectx(self._repo, d)
229 yield changectx(self._repo, d)
230
230
231 def _fileinfo(self, path):
231 def _fileinfo(self, path):
232 if '_manifest' in self.__dict__:
232 if '_manifest' in self.__dict__:
233 try:
233 try:
234 return self._manifest[path], self._manifest.flags(path)
234 return self._manifest[path], self._manifest.flags(path)
235 except KeyError:
235 except KeyError:
236 raise error.LookupError(self._node, path,
236 raise error.LookupError(self._node, path,
237 _('not found in manifest'))
237 _('not found in manifest'))
238 if '_manifestdelta' in self.__dict__ or path in self.files():
238 if '_manifestdelta' in self.__dict__ or path in self.files():
239 if path in self._manifestdelta:
239 if path in self._manifestdelta:
240 return self._manifestdelta[path], self._manifestdelta.flags(path)
240 return self._manifestdelta[path], self._manifestdelta.flags(path)
241 node, flag = self._repo.manifest.find(self._changeset[0], path)
241 node, flag = self._repo.manifest.find(self._changeset[0], path)
242 if not node:
242 if not node:
243 raise error.LookupError(self._node, path,
243 raise error.LookupError(self._node, path,
244 _('not found in manifest'))
244 _('not found in manifest'))
245
245
246 return node, flag
246 return node, flag
247
247
248 def filenode(self, path):
248 def filenode(self, path):
249 return self._fileinfo(path)[0]
249 return self._fileinfo(path)[0]
250
250
251 def flags(self, path):
251 def flags(self, path):
252 try:
252 try:
253 return self._fileinfo(path)[1]
253 return self._fileinfo(path)[1]
254 except error.LookupError:
254 except error.LookupError:
255 return ''
255 return ''
256
256
257 def filectx(self, path, fileid=None, filelog=None):
257 def filectx(self, path, fileid=None, filelog=None):
258 """get a file context from this changeset"""
258 """get a file context from this changeset"""
259 if fileid is None:
259 if fileid is None:
260 fileid = self.filenode(path)
260 fileid = self.filenode(path)
261 return filectx(self._repo, path, fileid=fileid,
261 return filectx(self._repo, path, fileid=fileid,
262 changectx=self, filelog=filelog)
262 changectx=self, filelog=filelog)
263
263
264 def ancestor(self, c2):
264 def ancestor(self, c2):
265 """
265 """
266 return the ancestor context of self and c2
266 return the ancestor context of self and c2
267 """
267 """
268 # deal with workingctxs
268 # deal with workingctxs
269 n2 = c2._node
269 n2 = c2._node
270 if n2 is None:
270 if n2 is None:
271 n2 = c2._parents[0]._node
271 n2 = c2._parents[0]._node
272 n = self._repo.changelog.ancestor(self._node, n2)
272 n = self._repo.changelog.ancestor(self._node, n2)
273 return changectx(self._repo, n)
273 return changectx(self._repo, n)
274
274
275 def walk(self, match):
275 def walk(self, match):
276 fset = set(match.files())
276 fset = set(match.files())
277 # for dirstate.walk, files=['.'] means "walk the whole tree".
277 # for dirstate.walk, files=['.'] means "walk the whole tree".
278 # follow that here, too
278 # follow that here, too
279 fset.discard('.')
279 fset.discard('.')
280 for fn in self:
280 for fn in self:
281 if fn in fset:
281 if fn in fset:
282 # specified pattern is the exact name
282 # specified pattern is the exact name
283 fset.remove(fn)
283 fset.remove(fn)
284 if match(fn):
284 if match(fn):
285 yield fn
285 yield fn
286 for fn in sorted(fset):
286 for fn in sorted(fset):
287 if fn in self._dirs:
287 if fn in self._dirs:
288 # specified pattern is a directory
288 # specified pattern is a directory
289 continue
289 continue
290 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
290 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
291 yield fn
291 yield fn
292
292
293 def sub(self, path):
293 def sub(self, path):
294 return subrepo.subrepo(self, path)
294 return subrepo.subrepo(self, path)
295
295
296 def match(self, pats=[], include=None, exclude=None, default='glob'):
296 def match(self, pats=[], include=None, exclude=None, default='glob'):
297 r = self._repo
297 r = self._repo
298 return matchmod.match(r.root, r.getcwd(), pats,
298 return matchmod.match(r.root, r.getcwd(), pats,
299 include, exclude, default,
299 include, exclude, default,
300 auditor=r.auditor, ctx=self)
300 auditor=r.auditor, ctx=self)
301
301
302 def diff(self, ctx2=None, match=None, **opts):
302 def diff(self, ctx2=None, match=None, **opts):
303 """Returns a diff generator for the given contexts and matcher"""
303 """Returns a diff generator for the given contexts and matcher"""
304 if ctx2 is None:
304 if ctx2 is None:
305 ctx2 = self.p1()
305 ctx2 = self.p1()
306 if ctx2 is not None and not isinstance(ctx2, changectx):
306 if ctx2 is not None and not isinstance(ctx2, changectx):
307 ctx2 = self._repo[ctx2]
307 ctx2 = self._repo[ctx2]
308 diffopts = patch.diffopts(self._repo.ui, opts)
308 diffopts = patch.diffopts(self._repo.ui, opts)
309 return patch.diff(self._repo, ctx2.node(), self.node(),
309 return patch.diff(self._repo, ctx2.node(), self.node(),
310 match=match, opts=diffopts)
310 match=match, opts=diffopts)
311
311
312 @propertycache
312 @propertycache
313 def _dirs(self):
313 def _dirs(self):
314 dirs = set()
314 dirs = set()
315 for f in self._manifest:
315 for f in self._manifest:
316 pos = f.rfind('/')
316 pos = f.rfind('/')
317 while pos != -1:
317 while pos != -1:
318 f = f[:pos]
318 f = f[:pos]
319 if f in dirs:
319 if f in dirs:
320 break # dirs already contains this and above
320 break # dirs already contains this and above
321 dirs.add(f)
321 dirs.add(f)
322 pos = f.rfind('/')
322 pos = f.rfind('/')
323 return dirs
323 return dirs
324
324
325 def dirs(self):
325 def dirs(self):
326 return self._dirs
326 return self._dirs
327
327
328 class filectx(object):
328 class filectx(object):
329 """A filecontext object makes access to data related to a particular
329 """A filecontext object makes access to data related to a particular
330 filerevision convenient."""
330 filerevision convenient."""
331 def __init__(self, repo, path, changeid=None, fileid=None,
331 def __init__(self, repo, path, changeid=None, fileid=None,
332 filelog=None, changectx=None):
332 filelog=None, changectx=None):
333 """changeid can be a changeset revision, node, or tag.
333 """changeid can be a changeset revision, node, or tag.
334 fileid can be a file revision or node."""
334 fileid can be a file revision or node."""
335 self._repo = repo
335 self._repo = repo
336 self._path = path
336 self._path = path
337
337
338 assert (changeid is not None
338 assert (changeid is not None
339 or fileid is not None
339 or fileid is not None
340 or changectx is not None), \
340 or changectx is not None), \
341 ("bad args: changeid=%r, fileid=%r, changectx=%r"
341 ("bad args: changeid=%r, fileid=%r, changectx=%r"
342 % (changeid, fileid, changectx))
342 % (changeid, fileid, changectx))
343
343
344 if filelog:
344 if filelog:
345 self._filelog = filelog
345 self._filelog = filelog
346
346
347 if changeid is not None:
347 if changeid is not None:
348 self._changeid = changeid
348 self._changeid = changeid
349 if changectx is not None:
349 if changectx is not None:
350 self._changectx = changectx
350 self._changectx = changectx
351 if fileid is not None:
351 if fileid is not None:
352 self._fileid = fileid
352 self._fileid = fileid
353
353
354 @propertycache
354 @propertycache
355 def _changectx(self):
355 def _changectx(self):
356 return changectx(self._repo, self._changeid)
356 return changectx(self._repo, self._changeid)
357
357
358 @propertycache
358 @propertycache
359 def _filelog(self):
359 def _filelog(self):
360 return self._repo.file(self._path)
360 return self._repo.file(self._path)
361
361
362 @propertycache
362 @propertycache
363 def _changeid(self):
363 def _changeid(self):
364 if '_changectx' in self.__dict__:
364 if '_changectx' in self.__dict__:
365 return self._changectx.rev()
365 return self._changectx.rev()
366 else:
366 else:
367 return self._filelog.linkrev(self._filerev)
367 return self._filelog.linkrev(self._filerev)
368
368
369 @propertycache
369 @propertycache
370 def _filenode(self):
370 def _filenode(self):
371 if '_fileid' in self.__dict__:
371 if '_fileid' in self.__dict__:
372 return self._filelog.lookup(self._fileid)
372 return self._filelog.lookup(self._fileid)
373 else:
373 else:
374 return self._changectx.filenode(self._path)
374 return self._changectx.filenode(self._path)
375
375
376 @propertycache
376 @propertycache
377 def _filerev(self):
377 def _filerev(self):
378 return self._filelog.rev(self._filenode)
378 return self._filelog.rev(self._filenode)
379
379
380 @propertycache
380 @propertycache
381 def _repopath(self):
381 def _repopath(self):
382 return self._path
382 return self._path
383
383
384 def __nonzero__(self):
384 def __nonzero__(self):
385 try:
385 try:
386 self._filenode
386 self._filenode
387 return True
387 return True
388 except error.LookupError:
388 except error.LookupError:
389 # file is missing
389 # file is missing
390 return False
390 return False
391
391
392 def __str__(self):
392 def __str__(self):
393 return "%s@%s" % (self.path(), short(self.node()))
393 return "%s@%s" % (self.path(), short(self.node()))
394
394
395 def __repr__(self):
395 def __repr__(self):
396 return "<filectx %s>" % str(self)
396 return "<filectx %s>" % str(self)
397
397
398 def __hash__(self):
398 def __hash__(self):
399 try:
399 try:
400 return hash((self._path, self._filenode))
400 return hash((self._path, self._filenode))
401 except AttributeError:
401 except AttributeError:
402 return id(self)
402 return id(self)
403
403
404 def __eq__(self, other):
404 def __eq__(self, other):
405 try:
405 try:
406 return (self._path == other._path
406 return (self._path == other._path
407 and self._filenode == other._filenode)
407 and self._filenode == other._filenode)
408 except AttributeError:
408 except AttributeError:
409 return False
409 return False
410
410
411 def __ne__(self, other):
411 def __ne__(self, other):
412 return not (self == other)
412 return not (self == other)
413
413
414 def filectx(self, fileid):
414 def filectx(self, fileid):
415 '''opens an arbitrary revision of the file without
415 '''opens an arbitrary revision of the file without
416 opening a new filelog'''
416 opening a new filelog'''
417 return filectx(self._repo, self._path, fileid=fileid,
417 return filectx(self._repo, self._path, fileid=fileid,
418 filelog=self._filelog)
418 filelog=self._filelog)
419
419
420 def filerev(self):
420 def filerev(self):
421 return self._filerev
421 return self._filerev
422 def filenode(self):
422 def filenode(self):
423 return self._filenode
423 return self._filenode
424 def flags(self):
424 def flags(self):
425 return self._changectx.flags(self._path)
425 return self._changectx.flags(self._path)
426 def filelog(self):
426 def filelog(self):
427 return self._filelog
427 return self._filelog
428
428
429 def rev(self):
429 def rev(self):
430 if '_changectx' in self.__dict__:
430 if '_changectx' in self.__dict__:
431 return self._changectx.rev()
431 return self._changectx.rev()
432 if '_changeid' in self.__dict__:
432 if '_changeid' in self.__dict__:
433 return self._changectx.rev()
433 return self._changectx.rev()
434 return self._filelog.linkrev(self._filerev)
434 return self._filelog.linkrev(self._filerev)
435
435
436 def linkrev(self):
436 def linkrev(self):
437 return self._filelog.linkrev(self._filerev)
437 return self._filelog.linkrev(self._filerev)
438 def node(self):
438 def node(self):
439 return self._changectx.node()
439 return self._changectx.node()
440 def hex(self):
440 def hex(self):
441 return hex(self.node())
441 return hex(self.node())
442 def user(self):
442 def user(self):
443 return self._changectx.user()
443 return self._changectx.user()
444 def date(self):
444 def date(self):
445 return self._changectx.date()
445 return self._changectx.date()
446 def files(self):
446 def files(self):
447 return self._changectx.files()
447 return self._changectx.files()
448 def description(self):
448 def description(self):
449 return self._changectx.description()
449 return self._changectx.description()
450 def branch(self):
450 def branch(self):
451 return self._changectx.branch()
451 return self._changectx.branch()
452 def extra(self):
452 def extra(self):
453 return self._changectx.extra()
453 return self._changectx.extra()
454 def manifest(self):
454 def manifest(self):
455 return self._changectx.manifest()
455 return self._changectx.manifest()
456 def changectx(self):
456 def changectx(self):
457 return self._changectx
457 return self._changectx
458
458
459 def data(self):
459 def data(self):
460 return self._filelog.read(self._filenode)
460 return self._filelog.read(self._filenode)
461 def path(self):
461 def path(self):
462 return self._path
462 return self._path
463 def size(self):
463 def size(self):
464 return self._filelog.size(self._filerev)
464 return self._filelog.size(self._filerev)
465
465
466 def isbinary(self):
466 def isbinary(self):
467 try:
467 try:
468 return util.binary(self.data())
468 return util.binary(self.data())
469 except IOError:
469 except IOError:
470 return False
470 return False
471
471
472 def cmp(self, fctx):
472 def cmp(self, fctx):
473 """compare with other file context
473 """compare with other file context
474
474
475 returns True if different than fctx.
475 returns True if different than fctx.
476 """
476 """
477 if (fctx._filerev is None
477 if (fctx._filerev is None
478 and (self._repo._encodefilterpats
478 and (self._repo._encodefilterpats
479 # if file data starts with '\1\n', empty metadata block is
479 # if file data starts with '\1\n', empty metadata block is
480 # prepended, which adds 4 bytes to filelog.size().
480 # prepended, which adds 4 bytes to filelog.size().
481 or self.size() - 4 == fctx.size())
481 or self.size() - 4 == fctx.size())
482 or self.size() == fctx.size()):
482 or self.size() == fctx.size()):
483 return self._filelog.cmp(self._filenode, fctx.data())
483 return self._filelog.cmp(self._filenode, fctx.data())
484
484
485 return True
485 return True
486
486
487 def renamed(self):
487 def renamed(self):
488 """check if file was actually renamed in this changeset revision
488 """check if file was actually renamed in this changeset revision
489
489
490 If rename logged in file revision, we report copy for changeset only
490 If rename logged in file revision, we report copy for changeset only
491 if file revisions linkrev points back to the changeset in question
491 if file revisions linkrev points back to the changeset in question
492 or both changeset parents contain different file revisions.
492 or both changeset parents contain different file revisions.
493 """
493 """
494
494
495 renamed = self._filelog.renamed(self._filenode)
495 renamed = self._filelog.renamed(self._filenode)
496 if not renamed:
496 if not renamed:
497 return renamed
497 return renamed
498
498
499 if self.rev() == self.linkrev():
499 if self.rev() == self.linkrev():
500 return renamed
500 return renamed
501
501
502 name = self.path()
502 name = self.path()
503 fnode = self._filenode
503 fnode = self._filenode
504 for p in self._changectx.parents():
504 for p in self._changectx.parents():
505 try:
505 try:
506 if fnode == p.filenode(name):
506 if fnode == p.filenode(name):
507 return None
507 return None
508 except error.LookupError:
508 except error.LookupError:
509 pass
509 pass
510 return renamed
510 return renamed
511
511
512 def parents(self):
512 def parents(self):
513 p = self._path
513 p = self._path
514 fl = self._filelog
514 fl = self._filelog
515 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
515 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
516
516
517 r = self._filelog.renamed(self._filenode)
517 r = self._filelog.renamed(self._filenode)
518 if r:
518 if r:
519 pl[0] = (r[0], r[1], None)
519 pl[0] = (r[0], r[1], None)
520
520
521 return [filectx(self._repo, p, fileid=n, filelog=l)
521 return [filectx(self._repo, p, fileid=n, filelog=l)
522 for p, n, l in pl if n != nullid]
522 for p, n, l in pl if n != nullid]
523
523
524 def p1(self):
524 def p1(self):
525 return self.parents()[0]
525 return self.parents()[0]
526
526
527 def p2(self):
527 def p2(self):
528 p = self.parents()
528 p = self.parents()
529 if len(p) == 2:
529 if len(p) == 2:
530 return p[1]
530 return p[1]
531 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
531 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
532
532
533 def children(self):
533 def children(self):
534 # hard for renames
534 # hard for renames
535 c = self._filelog.children(self._filenode)
535 c = self._filelog.children(self._filenode)
536 return [filectx(self._repo, self._path, fileid=x,
536 return [filectx(self._repo, self._path, fileid=x,
537 filelog=self._filelog) for x in c]
537 filelog=self._filelog) for x in c]
538
538
539 def annotate(self, follow=False, linenumber=None, diffopts=None):
539 def annotate(self, follow=False, linenumber=None, diffopts=None):
540 '''returns a list of tuples of (ctx, line) for each line
540 '''returns a list of tuples of (ctx, line) for each line
541 in the file, where ctx is the filectx of the node where
541 in the file, where ctx is the filectx of the node where
542 that line was last changed.
542 that line was last changed.
543 This returns tuples of ((ctx, linenumber), line) for each line,
543 This returns tuples of ((ctx, linenumber), line) for each line,
544 if "linenumber" parameter is NOT "None".
544 if "linenumber" parameter is NOT "None".
545 In such tuples, linenumber means one at the first appearance
545 In such tuples, linenumber means one at the first appearance
546 in the managed file.
546 in the managed file.
547 To reduce annotation cost,
547 To reduce annotation cost,
548 this returns fixed value(False is used) as linenumber,
548 this returns fixed value(False is used) as linenumber,
549 if "linenumber" parameter is "False".'''
549 if "linenumber" parameter is "False".'''
550
550
551 def decorate_compat(text, rev):
551 def decorate_compat(text, rev):
552 return ([rev] * len(text.splitlines()), text)
552 return ([rev] * len(text.splitlines()), text)
553
553
554 def without_linenumber(text, rev):
554 def without_linenumber(text, rev):
555 return ([(rev, False)] * len(text.splitlines()), text)
555 return ([(rev, False)] * len(text.splitlines()), text)
556
556
557 def with_linenumber(text, rev):
557 def with_linenumber(text, rev):
558 size = len(text.splitlines())
558 size = len(text.splitlines())
559 return ([(rev, i) for i in xrange(1, size + 1)], text)
559 return ([(rev, i) for i in xrange(1, size + 1)], text)
560
560
561 decorate = (((linenumber is None) and decorate_compat) or
561 decorate = (((linenumber is None) and decorate_compat) or
562 (linenumber and with_linenumber) or
562 (linenumber and with_linenumber) or
563 without_linenumber)
563 without_linenumber)
564
564
565 def pair(parent, child):
565 def pair(parent, child):
566 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
566 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
567 refine=True)
567 refine=True)
568 for (a1, a2, b1, b2), t in blocks:
568 for (a1, a2, b1, b2), t in blocks:
569 # Changed blocks ('!') or blocks made only of blank lines ('~')
569 # Changed blocks ('!') or blocks made only of blank lines ('~')
570 # belong to the child.
570 # belong to the child.
571 if t == '=':
571 if t == '=':
572 child[0][b1:b2] = parent[0][a1:a2]
572 child[0][b1:b2] = parent[0][a1:a2]
573 return child
573 return child
574
574
575 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
575 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
576 def getctx(path, fileid):
576 def getctx(path, fileid):
577 log = path == self._path and self._filelog or getlog(path)
577 log = path == self._path and self._filelog or getlog(path)
578 return filectx(self._repo, path, fileid=fileid, filelog=log)
578 return filectx(self._repo, path, fileid=fileid, filelog=log)
579 getctx = util.lrucachefunc(getctx)
579 getctx = util.lrucachefunc(getctx)
580
580
581 def parents(f):
581 def parents(f):
582 # we want to reuse filectx objects as much as possible
582 # we want to reuse filectx objects as much as possible
583 p = f._path
583 p = f._path
584 if f._filerev is None: # working dir
584 if f._filerev is None: # working dir
585 pl = [(n.path(), n.filerev()) for n in f.parents()]
585 pl = [(n.path(), n.filerev()) for n in f.parents()]
586 else:
586 else:
587 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
587 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
588
588
589 if follow:
589 if follow:
590 r = f.renamed()
590 r = f.renamed()
591 if r:
591 if r:
592 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
592 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
593
593
594 return [getctx(p, n) for p, n in pl if n != nullrev]
594 return [getctx(p, n) for p, n in pl if n != nullrev]
595
595
596 # use linkrev to find the first changeset where self appeared
596 # use linkrev to find the first changeset where self appeared
597 if self.rev() != self.linkrev():
597 if self.rev() != self.linkrev():
598 base = self.filectx(self.filerev())
598 base = self.filectx(self.filerev())
599 else:
599 else:
600 base = self
600 base = self
601
601
602 # This algorithm would prefer to be recursive, but Python is a
602 # This algorithm would prefer to be recursive, but Python is a
603 # bit recursion-hostile. Instead we do an iterative
603 # bit recursion-hostile. Instead we do an iterative
604 # depth-first search.
604 # depth-first search.
605
605
606 visit = [base]
606 visit = [base]
607 hist = {}
607 hist = {}
608 pcache = {}
608 pcache = {}
609 needed = {base: 1}
609 needed = {base: 1}
610 while visit:
610 while visit:
611 f = visit[-1]
611 f = visit[-1]
612 if f not in pcache:
612 if f not in pcache:
613 pcache[f] = parents(f)
613 pcache[f] = parents(f)
614
614
615 ready = True
615 ready = True
616 pl = pcache[f]
616 pl = pcache[f]
617 for p in pl:
617 for p in pl:
618 if p not in hist:
618 if p not in hist:
619 ready = False
619 ready = False
620 visit.append(p)
620 visit.append(p)
621 needed[p] = needed.get(p, 0) + 1
621 needed[p] = needed.get(p, 0) + 1
622 if ready:
622 if ready:
623 visit.pop()
623 visit.pop()
624 curr = decorate(f.data(), f)
624 curr = decorate(f.data(), f)
625 for p in pl:
625 for p in pl:
626 curr = pair(hist[p], curr)
626 curr = pair(hist[p], curr)
627 if needed[p] == 1:
627 if needed[p] == 1:
628 del hist[p]
628 del hist[p]
629 else:
629 else:
630 needed[p] -= 1
630 needed[p] -= 1
631
631
632 hist[f] = curr
632 hist[f] = curr
633 pcache[f] = []
633 pcache[f] = []
634
634
635 return zip(hist[base][0], hist[base][1].splitlines(True))
635 return zip(hist[base][0], hist[base][1].splitlines(True))
636
636
637 def ancestor(self, fc2, actx=None):
637 def ancestor(self, fc2, actx=None):
638 """
638 """
639 find the common ancestor file context, if any, of self, and fc2
639 find the common ancestor file context, if any, of self, and fc2
640
640
641 If actx is given, it must be the changectx of the common ancestor
641 If actx is given, it must be the changectx of the common ancestor
642 of self's and fc2's respective changesets.
642 of self's and fc2's respective changesets.
643 """
643 """
644
644
645 if actx is None:
645 if actx is None:
646 actx = self.changectx().ancestor(fc2.changectx())
646 actx = self.changectx().ancestor(fc2.changectx())
647
647
648 # the trivial case: changesets are unrelated, files must be too
648 # the trivial case: changesets are unrelated, files must be too
649 if not actx:
649 if not actx:
650 return None
650 return None
651
651
652 # the easy case: no (relevant) renames
652 # the easy case: no (relevant) renames
653 if fc2.path() == self.path() and self.path() in actx:
653 if fc2.path() == self.path() and self.path() in actx:
654 return actx[self.path()]
654 return actx[self.path()]
655 acache = {}
655 acache = {}
656
656
657 # prime the ancestor cache for the working directory
657 # prime the ancestor cache for the working directory
658 for c in (self, fc2):
658 for c in (self, fc2):
659 if c._filerev is None:
659 if c._filerev is None:
660 pl = [(n.path(), n.filenode()) for n in c.parents()]
660 pl = [(n.path(), n.filenode()) for n in c.parents()]
661 acache[(c._path, None)] = pl
661 acache[(c._path, None)] = pl
662
662
663 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
663 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
664 def parents(vertex):
664 def parents(vertex):
665 if vertex in acache:
665 if vertex in acache:
666 return acache[vertex]
666 return acache[vertex]
667 f, n = vertex
667 f, n = vertex
668 if f not in flcache:
668 if f not in flcache:
669 flcache[f] = self._repo.file(f)
669 flcache[f] = self._repo.file(f)
670 fl = flcache[f]
670 fl = flcache[f]
671 pl = [(f, p) for p in fl.parents(n) if p != nullid]
671 pl = [(f, p) for p in fl.parents(n) if p != nullid]
672 re = fl.renamed(n)
672 re = fl.renamed(n)
673 if re:
673 if re:
674 pl.append(re)
674 pl.append(re)
675 acache[vertex] = pl
675 acache[vertex] = pl
676 return pl
676 return pl
677
677
678 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
678 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
679 v = ancestor.ancestor(a, b, parents)
679 v = ancestor.ancestor(a, b, parents)
680 if v:
680 if v:
681 f, n = v
681 f, n = v
682 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
682 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
683
683
684 return None
684 return None
685
685
686 def ancestors(self, followfirst=False):
686 def ancestors(self, followfirst=False):
687 visit = {}
687 visit = {}
688 c = self
688 c = self
689 cut = followfirst and 1 or None
689 cut = followfirst and 1 or None
690 while True:
690 while True:
691 for parent in c.parents()[:cut]:
691 for parent in c.parents()[:cut]:
692 visit[(parent.rev(), parent.node())] = parent
692 visit[(parent.rev(), parent.node())] = parent
693 if not visit:
693 if not visit:
694 break
694 break
695 c = visit.pop(max(visit))
695 c = visit.pop(max(visit))
696 yield c
696 yield c
697
697
698 class workingctx(changectx):
698 class workingctx(changectx):
699 """A workingctx object makes access to data related to
699 """A workingctx object makes access to data related to
700 the current working directory convenient.
700 the current working directory convenient.
701 date - any valid date string or (unixtime, offset), or None.
701 date - any valid date string or (unixtime, offset), or None.
702 user - username string, or None.
702 user - username string, or None.
703 extra - a dictionary of extra values, or None.
703 extra - a dictionary of extra values, or None.
704 changes - a list of file lists as returned by localrepo.status()
704 changes - a list of file lists as returned by localrepo.status()
705 or None to use the repository status.
705 or None to use the repository status.
706 """
706 """
707 def __init__(self, repo, text="", user=None, date=None, extra=None,
707 def __init__(self, repo, text="", user=None, date=None, extra=None,
708 changes=None):
708 changes=None):
709 self._repo = repo
709 self._repo = repo
710 self._rev = None
710 self._rev = None
711 self._node = None
711 self._node = None
712 self._text = text
712 self._text = text
713 if date:
713 if date:
714 self._date = util.parsedate(date)
714 self._date = util.parsedate(date)
715 if user:
715 if user:
716 self._user = user
716 self._user = user
717 if changes:
717 if changes:
718 self._status = list(changes[:4])
718 self._status = list(changes[:4])
719 self._unknown = changes[4]
719 self._unknown = changes[4]
720 self._ignored = changes[5]
720 self._ignored = changes[5]
721 self._clean = changes[6]
721 self._clean = changes[6]
722 else:
722 else:
723 self._unknown = None
723 self._unknown = None
724 self._ignored = None
724 self._ignored = None
725 self._clean = None
725 self._clean = None
726
726
727 self._extra = {}
727 self._extra = {}
728 if extra:
728 if extra:
729 self._extra = extra.copy()
729 self._extra = extra.copy()
730 if 'branch' not in self._extra:
730 if 'branch' not in self._extra:
731 try:
731 try:
732 branch = encoding.fromlocal(self._repo.dirstate.branch())
732 branch = encoding.fromlocal(self._repo.dirstate.branch())
733 except UnicodeDecodeError:
733 except UnicodeDecodeError:
734 raise util.Abort(_('branch name not in UTF-8!'))
734 raise util.Abort(_('branch name not in UTF-8!'))
735 self._extra['branch'] = branch
735 self._extra['branch'] = branch
736 if self._extra['branch'] == '':
736 if self._extra['branch'] == '':
737 self._extra['branch'] = 'default'
737 self._extra['branch'] = 'default'
738
738
739 def __str__(self):
739 def __str__(self):
740 return str(self._parents[0]) + "+"
740 return str(self._parents[0]) + "+"
741
741
742 def __repr__(self):
742 def __repr__(self):
743 return "<workingctx %s>" % str(self)
743 return "<workingctx %s>" % str(self)
744
744
745 def __nonzero__(self):
745 def __nonzero__(self):
746 return True
746 return True
747
747
748 def __contains__(self, key):
748 def __contains__(self, key):
749 return self._repo.dirstate[key] not in "?r"
749 return self._repo.dirstate[key] not in "?r"
750
750
751 def _buildflagfunc(self):
751 def _buildflagfunc(self):
752 # Create a fallback function for getting file flags when the
752 # Create a fallback function for getting file flags when the
753 # filesystem doesn't support them
753 # filesystem doesn't support them
754
754
755 copiesget = self._repo.dirstate.copies().get
755 copiesget = self._repo.dirstate.copies().get
756
756
757 if len(self._parents) < 2:
757 if len(self._parents) < 2:
758 # when we have one parent, it's easy: copy from parent
758 # when we have one parent, it's easy: copy from parent
759 man = self._parents[0].manifest()
759 man = self._parents[0].manifest()
760 def func(f):
760 def func(f):
761 f = copiesget(f, f)
761 f = copiesget(f, f)
762 return man.flags(f)
762 return man.flags(f)
763 else:
763 else:
764 # merges are tricky: we try to reconstruct the unstored
764 # merges are tricky: we try to reconstruct the unstored
765 # result from the merge (issue1802)
765 # result from the merge (issue1802)
766 p1, p2 = self._parents
766 p1, p2 = self._parents
767 pa = p1.ancestor(p2)
767 pa = p1.ancestor(p2)
768 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
768 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
769
769
770 def func(f):
770 def func(f):
771 f = copiesget(f, f) # may be wrong for merges with copies
771 f = copiesget(f, f) # may be wrong for merges with copies
772 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
772 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
773 if fl1 == fl2:
773 if fl1 == fl2:
774 return fl1
774 return fl1
775 if fl1 == fla:
775 if fl1 == fla:
776 return fl2
776 return fl2
777 if fl2 == fla:
777 if fl2 == fla:
778 return fl1
778 return fl1
779 return '' # punt for conflicts
779 return '' # punt for conflicts
780
780
781 return func
781 return func
782
782
783 @propertycache
783 @propertycache
784 def _flagfunc(self):
784 def _flagfunc(self):
785 return self._repo.dirstate.flagfunc(self._buildflagfunc)
785 return self._repo.dirstate.flagfunc(self._buildflagfunc)
786
786
787 @propertycache
787 @propertycache
788 def _manifest(self):
788 def _manifest(self):
789 """generate a manifest corresponding to the working directory"""
789 """generate a manifest corresponding to the working directory"""
790
790
791 man = self._parents[0].manifest().copy()
791 man = self._parents[0].manifest().copy()
792 if len(self._parents) > 1:
792 if len(self._parents) > 1:
793 man2 = self.p2().manifest()
793 man2 = self.p2().manifest()
794 def getman(f):
794 def getman(f):
795 if f in man:
795 if f in man:
796 return man
796 return man
797 return man2
797 return man2
798 else:
798 else:
799 getman = lambda f: man
799 getman = lambda f: man
800
800
801 copied = self._repo.dirstate.copies()
801 copied = self._repo.dirstate.copies()
802 ff = self._flagfunc
802 ff = self._flagfunc
803 modified, added, removed, deleted = self._status
803 modified, added, removed, deleted = self._status
804 for i, l in (("a", added), ("m", modified)):
804 for i, l in (("a", added), ("m", modified)):
805 for f in l:
805 for f in l:
806 orig = copied.get(f, f)
806 orig = copied.get(f, f)
807 man[f] = getman(orig).get(orig, nullid) + i
807 man[f] = getman(orig).get(orig, nullid) + i
808 try:
808 try:
809 man.set(f, ff(f))
809 man.set(f, ff(f))
810 except OSError:
810 except OSError:
811 pass
811 pass
812
812
813 for f in deleted + removed:
813 for f in deleted + removed:
814 if f in man:
814 if f in man:
815 del man[f]
815 del man[f]
816
816
817 return man
817 return man
818
818
819 def __iter__(self):
819 def __iter__(self):
820 d = self._repo.dirstate
820 d = self._repo.dirstate
821 for f in d:
821 for f in d:
822 if d[f] != 'r':
822 if d[f] != 'r':
823 yield f
823 yield f
824
824
825 @propertycache
825 @propertycache
826 def _status(self):
826 def _status(self):
827 return self._repo.status()[:4]
827 return self._repo.status()[:4]
828
828
829 @propertycache
829 @propertycache
830 def _user(self):
830 def _user(self):
831 return self._repo.ui.username()
831 return self._repo.ui.username()
832
832
833 @propertycache
833 @propertycache
834 def _date(self):
834 def _date(self):
835 return util.makedate()
835 return util.makedate()
836
836
837 @propertycache
837 @propertycache
838 def _parents(self):
838 def _parents(self):
839 p = self._repo.dirstate.parents()
839 p = self._repo.dirstate.parents()
840 if p[1] == nullid:
840 if p[1] == nullid:
841 p = p[:-1]
841 p = p[:-1]
842 self._parents = [changectx(self._repo, x) for x in p]
842 self._parents = [changectx(self._repo, x) for x in p]
843 return self._parents
843 return self._parents
844
844
845 def status(self, ignored=False, clean=False, unknown=False):
845 def status(self, ignored=False, clean=False, unknown=False):
846 """Explicit status query
846 """Explicit status query
847 Unless this method is used to query the working copy status, the
847 Unless this method is used to query the working copy status, the
848 _status property will implicitly read the status using its default
848 _status property will implicitly read the status using its default
849 arguments."""
849 arguments."""
850 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
850 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
851 self._unknown = self._ignored = self._clean = None
851 self._unknown = self._ignored = self._clean = None
852 if unknown:
852 if unknown:
853 self._unknown = stat[4]
853 self._unknown = stat[4]
854 if ignored:
854 if ignored:
855 self._ignored = stat[5]
855 self._ignored = stat[5]
856 if clean:
856 if clean:
857 self._clean = stat[6]
857 self._clean = stat[6]
858 self._status = stat[:4]
858 self._status = stat[:4]
859 return stat
859 return stat
860
860
861 def manifest(self):
861 def manifest(self):
862 return self._manifest
862 return self._manifest
863 def user(self):
863 def user(self):
864 return self._user or self._repo.ui.username()
864 return self._user or self._repo.ui.username()
865 def date(self):
865 def date(self):
866 return self._date
866 return self._date
867 def description(self):
867 def description(self):
868 return self._text
868 return self._text
869 def files(self):
869 def files(self):
870 return sorted(self._status[0] + self._status[1] + self._status[2])
870 return sorted(self._status[0] + self._status[1] + self._status[2])
871
871
872 def modified(self):
872 def modified(self):
873 return self._status[0]
873 return self._status[0]
874 def added(self):
874 def added(self):
875 return self._status[1]
875 return self._status[1]
876 def removed(self):
876 def removed(self):
877 return self._status[2]
877 return self._status[2]
878 def deleted(self):
878 def deleted(self):
879 return self._status[3]
879 return self._status[3]
880 def unknown(self):
880 def unknown(self):
881 assert self._unknown is not None # must call status first
881 assert self._unknown is not None # must call status first
882 return self._unknown
882 return self._unknown
883 def ignored(self):
883 def ignored(self):
884 assert self._ignored is not None # must call status first
884 assert self._ignored is not None # must call status first
885 return self._ignored
885 return self._ignored
886 def clean(self):
886 def clean(self):
887 assert self._clean is not None # must call status first
887 assert self._clean is not None # must call status first
888 return self._clean
888 return self._clean
889 def branch(self):
889 def branch(self):
890 return encoding.tolocal(self._extra['branch'])
890 return encoding.tolocal(self._extra['branch'])
891 def extra(self):
891 def extra(self):
892 return self._extra
892 return self._extra
893
893
894 def tags(self):
894 def tags(self):
895 t = []
895 t = []
896 for p in self.parents():
896 for p in self.parents():
897 t.extend(p.tags())
897 t.extend(p.tags())
898 return t
898 return t
899
899
900 def bookmarks(self):
900 def bookmarks(self):
901 b = []
901 b = []
902 for p in self.parents():
902 for p in self.parents():
903 b.extend(p.bookmarks())
903 b.extend(p.bookmarks())
904 return b
904 return b
905
905
906 def phase(self):
906 def phase(self):
907 phase = phases.draft # default phase to draft
907 phase = phases.draft # default phase to draft
908 for p in self.parents():
908 for p in self.parents():
909 phase = max(phase, p.phase())
909 phase = max(phase, p.phase())
910 return phase
910 return phase
911
911
912 def hidden(self):
912 def hidden(self):
913 return False
913 return False
914
914
915 def children(self):
915 def children(self):
916 return []
916 return []
917
917
918 def flags(self, path):
918 def flags(self, path):
919 if '_manifest' in self.__dict__:
919 if '_manifest' in self.__dict__:
920 try:
920 try:
921 return self._manifest.flags(path)
921 return self._manifest.flags(path)
922 except KeyError:
922 except KeyError:
923 return ''
923 return ''
924
924
925 try:
925 try:
926 return self._flagfunc(path)
926 return self._flagfunc(path)
927 except OSError:
927 except OSError:
928 return ''
928 return ''
929
929
930 def filectx(self, path, filelog=None):
930 def filectx(self, path, filelog=None):
931 """get a file context from the working directory"""
931 """get a file context from the working directory"""
932 return workingfilectx(self._repo, path, workingctx=self,
932 return workingfilectx(self._repo, path, workingctx=self,
933 filelog=filelog)
933 filelog=filelog)
934
934
935 def ancestor(self, c2):
935 def ancestor(self, c2):
936 """return the ancestor context of self and c2"""
936 """return the ancestor context of self and c2"""
937 return self._parents[0].ancestor(c2) # punt on two parents for now
937 return self._parents[0].ancestor(c2) # punt on two parents for now
938
938
939 def walk(self, match):
939 def walk(self, match):
940 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
940 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
941 True, False))
941 True, False))
942
942
943 def dirty(self, missing=False):
943 def dirty(self, missing=False):
944 "check whether a working directory is modified"
944 "check whether a working directory is modified"
945 # check subrepos first
945 # check subrepos first
946 for s in self.substate:
946 for s in self.substate:
947 if self.sub(s).dirty():
947 if self.sub(s).dirty():
948 return True
948 return True
949 # check current working dir
949 # check current working dir
950 return (self.p2() or self.branch() != self.p1().branch() or
950 return (self.p2() or self.branch() != self.p1().branch() or
951 self.modified() or self.added() or self.removed() or
951 self.modified() or self.added() or self.removed() or
952 (missing and self.deleted()))
952 (missing and self.deleted()))
953
953
954 def add(self, list, prefix=""):
954 def add(self, list, prefix=""):
955 join = lambda f: os.path.join(prefix, f)
955 join = lambda f: os.path.join(prefix, f)
956 wlock = self._repo.wlock()
956 wlock = self._repo.wlock()
957 ui, ds = self._repo.ui, self._repo.dirstate
957 ui, ds = self._repo.ui, self._repo.dirstate
958 try:
958 try:
959 rejected = []
959 rejected = []
960 for f in list:
960 for f in list:
961 scmutil.checkportable(ui, join(f))
961 scmutil.checkportable(ui, join(f))
962 p = self._repo.wjoin(f)
962 p = self._repo.wjoin(f)
963 try:
963 try:
964 st = os.lstat(p)
964 st = os.lstat(p)
965 except OSError:
965 except OSError:
966 ui.warn(_("%s does not exist!\n") % join(f))
966 ui.warn(_("%s does not exist!\n") % join(f))
967 rejected.append(f)
967 rejected.append(f)
968 continue
968 continue
969 if st.st_size > 10000000:
969 if st.st_size > 10000000:
970 ui.warn(_("%s: up to %d MB of RAM may be required "
970 ui.warn(_("%s: up to %d MB of RAM may be required "
971 "to manage this file\n"
971 "to manage this file\n"
972 "(use 'hg revert %s' to cancel the "
972 "(use 'hg revert %s' to cancel the "
973 "pending addition)\n")
973 "pending addition)\n")
974 % (f, 3 * st.st_size // 1000000, join(f)))
974 % (f, 3 * st.st_size // 1000000, join(f)))
975 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
975 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
976 ui.warn(_("%s not added: only files and symlinks "
976 ui.warn(_("%s not added: only files and symlinks "
977 "supported currently\n") % join(f))
977 "supported currently\n") % join(f))
978 rejected.append(p)
978 rejected.append(p)
979 elif ds[f] in 'amn':
979 elif ds[f] in 'amn':
980 ui.warn(_("%s already tracked!\n") % join(f))
980 ui.warn(_("%s already tracked!\n") % join(f))
981 elif ds[f] == 'r':
981 elif ds[f] == 'r':
982 ds.normallookup(f)
982 ds.normallookup(f)
983 else:
983 else:
984 ds.add(f)
984 ds.add(f)
985 return rejected
985 return rejected
986 finally:
986 finally:
987 wlock.release()
987 wlock.release()
988
988
989 def forget(self, files, prefix=""):
989 def forget(self, files, prefix=""):
990 join = lambda f: os.path.join(prefix, f)
990 join = lambda f: os.path.join(prefix, f)
991 wlock = self._repo.wlock()
991 wlock = self._repo.wlock()
992 try:
992 try:
993 rejected = []
993 rejected = []
994 for f in files:
994 for f in files:
995 if f not in self._repo.dirstate:
995 if f not in self._repo.dirstate:
996 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
996 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
997 rejected.append(f)
997 rejected.append(f)
998 elif self._repo.dirstate[f] != 'a':
998 elif self._repo.dirstate[f] != 'a':
999 self._repo.dirstate.remove(f)
999 self._repo.dirstate.remove(f)
1000 else:
1000 else:
1001 self._repo.dirstate.drop(f)
1001 self._repo.dirstate.drop(f)
1002 return rejected
1002 return rejected
1003 finally:
1003 finally:
1004 wlock.release()
1004 wlock.release()
1005
1005
1006 def ancestors(self, followfirst=False):
1006 def ancestors(self, followfirst=False):
1007 cut = followfirst and 1 or None
1007 cut = followfirst and 1 or None
1008 for a in self._repo.changelog.ancestors(
1008 for a in self._repo.changelog.ancestors(
1009 *[p.rev() for p in self._parents[:cut]]):
1009 *[p.rev() for p in self._parents[:cut]]):
1010 yield changectx(self._repo, a)
1010 yield changectx(self._repo, a)
1011
1011
1012 def undelete(self, list):
1012 def undelete(self, list):
1013 pctxs = self.parents()
1013 pctxs = self.parents()
1014 wlock = self._repo.wlock()
1014 wlock = self._repo.wlock()
1015 try:
1015 try:
1016 for f in list:
1016 for f in list:
1017 if self._repo.dirstate[f] != 'r':
1017 if self._repo.dirstate[f] != 'r':
1018 self._repo.ui.warn(_("%s not removed!\n") % f)
1018 self._repo.ui.warn(_("%s not removed!\n") % f)
1019 else:
1019 else:
1020 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1020 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1021 t = fctx.data()
1021 t = fctx.data()
1022 self._repo.wwrite(f, t, fctx.flags())
1022 self._repo.wwrite(f, t, fctx.flags())
1023 self._repo.dirstate.normal(f)
1023 self._repo.dirstate.normal(f)
1024 finally:
1024 finally:
1025 wlock.release()
1025 wlock.release()
1026
1026
1027 def copy(self, source, dest):
1027 def copy(self, source, dest):
1028 p = self._repo.wjoin(dest)
1028 p = self._repo.wjoin(dest)
1029 if not os.path.lexists(p):
1029 if not os.path.lexists(p):
1030 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1030 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1031 elif not (os.path.isfile(p) or os.path.islink(p)):
1031 elif not (os.path.isfile(p) or os.path.islink(p)):
1032 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1032 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1033 "symbolic link\n") % dest)
1033 "symbolic link\n") % dest)
1034 else:
1034 else:
1035 wlock = self._repo.wlock()
1035 wlock = self._repo.wlock()
1036 try:
1036 try:
1037 if self._repo.dirstate[dest] in '?r':
1037 if self._repo.dirstate[dest] in '?r':
1038 self._repo.dirstate.add(dest)
1038 self._repo.dirstate.add(dest)
1039 self._repo.dirstate.copy(source, dest)
1039 self._repo.dirstate.copy(source, dest)
1040 finally:
1040 finally:
1041 wlock.release()
1041 wlock.release()
1042
1042
1043 def dirs(self):
1043 def dirs(self):
1044 return self._repo.dirstate.dirs()
1044 return self._repo.dirstate.dirs()
1045
1045
1046 class workingfilectx(filectx):
1046 class workingfilectx(filectx):
1047 """A workingfilectx object makes access to data related to a particular
1047 """A workingfilectx object makes access to data related to a particular
1048 file in the working directory convenient."""
1048 file in the working directory convenient."""
1049 def __init__(self, repo, path, filelog=None, workingctx=None):
1049 def __init__(self, repo, path, filelog=None, workingctx=None):
1050 """changeid can be a changeset revision, node, or tag.
1050 """changeid can be a changeset revision, node, or tag.
1051 fileid can be a file revision or node."""
1051 fileid can be a file revision or node."""
1052 self._repo = repo
1052 self._repo = repo
1053 self._path = path
1053 self._path = path
1054 self._changeid = None
1054 self._changeid = None
1055 self._filerev = self._filenode = None
1055 self._filerev = self._filenode = None
1056
1056
1057 if filelog:
1057 if filelog:
1058 self._filelog = filelog
1058 self._filelog = filelog
1059 if workingctx:
1059 if workingctx:
1060 self._changectx = workingctx
1060 self._changectx = workingctx
1061
1061
1062 @propertycache
1062 @propertycache
1063 def _changectx(self):
1063 def _changectx(self):
1064 return workingctx(self._repo)
1064 return workingctx(self._repo)
1065
1065
1066 def __nonzero__(self):
1066 def __nonzero__(self):
1067 return True
1067 return True
1068
1068
1069 def __str__(self):
1069 def __str__(self):
1070 return "%s@%s" % (self.path(), self._changectx)
1070 return "%s@%s" % (self.path(), self._changectx)
1071
1071
1072 def __repr__(self):
1072 def __repr__(self):
1073 return "<workingfilectx %s>" % str(self)
1073 return "<workingfilectx %s>" % str(self)
1074
1074
1075 def data(self):
1075 def data(self):
1076 return self._repo.wread(self._path)
1076 return self._repo.wread(self._path)
1077 def renamed(self):
1077 def renamed(self):
1078 rp = self._repo.dirstate.copied(self._path)
1078 rp = self._repo.dirstate.copied(self._path)
1079 if not rp:
1079 if not rp:
1080 return None
1080 return None
1081 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1081 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1082
1082
1083 def parents(self):
1083 def parents(self):
1084 '''return parent filectxs, following copies if necessary'''
1084 '''return parent filectxs, following copies if necessary'''
1085 def filenode(ctx, path):
1085 def filenode(ctx, path):
1086 return ctx._manifest.get(path, nullid)
1086 return ctx._manifest.get(path, nullid)
1087
1087
1088 path = self._path
1088 path = self._path
1089 fl = self._filelog
1089 fl = self._filelog
1090 pcl = self._changectx._parents
1090 pcl = self._changectx._parents
1091 renamed = self.renamed()
1091 renamed = self.renamed()
1092
1092
1093 if renamed:
1093 if renamed:
1094 pl = [renamed + (None,)]
1094 pl = [renamed + (None,)]
1095 else:
1095 else:
1096 pl = [(path, filenode(pcl[0], path), fl)]
1096 pl = [(path, filenode(pcl[0], path), fl)]
1097
1097
1098 for pc in pcl[1:]:
1098 for pc in pcl[1:]:
1099 pl.append((path, filenode(pc, path), fl))
1099 pl.append((path, filenode(pc, path), fl))
1100
1100
1101 return [filectx(self._repo, p, fileid=n, filelog=l)
1101 return [filectx(self._repo, p, fileid=n, filelog=l)
1102 for p, n, l in pl if n != nullid]
1102 for p, n, l in pl if n != nullid]
1103
1103
1104 def children(self):
1104 def children(self):
1105 return []
1105 return []
1106
1106
1107 def size(self):
1107 def size(self):
1108 return os.lstat(self._repo.wjoin(self._path)).st_size
1108 return os.lstat(self._repo.wjoin(self._path)).st_size
1109 def date(self):
1109 def date(self):
1110 t, tz = self._changectx.date()
1110 t, tz = self._changectx.date()
1111 try:
1111 try:
1112 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1112 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1113 except OSError, err:
1113 except OSError, err:
1114 if err.errno != errno.ENOENT:
1114 if err.errno != errno.ENOENT:
1115 raise
1115 raise
1116 return (t, tz)
1116 return (t, tz)
1117
1117
1118 def cmp(self, fctx):
1118 def cmp(self, fctx):
1119 """compare with other file context
1119 """compare with other file context
1120
1120
1121 returns True if different than fctx.
1121 returns True if different than fctx.
1122 """
1122 """
1123 # fctx should be a filectx (not a wfctx)
1123 # fctx should be a filectx (not a wfctx)
1124 # invert comparison to reuse the same code path
1124 # invert comparison to reuse the same code path
1125 return fctx.cmp(self)
1125 return fctx.cmp(self)
1126
1126
1127 class memctx(object):
1127 class memctx(object):
1128 """Use memctx to perform in-memory commits via localrepo.commitctx().
1128 """Use memctx to perform in-memory commits via localrepo.commitctx().
1129
1129
1130 Revision information is supplied at initialization time while
1130 Revision information is supplied at initialization time while
1131 related files data and is made available through a callback
1131 related files data and is made available through a callback
1132 mechanism. 'repo' is the current localrepo, 'parents' is a
1132 mechanism. 'repo' is the current localrepo, 'parents' is a
1133 sequence of two parent revisions identifiers (pass None for every
1133 sequence of two parent revisions identifiers (pass None for every
1134 missing parent), 'text' is the commit message and 'files' lists
1134 missing parent), 'text' is the commit message and 'files' lists
1135 names of files touched by the revision (normalized and relative to
1135 names of files touched by the revision (normalized and relative to
1136 repository root).
1136 repository root).
1137
1137
1138 filectxfn(repo, memctx, path) is a callable receiving the
1138 filectxfn(repo, memctx, path) is a callable receiving the
1139 repository, the current memctx object and the normalized path of
1139 repository, the current memctx object and the normalized path of
1140 requested file, relative to repository root. It is fired by the
1140 requested file, relative to repository root. It is fired by the
1141 commit function for every file in 'files', but calls order is
1141 commit function for every file in 'files', but calls order is
1142 undefined. If the file is available in the revision being
1142 undefined. If the file is available in the revision being
1143 committed (updated or added), filectxfn returns a memfilectx
1143 committed (updated or added), filectxfn returns a memfilectx
1144 object. If the file was removed, filectxfn raises an
1144 object. If the file was removed, filectxfn raises an
1145 IOError. Moved files are represented by marking the source file
1145 IOError. Moved files are represented by marking the source file
1146 removed and the new file added with copy information (see
1146 removed and the new file added with copy information (see
1147 memfilectx).
1147 memfilectx).
1148
1148
1149 user receives the committer name and defaults to current
1149 user receives the committer name and defaults to current
1150 repository username, date is the commit date in any format
1150 repository username, date is the commit date in any format
1151 supported by util.parsedate() and defaults to current date, extra
1151 supported by util.parsedate() and defaults to current date, extra
1152 is a dictionary of metadata or is left empty.
1152 is a dictionary of metadata or is left empty.
1153 """
1153 """
1154 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1154 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1155 date=None, extra=None):
1155 date=None, extra=None):
1156 self._repo = repo
1156 self._repo = repo
1157 self._rev = None
1157 self._rev = None
1158 self._node = None
1158 self._node = None
1159 self._text = text
1159 self._text = text
1160 self._date = date and util.parsedate(date) or util.makedate()
1160 self._date = date and util.parsedate(date) or util.makedate()
1161 self._user = user
1161 self._user = user
1162 parents = [(p or nullid) for p in parents]
1162 parents = [(p or nullid) for p in parents]
1163 p1, p2 = parents
1163 p1, p2 = parents
1164 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1164 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1165 files = sorted(set(files))
1165 files = sorted(set(files))
1166 self._status = [files, [], [], [], []]
1166 self._status = [files, [], [], [], []]
1167 self._filectxfn = filectxfn
1167 self._filectxfn = filectxfn
1168
1168
1169 self._extra = extra and extra.copy() or {}
1169 self._extra = extra and extra.copy() or {}
1170 if self._extra.get('branch', '') == '':
1170 if self._extra.get('branch', '') == '':
1171 self._extra['branch'] = 'default'
1171 self._extra['branch'] = 'default'
1172
1172
1173 def __str__(self):
1173 def __str__(self):
1174 return str(self._parents[0]) + "+"
1174 return str(self._parents[0]) + "+"
1175
1175
1176 def __int__(self):
1176 def __int__(self):
1177 return self._rev
1177 return self._rev
1178
1178
1179 def __nonzero__(self):
1179 def __nonzero__(self):
1180 return True
1180 return True
1181
1181
1182 def __getitem__(self, key):
1182 def __getitem__(self, key):
1183 return self.filectx(key)
1183 return self.filectx(key)
1184
1184
1185 def p1(self):
1185 def p1(self):
1186 return self._parents[0]
1186 return self._parents[0]
1187 def p2(self):
1187 def p2(self):
1188 return self._parents[1]
1188 return self._parents[1]
1189
1189
1190 def user(self):
1190 def user(self):
1191 return self._user or self._repo.ui.username()
1191 return self._user or self._repo.ui.username()
1192 def date(self):
1192 def date(self):
1193 return self._date
1193 return self._date
1194 def description(self):
1194 def description(self):
1195 return self._text
1195 return self._text
1196 def files(self):
1196 def files(self):
1197 return self.modified()
1197 return self.modified()
1198 def modified(self):
1198 def modified(self):
1199 return self._status[0]
1199 return self._status[0]
1200 def added(self):
1200 def added(self):
1201 return self._status[1]
1201 return self._status[1]
1202 def removed(self):
1202 def removed(self):
1203 return self._status[2]
1203 return self._status[2]
1204 def deleted(self):
1204 def deleted(self):
1205 return self._status[3]
1205 return self._status[3]
1206 def unknown(self):
1206 def unknown(self):
1207 return self._status[4]
1207 return self._status[4]
1208 def ignored(self):
1208 def ignored(self):
1209 return self._status[5]
1209 return self._status[5]
1210 def clean(self):
1210 def clean(self):
1211 return self._status[6]
1211 return self._status[6]
1212 def branch(self):
1212 def branch(self):
1213 return encoding.tolocal(self._extra['branch'])
1213 return encoding.tolocal(self._extra['branch'])
1214 def extra(self):
1214 def extra(self):
1215 return self._extra
1215 return self._extra
1216 def flags(self, f):
1216 def flags(self, f):
1217 return self[f].flags()
1217 return self[f].flags()
1218
1218
1219 def parents(self):
1219 def parents(self):
1220 """return contexts for each parent changeset"""
1220 """return contexts for each parent changeset"""
1221 return self._parents
1221 return self._parents
1222
1222
1223 def filectx(self, path, filelog=None):
1223 def filectx(self, path, filelog=None):
1224 """get a file context from the working directory"""
1224 """get a file context from the working directory"""
1225 return self._filectxfn(self._repo, self, path)
1225 return self._filectxfn(self._repo, self, path)
1226
1226
1227 def commit(self):
1227 def commit(self):
1228 """commit context to the repo"""
1228 """commit context to the repo"""
1229 return self._repo.commitctx(self)
1229 return self._repo.commitctx(self)
1230
1230
1231 class memfilectx(object):
1231 class memfilectx(object):
1232 """memfilectx represents an in-memory file to commit.
1232 """memfilectx represents an in-memory file to commit.
1233
1233
1234 See memctx for more details.
1234 See memctx for more details.
1235 """
1235 """
1236 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1236 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1237 """
1237 """
1238 path is the normalized file path relative to repository root.
1238 path is the normalized file path relative to repository root.
1239 data is the file content as a string.
1239 data is the file content as a string.
1240 islink is True if the file is a symbolic link.
1240 islink is True if the file is a symbolic link.
1241 isexec is True if the file is executable.
1241 isexec is True if the file is executable.
1242 copied is the source file path if current file was copied in the
1242 copied is the source file path if current file was copied in the
1243 revision being committed, or None."""
1243 revision being committed, or None."""
1244 self._path = path
1244 self._path = path
1245 self._data = data
1245 self._data = data
1246 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1246 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1247 self._copied = None
1247 self._copied = None
1248 if copied:
1248 if copied:
1249 self._copied = (copied, nullid)
1249 self._copied = (copied, nullid)
1250
1250
1251 def __nonzero__(self):
1251 def __nonzero__(self):
1252 return True
1252 return True
1253 def __str__(self):
1253 def __str__(self):
1254 return "%s@%s" % (self.path(), self._changectx)
1254 return "%s@%s" % (self.path(), self._changectx)
1255 def path(self):
1255 def path(self):
1256 return self._path
1256 return self._path
1257 def data(self):
1257 def data(self):
1258 return self._data
1258 return self._data
1259 def flags(self):
1259 def flags(self):
1260 return self._flags
1260 return self._flags
1261 def isexec(self):
1261 def isexec(self):
1262 return 'x' in self._flags
1262 return 'x' in self._flags
1263 def islink(self):
1263 def islink(self):
1264 return 'l' in self._flags
1264 return 'l' in self._flags
1265 def renamed(self):
1265 def renamed(self):
1266 return self._copied
1266 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now