##// END OF EJS Templates
dirstate.walk: eliminate src from yield...
Matt Mackall -
r6818:6e93fbd8 default
parent child Browse files
Show More
@@ -1,774 +1,774
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullid, nullrev, short, hex
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, revlog, util, os, errno
10 import ancestor, bdiff, revlog, util, os, errno
11
11
12 class changectx(object):
12 class changectx(object):
13 """A changecontext object makes access to data related to a particular
13 """A changecontext object makes access to data related to a particular
14 changeset convenient."""
14 changeset convenient."""
15 def __init__(self, repo, changeid=''):
15 def __init__(self, repo, changeid=''):
16 """changeid is a revision number, node, or tag"""
16 """changeid is a revision number, node, or tag"""
17 if changeid == '':
17 if changeid == '':
18 changeid = '.'
18 changeid = '.'
19 self._repo = repo
19 self._repo = repo
20 self._node = self._repo.lookup(changeid)
20 self._node = self._repo.lookup(changeid)
21 self._rev = self._repo.changelog.rev(self._node)
21 self._rev = self._repo.changelog.rev(self._node)
22
22
23 def __str__(self):
23 def __str__(self):
24 return short(self.node())
24 return short(self.node())
25
25
26 def __int__(self):
26 def __int__(self):
27 return self.rev()
27 return self.rev()
28
28
29 def __repr__(self):
29 def __repr__(self):
30 return "<changectx %s>" % str(self)
30 return "<changectx %s>" % str(self)
31
31
32 def __hash__(self):
32 def __hash__(self):
33 try:
33 try:
34 return hash(self._rev)
34 return hash(self._rev)
35 except AttributeError:
35 except AttributeError:
36 return id(self)
36 return id(self)
37
37
38 def __eq__(self, other):
38 def __eq__(self, other):
39 try:
39 try:
40 return self._rev == other._rev
40 return self._rev == other._rev
41 except AttributeError:
41 except AttributeError:
42 return False
42 return False
43
43
44 def __ne__(self, other):
44 def __ne__(self, other):
45 return not (self == other)
45 return not (self == other)
46
46
47 def __nonzero__(self):
47 def __nonzero__(self):
48 return self._rev != nullrev
48 return self._rev != nullrev
49
49
50 def __getattr__(self, name):
50 def __getattr__(self, name):
51 if name == '_changeset':
51 if name == '_changeset':
52 self._changeset = self._repo.changelog.read(self.node())
52 self._changeset = self._repo.changelog.read(self.node())
53 return self._changeset
53 return self._changeset
54 elif name == '_manifest':
54 elif name == '_manifest':
55 self._manifest = self._repo.manifest.read(self._changeset[0])
55 self._manifest = self._repo.manifest.read(self._changeset[0])
56 return self._manifest
56 return self._manifest
57 elif name == '_manifestdelta':
57 elif name == '_manifestdelta':
58 md = self._repo.manifest.readdelta(self._changeset[0])
58 md = self._repo.manifest.readdelta(self._changeset[0])
59 self._manifestdelta = md
59 self._manifestdelta = md
60 return self._manifestdelta
60 return self._manifestdelta
61 elif name == '_parents':
61 elif name == '_parents':
62 p = self._repo.changelog.parents(self._node)
62 p = self._repo.changelog.parents(self._node)
63 if p[1] == nullid:
63 if p[1] == nullid:
64 p = p[:-1]
64 p = p[:-1]
65 self._parents = [changectx(self._repo, x) for x in p]
65 self._parents = [changectx(self._repo, x) for x in p]
66 return self._parents
66 return self._parents
67 else:
67 else:
68 raise AttributeError, name
68 raise AttributeError, name
69
69
70 def __contains__(self, key):
70 def __contains__(self, key):
71 return key in self._manifest
71 return key in self._manifest
72
72
73 def __getitem__(self, key):
73 def __getitem__(self, key):
74 return self.filectx(key)
74 return self.filectx(key)
75
75
76 def __iter__(self):
76 def __iter__(self):
77 for f in util.sort(self._manifest):
77 for f in util.sort(self._manifest):
78 yield f
78 yield f
79
79
80 def changeset(self): return self._changeset
80 def changeset(self): return self._changeset
81 def manifest(self): return self._manifest
81 def manifest(self): return self._manifest
82
82
83 def rev(self): return self._rev
83 def rev(self): return self._rev
84 def node(self): return self._node
84 def node(self): return self._node
85 def hex(self): return hex(self._node)
85 def hex(self): return hex(self._node)
86 def user(self): return self._changeset[1]
86 def user(self): return self._changeset[1]
87 def date(self): return self._changeset[2]
87 def date(self): return self._changeset[2]
88 def files(self): return self._changeset[3]
88 def files(self): return self._changeset[3]
89 def description(self): return self._changeset[4]
89 def description(self): return self._changeset[4]
90 def branch(self): return self._changeset[5].get("branch")
90 def branch(self): return self._changeset[5].get("branch")
91 def extra(self): return self._changeset[5]
91 def extra(self): return self._changeset[5]
92 def tags(self): return self._repo.nodetags(self._node)
92 def tags(self): return self._repo.nodetags(self._node)
93
93
94 def parents(self):
94 def parents(self):
95 """return contexts for each parent changeset"""
95 """return contexts for each parent changeset"""
96 return self._parents
96 return self._parents
97
97
98 def children(self):
98 def children(self):
99 """return contexts for each child changeset"""
99 """return contexts for each child changeset"""
100 c = self._repo.changelog.children(self._node)
100 c = self._repo.changelog.children(self._node)
101 return [changectx(self._repo, x) for x in c]
101 return [changectx(self._repo, x) for x in c]
102
102
103 def _fileinfo(self, path):
103 def _fileinfo(self, path):
104 if '_manifest' in self.__dict__:
104 if '_manifest' in self.__dict__:
105 try:
105 try:
106 return self._manifest[path], self._manifest.flags(path)
106 return self._manifest[path], self._manifest.flags(path)
107 except KeyError:
107 except KeyError:
108 raise revlog.LookupError(self._node, path,
108 raise revlog.LookupError(self._node, path,
109 _('not found in manifest'))
109 _('not found in manifest'))
110 if '_manifestdelta' in self.__dict__ or path in self.files():
110 if '_manifestdelta' in self.__dict__ or path in self.files():
111 if path in self._manifestdelta:
111 if path in self._manifestdelta:
112 return self._manifestdelta[path], self._manifestdelta.flags(path)
112 return self._manifestdelta[path], self._manifestdelta.flags(path)
113 node, flag = self._repo.manifest.find(self._changeset[0], path)
113 node, flag = self._repo.manifest.find(self._changeset[0], path)
114 if not node:
114 if not node:
115 raise revlog.LookupError(self._node, path,
115 raise revlog.LookupError(self._node, path,
116 _('not found in manifest'))
116 _('not found in manifest'))
117
117
118 return node, flag
118 return node, flag
119
119
120 def filenode(self, path):
120 def filenode(self, path):
121 return self._fileinfo(path)[0]
121 return self._fileinfo(path)[0]
122
122
123 def flags(self, path):
123 def flags(self, path):
124 try:
124 try:
125 return self._fileinfo(path)[1]
125 return self._fileinfo(path)[1]
126 except revlog.LookupError:
126 except revlog.LookupError:
127 return ''
127 return ''
128
128
129 def filectx(self, path, fileid=None, filelog=None):
129 def filectx(self, path, fileid=None, filelog=None):
130 """get a file context from this changeset"""
130 """get a file context from this changeset"""
131 if fileid is None:
131 if fileid is None:
132 fileid = self.filenode(path)
132 fileid = self.filenode(path)
133 return filectx(self._repo, path, fileid=fileid,
133 return filectx(self._repo, path, fileid=fileid,
134 changectx=self, filelog=filelog)
134 changectx=self, filelog=filelog)
135
135
136 def filectxs(self):
136 def filectxs(self):
137 """generate a file context for each file in this changeset's
137 """generate a file context for each file in this changeset's
138 manifest"""
138 manifest"""
139 for f in util.sort(mf):
139 for f in util.sort(mf):
140 yield self.filectx(f, fileid=mf[f])
140 yield self.filectx(f, fileid=mf[f])
141
141
142 def ancestor(self, c2):
142 def ancestor(self, c2):
143 """
143 """
144 return the ancestor context of self and c2
144 return the ancestor context of self and c2
145 """
145 """
146 n = self._repo.changelog.ancestor(self._node, c2._node)
146 n = self._repo.changelog.ancestor(self._node, c2._node)
147 return changectx(self._repo, n)
147 return changectx(self._repo, n)
148
148
149 def walk(self, match):
149 def walk(self, match):
150 fdict = dict.fromkeys(match.files())
150 fdict = dict.fromkeys(match.files())
151 # for dirstate.walk, files=['.'] means "walk the whole tree".
151 # for dirstate.walk, files=['.'] means "walk the whole tree".
152 # follow that here, too
152 # follow that here, too
153 fdict.pop('.', None)
153 fdict.pop('.', None)
154 for fn in self:
154 for fn in self:
155 for ffn in fdict:
155 for ffn in fdict:
156 # match if the file is the exact name or a directory
156 # match if the file is the exact name or a directory
157 if ffn == fn or fn.startswith("%s/" % ffn):
157 if ffn == fn or fn.startswith("%s/" % ffn):
158 del fdict[ffn]
158 del fdict[ffn]
159 break
159 break
160 if match(fn):
160 if match(fn):
161 yield fn
161 yield fn
162 for fn in util.sort(fdict):
162 for fn in util.sort(fdict):
163 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
163 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
164 yield fn
164 yield fn
165
165
166 class filectx(object):
166 class filectx(object):
167 """A filecontext object makes access to data related to a particular
167 """A filecontext object makes access to data related to a particular
168 filerevision convenient."""
168 filerevision convenient."""
169 def __init__(self, repo, path, changeid=None, fileid=None,
169 def __init__(self, repo, path, changeid=None, fileid=None,
170 filelog=None, changectx=None):
170 filelog=None, changectx=None):
171 """changeid can be a changeset revision, node, or tag.
171 """changeid can be a changeset revision, node, or tag.
172 fileid can be a file revision or node."""
172 fileid can be a file revision or node."""
173 self._repo = repo
173 self._repo = repo
174 self._path = path
174 self._path = path
175
175
176 assert (changeid is not None
176 assert (changeid is not None
177 or fileid is not None
177 or fileid is not None
178 or changectx is not None)
178 or changectx is not None)
179
179
180 if filelog:
180 if filelog:
181 self._filelog = filelog
181 self._filelog = filelog
182
182
183 if changeid is not None:
183 if changeid is not None:
184 self._changeid = changeid
184 self._changeid = changeid
185 if changectx is not None:
185 if changectx is not None:
186 self._changectx = changectx
186 self._changectx = changectx
187 if fileid is not None:
187 if fileid is not None:
188 self._fileid = fileid
188 self._fileid = fileid
189
189
190 def __getattr__(self, name):
190 def __getattr__(self, name):
191 if name == '_changectx':
191 if name == '_changectx':
192 self._changectx = changectx(self._repo, self._changeid)
192 self._changectx = changectx(self._repo, self._changeid)
193 return self._changectx
193 return self._changectx
194 elif name == '_filelog':
194 elif name == '_filelog':
195 self._filelog = self._repo.file(self._path)
195 self._filelog = self._repo.file(self._path)
196 return self._filelog
196 return self._filelog
197 elif name == '_changeid':
197 elif name == '_changeid':
198 if '_changectx' in self.__dict__:
198 if '_changectx' in self.__dict__:
199 self._changeid = self._changectx.rev()
199 self._changeid = self._changectx.rev()
200 else:
200 else:
201 self._changeid = self._filelog.linkrev(self._filenode)
201 self._changeid = self._filelog.linkrev(self._filenode)
202 return self._changeid
202 return self._changeid
203 elif name == '_filenode':
203 elif name == '_filenode':
204 if '_fileid' in self.__dict__:
204 if '_fileid' in self.__dict__:
205 self._filenode = self._filelog.lookup(self._fileid)
205 self._filenode = self._filelog.lookup(self._fileid)
206 else:
206 else:
207 self._filenode = self._changectx.filenode(self._path)
207 self._filenode = self._changectx.filenode(self._path)
208 return self._filenode
208 return self._filenode
209 elif name == '_filerev':
209 elif name == '_filerev':
210 self._filerev = self._filelog.rev(self._filenode)
210 self._filerev = self._filelog.rev(self._filenode)
211 return self._filerev
211 return self._filerev
212 elif name == '_repopath':
212 elif name == '_repopath':
213 self._repopath = self._path
213 self._repopath = self._path
214 return self._repopath
214 return self._repopath
215 else:
215 else:
216 raise AttributeError, name
216 raise AttributeError, name
217
217
218 def __nonzero__(self):
218 def __nonzero__(self):
219 try:
219 try:
220 n = self._filenode
220 n = self._filenode
221 return True
221 return True
222 except revlog.LookupError:
222 except revlog.LookupError:
223 # file is missing
223 # file is missing
224 return False
224 return False
225
225
226 def __str__(self):
226 def __str__(self):
227 return "%s@%s" % (self.path(), short(self.node()))
227 return "%s@%s" % (self.path(), short(self.node()))
228
228
229 def __repr__(self):
229 def __repr__(self):
230 return "<filectx %s>" % str(self)
230 return "<filectx %s>" % str(self)
231
231
232 def __hash__(self):
232 def __hash__(self):
233 try:
233 try:
234 return hash((self._path, self._fileid))
234 return hash((self._path, self._fileid))
235 except AttributeError:
235 except AttributeError:
236 return id(self)
236 return id(self)
237
237
238 def __eq__(self, other):
238 def __eq__(self, other):
239 try:
239 try:
240 return (self._path == other._path
240 return (self._path == other._path
241 and self._fileid == other._fileid)
241 and self._fileid == other._fileid)
242 except AttributeError:
242 except AttributeError:
243 return False
243 return False
244
244
245 def __ne__(self, other):
245 def __ne__(self, other):
246 return not (self == other)
246 return not (self == other)
247
247
248 def filectx(self, fileid):
248 def filectx(self, fileid):
249 '''opens an arbitrary revision of the file without
249 '''opens an arbitrary revision of the file without
250 opening a new filelog'''
250 opening a new filelog'''
251 return filectx(self._repo, self._path, fileid=fileid,
251 return filectx(self._repo, self._path, fileid=fileid,
252 filelog=self._filelog)
252 filelog=self._filelog)
253
253
254 def filerev(self): return self._filerev
254 def filerev(self): return self._filerev
255 def filenode(self): return self._filenode
255 def filenode(self): return self._filenode
256 def flags(self): return self._changectx.flags(self._path)
256 def flags(self): return self._changectx.flags(self._path)
257 def filelog(self): return self._filelog
257 def filelog(self): return self._filelog
258
258
259 def rev(self):
259 def rev(self):
260 if '_changectx' in self.__dict__:
260 if '_changectx' in self.__dict__:
261 return self._changectx.rev()
261 return self._changectx.rev()
262 if '_changeid' in self.__dict__:
262 if '_changeid' in self.__dict__:
263 return self._changectx.rev()
263 return self._changectx.rev()
264 return self._filelog.linkrev(self._filenode)
264 return self._filelog.linkrev(self._filenode)
265
265
266 def linkrev(self): return self._filelog.linkrev(self._filenode)
266 def linkrev(self): return self._filelog.linkrev(self._filenode)
267 def node(self): return self._changectx.node()
267 def node(self): return self._changectx.node()
268 def user(self): return self._changectx.user()
268 def user(self): return self._changectx.user()
269 def date(self): return self._changectx.date()
269 def date(self): return self._changectx.date()
270 def files(self): return self._changectx.files()
270 def files(self): return self._changectx.files()
271 def description(self): return self._changectx.description()
271 def description(self): return self._changectx.description()
272 def branch(self): return self._changectx.branch()
272 def branch(self): return self._changectx.branch()
273 def manifest(self): return self._changectx.manifest()
273 def manifest(self): return self._changectx.manifest()
274 def changectx(self): return self._changectx
274 def changectx(self): return self._changectx
275
275
276 def data(self): return self._filelog.read(self._filenode)
276 def data(self): return self._filelog.read(self._filenode)
277 def path(self): return self._path
277 def path(self): return self._path
278 def size(self): return self._filelog.size(self._filerev)
278 def size(self): return self._filelog.size(self._filerev)
279
279
280 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
280 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
281
281
282 def renamed(self):
282 def renamed(self):
283 """check if file was actually renamed in this changeset revision
283 """check if file was actually renamed in this changeset revision
284
284
285 If rename logged in file revision, we report copy for changeset only
285 If rename logged in file revision, we report copy for changeset only
286 if file revisions linkrev points back to the changeset in question
286 if file revisions linkrev points back to the changeset in question
287 or both changeset parents contain different file revisions.
287 or both changeset parents contain different file revisions.
288 """
288 """
289
289
290 renamed = self._filelog.renamed(self._filenode)
290 renamed = self._filelog.renamed(self._filenode)
291 if not renamed:
291 if not renamed:
292 return renamed
292 return renamed
293
293
294 if self.rev() == self.linkrev():
294 if self.rev() == self.linkrev():
295 return renamed
295 return renamed
296
296
297 name = self.path()
297 name = self.path()
298 fnode = self._filenode
298 fnode = self._filenode
299 for p in self._changectx.parents():
299 for p in self._changectx.parents():
300 try:
300 try:
301 if fnode == p.filenode(name):
301 if fnode == p.filenode(name):
302 return None
302 return None
303 except revlog.LookupError:
303 except revlog.LookupError:
304 pass
304 pass
305 return renamed
305 return renamed
306
306
307 def parents(self):
307 def parents(self):
308 p = self._path
308 p = self._path
309 fl = self._filelog
309 fl = self._filelog
310 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
310 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
311
311
312 r = self._filelog.renamed(self._filenode)
312 r = self._filelog.renamed(self._filenode)
313 if r:
313 if r:
314 pl[0] = (r[0], r[1], None)
314 pl[0] = (r[0], r[1], None)
315
315
316 return [filectx(self._repo, p, fileid=n, filelog=l)
316 return [filectx(self._repo, p, fileid=n, filelog=l)
317 for p,n,l in pl if n != nullid]
317 for p,n,l in pl if n != nullid]
318
318
319 def children(self):
319 def children(self):
320 # hard for renames
320 # hard for renames
321 c = self._filelog.children(self._filenode)
321 c = self._filelog.children(self._filenode)
322 return [filectx(self._repo, self._path, fileid=x,
322 return [filectx(self._repo, self._path, fileid=x,
323 filelog=self._filelog) for x in c]
323 filelog=self._filelog) for x in c]
324
324
325 def annotate(self, follow=False, linenumber=None):
325 def annotate(self, follow=False, linenumber=None):
326 '''returns a list of tuples of (ctx, line) for each line
326 '''returns a list of tuples of (ctx, line) for each line
327 in the file, where ctx is the filectx of the node where
327 in the file, where ctx is the filectx of the node where
328 that line was last changed.
328 that line was last changed.
329 This returns tuples of ((ctx, linenumber), line) for each line,
329 This returns tuples of ((ctx, linenumber), line) for each line,
330 if "linenumber" parameter is NOT "None".
330 if "linenumber" parameter is NOT "None".
331 In such tuples, linenumber means one at the first appearance
331 In such tuples, linenumber means one at the first appearance
332 in the managed file.
332 in the managed file.
333 To reduce annotation cost,
333 To reduce annotation cost,
334 this returns fixed value(False is used) as linenumber,
334 this returns fixed value(False is used) as linenumber,
335 if "linenumber" parameter is "False".'''
335 if "linenumber" parameter is "False".'''
336
336
337 def decorate_compat(text, rev):
337 def decorate_compat(text, rev):
338 return ([rev] * len(text.splitlines()), text)
338 return ([rev] * len(text.splitlines()), text)
339
339
340 def without_linenumber(text, rev):
340 def without_linenumber(text, rev):
341 return ([(rev, False)] * len(text.splitlines()), text)
341 return ([(rev, False)] * len(text.splitlines()), text)
342
342
343 def with_linenumber(text, rev):
343 def with_linenumber(text, rev):
344 size = len(text.splitlines())
344 size = len(text.splitlines())
345 return ([(rev, i) for i in xrange(1, size + 1)], text)
345 return ([(rev, i) for i in xrange(1, size + 1)], text)
346
346
347 decorate = (((linenumber is None) and decorate_compat) or
347 decorate = (((linenumber is None) and decorate_compat) or
348 (linenumber and with_linenumber) or
348 (linenumber and with_linenumber) or
349 without_linenumber)
349 without_linenumber)
350
350
351 def pair(parent, child):
351 def pair(parent, child):
352 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
352 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
353 child[0][b1:b2] = parent[0][a1:a2]
353 child[0][b1:b2] = parent[0][a1:a2]
354 return child
354 return child
355
355
356 getlog = util.cachefunc(lambda x: self._repo.file(x))
356 getlog = util.cachefunc(lambda x: self._repo.file(x))
357 def getctx(path, fileid):
357 def getctx(path, fileid):
358 log = path == self._path and self._filelog or getlog(path)
358 log = path == self._path and self._filelog or getlog(path)
359 return filectx(self._repo, path, fileid=fileid, filelog=log)
359 return filectx(self._repo, path, fileid=fileid, filelog=log)
360 getctx = util.cachefunc(getctx)
360 getctx = util.cachefunc(getctx)
361
361
362 def parents(f):
362 def parents(f):
363 # we want to reuse filectx objects as much as possible
363 # we want to reuse filectx objects as much as possible
364 p = f._path
364 p = f._path
365 if f._filerev is None: # working dir
365 if f._filerev is None: # working dir
366 pl = [(n.path(), n.filerev()) for n in f.parents()]
366 pl = [(n.path(), n.filerev()) for n in f.parents()]
367 else:
367 else:
368 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
368 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
369
369
370 if follow:
370 if follow:
371 r = f.renamed()
371 r = f.renamed()
372 if r:
372 if r:
373 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
373 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
374
374
375 return [getctx(p, n) for p, n in pl if n != nullrev]
375 return [getctx(p, n) for p, n in pl if n != nullrev]
376
376
377 # use linkrev to find the first changeset where self appeared
377 # use linkrev to find the first changeset where self appeared
378 if self.rev() != self.linkrev():
378 if self.rev() != self.linkrev():
379 base = self.filectx(self.filerev())
379 base = self.filectx(self.filerev())
380 else:
380 else:
381 base = self
381 base = self
382
382
383 # find all ancestors
383 # find all ancestors
384 needed = {base: 1}
384 needed = {base: 1}
385 visit = [base]
385 visit = [base]
386 files = [base._path]
386 files = [base._path]
387 while visit:
387 while visit:
388 f = visit.pop(0)
388 f = visit.pop(0)
389 for p in parents(f):
389 for p in parents(f):
390 if p not in needed:
390 if p not in needed:
391 needed[p] = 1
391 needed[p] = 1
392 visit.append(p)
392 visit.append(p)
393 if p._path not in files:
393 if p._path not in files:
394 files.append(p._path)
394 files.append(p._path)
395 else:
395 else:
396 # count how many times we'll use this
396 # count how many times we'll use this
397 needed[p] += 1
397 needed[p] += 1
398
398
399 # sort by revision (per file) which is a topological order
399 # sort by revision (per file) which is a topological order
400 visit = []
400 visit = []
401 for f in files:
401 for f in files:
402 fn = [(n.rev(), n) for n in needed if n._path == f]
402 fn = [(n.rev(), n) for n in needed if n._path == f]
403 visit.extend(fn)
403 visit.extend(fn)
404
404
405 hist = {}
405 hist = {}
406 for r, f in util.sort(visit):
406 for r, f in util.sort(visit):
407 curr = decorate(f.data(), f)
407 curr = decorate(f.data(), f)
408 for p in parents(f):
408 for p in parents(f):
409 if p != nullid:
409 if p != nullid:
410 curr = pair(hist[p], curr)
410 curr = pair(hist[p], curr)
411 # trim the history of unneeded revs
411 # trim the history of unneeded revs
412 needed[p] -= 1
412 needed[p] -= 1
413 if not needed[p]:
413 if not needed[p]:
414 del hist[p]
414 del hist[p]
415 hist[f] = curr
415 hist[f] = curr
416
416
417 return zip(hist[f][0], hist[f][1].splitlines(1))
417 return zip(hist[f][0], hist[f][1].splitlines(1))
418
418
419 def ancestor(self, fc2):
419 def ancestor(self, fc2):
420 """
420 """
421 find the common ancestor file context, if any, of self, and fc2
421 find the common ancestor file context, if any, of self, and fc2
422 """
422 """
423
423
424 acache = {}
424 acache = {}
425
425
426 # prime the ancestor cache for the working directory
426 # prime the ancestor cache for the working directory
427 for c in (self, fc2):
427 for c in (self, fc2):
428 if c._filerev == None:
428 if c._filerev == None:
429 pl = [(n.path(), n.filenode()) for n in c.parents()]
429 pl = [(n.path(), n.filenode()) for n in c.parents()]
430 acache[(c._path, None)] = pl
430 acache[(c._path, None)] = pl
431
431
432 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
432 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
433 def parents(vertex):
433 def parents(vertex):
434 if vertex in acache:
434 if vertex in acache:
435 return acache[vertex]
435 return acache[vertex]
436 f, n = vertex
436 f, n = vertex
437 if f not in flcache:
437 if f not in flcache:
438 flcache[f] = self._repo.file(f)
438 flcache[f] = self._repo.file(f)
439 fl = flcache[f]
439 fl = flcache[f]
440 pl = [(f, p) for p in fl.parents(n) if p != nullid]
440 pl = [(f, p) for p in fl.parents(n) if p != nullid]
441 re = fl.renamed(n)
441 re = fl.renamed(n)
442 if re:
442 if re:
443 pl.append(re)
443 pl.append(re)
444 acache[vertex] = pl
444 acache[vertex] = pl
445 return pl
445 return pl
446
446
447 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
447 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
448 v = ancestor.ancestor(a, b, parents)
448 v = ancestor.ancestor(a, b, parents)
449 if v:
449 if v:
450 f, n = v
450 f, n = v
451 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
451 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
452
452
453 return None
453 return None
454
454
455 class workingctx(changectx):
455 class workingctx(changectx):
456 """A workingctx object makes access to data related to
456 """A workingctx object makes access to data related to
457 the current working directory convenient.
457 the current working directory convenient.
458 parents - a pair of parent nodeids, or None to use the dirstate.
458 parents - a pair of parent nodeids, or None to use the dirstate.
459 date - any valid date string or (unixtime, offset), or None.
459 date - any valid date string or (unixtime, offset), or None.
460 user - username string, or None.
460 user - username string, or None.
461 extra - a dictionary of extra values, or None.
461 extra - a dictionary of extra values, or None.
462 changes - a list of file lists as returned by localrepo.status()
462 changes - a list of file lists as returned by localrepo.status()
463 or None to use the repository status.
463 or None to use the repository status.
464 """
464 """
465 def __init__(self, repo, parents=None, text="", user=None, date=None,
465 def __init__(self, repo, parents=None, text="", user=None, date=None,
466 extra=None, changes=None):
466 extra=None, changes=None):
467 self._repo = repo
467 self._repo = repo
468 self._rev = None
468 self._rev = None
469 self._node = None
469 self._node = None
470 self._text = text
470 self._text = text
471 if date:
471 if date:
472 self._date = util.parsedate(date)
472 self._date = util.parsedate(date)
473 if user:
473 if user:
474 self._user = user
474 self._user = user
475 if parents:
475 if parents:
476 self._parents = [changectx(self._repo, p) for p in parents]
476 self._parents = [changectx(self._repo, p) for p in parents]
477 if changes:
477 if changes:
478 self._status = list(changes)
478 self._status = list(changes)
479
479
480 self._extra = {}
480 self._extra = {}
481 if extra:
481 if extra:
482 self._extra = extra.copy()
482 self._extra = extra.copy()
483 if 'branch' not in self._extra:
483 if 'branch' not in self._extra:
484 branch = self._repo.dirstate.branch()
484 branch = self._repo.dirstate.branch()
485 try:
485 try:
486 branch = branch.decode('UTF-8').encode('UTF-8')
486 branch = branch.decode('UTF-8').encode('UTF-8')
487 except UnicodeDecodeError:
487 except UnicodeDecodeError:
488 raise util.Abort(_('branch name not in UTF-8!'))
488 raise util.Abort(_('branch name not in UTF-8!'))
489 self._extra['branch'] = branch
489 self._extra['branch'] = branch
490 if self._extra['branch'] == '':
490 if self._extra['branch'] == '':
491 self._extra['branch'] = 'default'
491 self._extra['branch'] = 'default'
492
492
493 def __str__(self):
493 def __str__(self):
494 return str(self._parents[0]) + "+"
494 return str(self._parents[0]) + "+"
495
495
496 def __nonzero__(self):
496 def __nonzero__(self):
497 return True
497 return True
498
498
499 def __contains__(self, key):
499 def __contains__(self, key):
500 return self._dirstate[f] not in "?r"
500 return self._dirstate[f] not in "?r"
501
501
502 def __getattr__(self, name):
502 def __getattr__(self, name):
503 if name == '_status':
503 if name == '_status':
504 self._status = self._repo.status(unknown=True)
504 self._status = self._repo.status(unknown=True)
505 return self._status
505 return self._status
506 elif name == '_user':
506 elif name == '_user':
507 self._user = self._repo.ui.username()
507 self._user = self._repo.ui.username()
508 return self._user
508 return self._user
509 elif name == '_date':
509 elif name == '_date':
510 self._date = util.makedate()
510 self._date = util.makedate()
511 return self._date
511 return self._date
512 if name == '_manifest':
512 if name == '_manifest':
513 self._buildmanifest()
513 self._buildmanifest()
514 return self._manifest
514 return self._manifest
515 elif name == '_parents':
515 elif name == '_parents':
516 p = self._repo.dirstate.parents()
516 p = self._repo.dirstate.parents()
517 if p[1] == nullid:
517 if p[1] == nullid:
518 p = p[:-1]
518 p = p[:-1]
519 self._parents = [changectx(self._repo, x) for x in p]
519 self._parents = [changectx(self._repo, x) for x in p]
520 return self._parents
520 return self._parents
521 else:
521 else:
522 raise AttributeError, name
522 raise AttributeError, name
523
523
524 def _buildmanifest(self):
524 def _buildmanifest(self):
525 """generate a manifest corresponding to the working directory"""
525 """generate a manifest corresponding to the working directory"""
526
526
527 man = self._parents[0].manifest().copy()
527 man = self._parents[0].manifest().copy()
528 copied = self._repo.dirstate.copies()
528 copied = self._repo.dirstate.copies()
529 cf = lambda x: man.flags(copied.get(x, x))
529 cf = lambda x: man.flags(copied.get(x, x))
530 ff = self._repo.dirstate.flagfunc(cf)
530 ff = self._repo.dirstate.flagfunc(cf)
531 modified, added, removed, deleted, unknown = self._status[:5]
531 modified, added, removed, deleted, unknown = self._status[:5]
532 for i, l in (("a", added), ("m", modified), ("u", unknown)):
532 for i, l in (("a", added), ("m", modified), ("u", unknown)):
533 for f in l:
533 for f in l:
534 man[f] = man.get(copied.get(f, f), nullid) + i
534 man[f] = man.get(copied.get(f, f), nullid) + i
535 try:
535 try:
536 man.set(f, ff(f))
536 man.set(f, ff(f))
537 except OSError:
537 except OSError:
538 pass
538 pass
539
539
540 for f in deleted + removed:
540 for f in deleted + removed:
541 if f in man:
541 if f in man:
542 del man[f]
542 del man[f]
543
543
544 self._manifest = man
544 self._manifest = man
545
545
546 def manifest(self): return self._manifest
546 def manifest(self): return self._manifest
547
547
548 def user(self): return self._user or self._repo.ui.username()
548 def user(self): return self._user or self._repo.ui.username()
549 def date(self): return self._date
549 def date(self): return self._date
550 def description(self): return self._text
550 def description(self): return self._text
551 def files(self):
551 def files(self):
552 return util.sort(self._status[0] + self._status[1] + self._status[2])
552 return util.sort(self._status[0] + self._status[1] + self._status[2])
553
553
554 def modified(self): return self._status[0]
554 def modified(self): return self._status[0]
555 def added(self): return self._status[1]
555 def added(self): return self._status[1]
556 def removed(self): return self._status[2]
556 def removed(self): return self._status[2]
557 def deleted(self): return self._status[3]
557 def deleted(self): return self._status[3]
558 def unknown(self): return self._status[4]
558 def unknown(self): return self._status[4]
559 def clean(self): return self._status[5]
559 def clean(self): return self._status[5]
560 def branch(self): return self._extra['branch']
560 def branch(self): return self._extra['branch']
561 def extra(self): return self._extra
561 def extra(self): return self._extra
562
562
563 def tags(self):
563 def tags(self):
564 t = []
564 t = []
565 [t.extend(p.tags()) for p in self.parents()]
565 [t.extend(p.tags()) for p in self.parents()]
566 return t
566 return t
567
567
568 def children(self):
568 def children(self):
569 return []
569 return []
570
570
571 def flags(self, path):
571 def flags(self, path):
572 if '_manifest' in self.__dict__:
572 if '_manifest' in self.__dict__:
573 try:
573 try:
574 return self._manifest.flags(path)
574 return self._manifest.flags(path)
575 except KeyError:
575 except KeyError:
576 return ''
576 return ''
577
577
578 pnode = self._parents[0].changeset()[0]
578 pnode = self._parents[0].changeset()[0]
579 orig = self._repo.dirstate.copies().get(path, path)
579 orig = self._repo.dirstate.copies().get(path, path)
580 node, flag = self._repo.manifest.find(pnode, orig)
580 node, flag = self._repo.manifest.find(pnode, orig)
581 try:
581 try:
582 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
582 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
583 return ff(path)
583 return ff(path)
584 except OSError:
584 except OSError:
585 pass
585 pass
586
586
587 if not node or path in self.deleted() or path in self.removed():
587 if not node or path in self.deleted() or path in self.removed():
588 return ''
588 return ''
589 return flag
589 return flag
590
590
591 def filectx(self, path, filelog=None):
591 def filectx(self, path, filelog=None):
592 """get a file context from the working directory"""
592 """get a file context from the working directory"""
593 return workingfilectx(self._repo, path, workingctx=self,
593 return workingfilectx(self._repo, path, workingctx=self,
594 filelog=filelog)
594 filelog=filelog)
595
595
596 def ancestor(self, c2):
596 def ancestor(self, c2):
597 """return the ancestor context of self and c2"""
597 """return the ancestor context of self and c2"""
598 return self._parents[0].ancestor(c2) # punt on two parents for now
598 return self._parents[0].ancestor(c2) # punt on two parents for now
599
599
600 def walk(self, match):
600 def walk(self, match):
601 for src, fn, st in self._repo.dirstate.walk(match, True, False):
601 for fn, st in self._repo.dirstate.walk(match, True, False):
602 yield fn
602 yield fn
603
603
604 class workingfilectx(filectx):
604 class workingfilectx(filectx):
605 """A workingfilectx object makes access to data related to a particular
605 """A workingfilectx object makes access to data related to a particular
606 file in the working directory convenient."""
606 file in the working directory convenient."""
607 def __init__(self, repo, path, filelog=None, workingctx=None):
607 def __init__(self, repo, path, filelog=None, workingctx=None):
608 """changeid can be a changeset revision, node, or tag.
608 """changeid can be a changeset revision, node, or tag.
609 fileid can be a file revision or node."""
609 fileid can be a file revision or node."""
610 self._repo = repo
610 self._repo = repo
611 self._path = path
611 self._path = path
612 self._changeid = None
612 self._changeid = None
613 self._filerev = self._filenode = None
613 self._filerev = self._filenode = None
614
614
615 if filelog:
615 if filelog:
616 self._filelog = filelog
616 self._filelog = filelog
617 if workingctx:
617 if workingctx:
618 self._changectx = workingctx
618 self._changectx = workingctx
619
619
620 def __getattr__(self, name):
620 def __getattr__(self, name):
621 if name == '_changectx':
621 if name == '_changectx':
622 self._changectx = workingctx(self._repo)
622 self._changectx = workingctx(self._repo)
623 return self._changectx
623 return self._changectx
624 elif name == '_repopath':
624 elif name == '_repopath':
625 self._repopath = (self._repo.dirstate.copied(self._path)
625 self._repopath = (self._repo.dirstate.copied(self._path)
626 or self._path)
626 or self._path)
627 return self._repopath
627 return self._repopath
628 elif name == '_filelog':
628 elif name == '_filelog':
629 self._filelog = self._repo.file(self._repopath)
629 self._filelog = self._repo.file(self._repopath)
630 return self._filelog
630 return self._filelog
631 else:
631 else:
632 raise AttributeError, name
632 raise AttributeError, name
633
633
634 def __nonzero__(self):
634 def __nonzero__(self):
635 return True
635 return True
636
636
637 def __str__(self):
637 def __str__(self):
638 return "%s@%s" % (self.path(), self._changectx)
638 return "%s@%s" % (self.path(), self._changectx)
639
639
640 def filectx(self, fileid):
640 def filectx(self, fileid):
641 '''opens an arbitrary revision of the file without
641 '''opens an arbitrary revision of the file without
642 opening a new filelog'''
642 opening a new filelog'''
643 return filectx(self._repo, self._repopath, fileid=fileid,
643 return filectx(self._repo, self._repopath, fileid=fileid,
644 filelog=self._filelog)
644 filelog=self._filelog)
645
645
646 def rev(self):
646 def rev(self):
647 if '_changectx' in self.__dict__:
647 if '_changectx' in self.__dict__:
648 return self._changectx.rev()
648 return self._changectx.rev()
649 return self._filelog.linkrev(self._filenode)
649 return self._filelog.linkrev(self._filenode)
650
650
651 def data(self): return self._repo.wread(self._path)
651 def data(self): return self._repo.wread(self._path)
652 def renamed(self):
652 def renamed(self):
653 rp = self._repopath
653 rp = self._repopath
654 if rp == self._path:
654 if rp == self._path:
655 return None
655 return None
656 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
656 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
657
657
658 def parents(self):
658 def parents(self):
659 '''return parent filectxs, following copies if necessary'''
659 '''return parent filectxs, following copies if necessary'''
660 p = self._path
660 p = self._path
661 rp = self._repopath
661 rp = self._repopath
662 pcl = self._changectx._parents
662 pcl = self._changectx._parents
663 fl = self._filelog
663 fl = self._filelog
664 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
664 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
665 if len(pcl) > 1:
665 if len(pcl) > 1:
666 if rp != p:
666 if rp != p:
667 fl = None
667 fl = None
668 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
668 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
669
669
670 return [filectx(self._repo, p, fileid=n, filelog=l)
670 return [filectx(self._repo, p, fileid=n, filelog=l)
671 for p,n,l in pl if n != nullid]
671 for p,n,l in pl if n != nullid]
672
672
673 def children(self):
673 def children(self):
674 return []
674 return []
675
675
676 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
676 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
677 def date(self):
677 def date(self):
678 t, tz = self._changectx.date()
678 t, tz = self._changectx.date()
679 try:
679 try:
680 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
680 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
681 except OSError, err:
681 except OSError, err:
682 if err.errno != errno.ENOENT: raise
682 if err.errno != errno.ENOENT: raise
683 return (t, tz)
683 return (t, tz)
684
684
685 def cmp(self, text): return self._repo.wread(self._path) == text
685 def cmp(self, text): return self._repo.wread(self._path) == text
686
686
687 class memctx(object):
687 class memctx(object):
688 """A memctx is a subset of changectx supposed to be built on memory
688 """A memctx is a subset of changectx supposed to be built on memory
689 and passed to commit functions.
689 and passed to commit functions.
690
690
691 NOTE: this interface and the related memfilectx are experimental and
691 NOTE: this interface and the related memfilectx are experimental and
692 may change without notice.
692 may change without notice.
693
693
694 parents - a pair of parent nodeids.
694 parents - a pair of parent nodeids.
695 filectxfn - a callable taking (repo, memctx, path) arguments and
695 filectxfn - a callable taking (repo, memctx, path) arguments and
696 returning a memctx object.
696 returning a memctx object.
697 date - any valid date string or (unixtime, offset), or None.
697 date - any valid date string or (unixtime, offset), or None.
698 user - username string, or None.
698 user - username string, or None.
699 extra - a dictionary of extra values, or None.
699 extra - a dictionary of extra values, or None.
700 """
700 """
701 def __init__(self, repo, parents, text, files, filectxfn, user=None,
701 def __init__(self, repo, parents, text, files, filectxfn, user=None,
702 date=None, extra=None):
702 date=None, extra=None):
703 self._repo = repo
703 self._repo = repo
704 self._rev = None
704 self._rev = None
705 self._node = None
705 self._node = None
706 self._text = text
706 self._text = text
707 self._date = date and util.parsedate(date) or util.makedate()
707 self._date = date and util.parsedate(date) or util.makedate()
708 self._user = user
708 self._user = user
709 parents = [(p or nullid) for p in parents]
709 parents = [(p or nullid) for p in parents]
710 p1, p2 = parents
710 p1, p2 = parents
711 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
711 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
712 files = util.sort(list(files))
712 files = util.sort(list(files))
713 self._status = [files, [], [], [], []]
713 self._status = [files, [], [], [], []]
714 self._filectxfn = filectxfn
714 self._filectxfn = filectxfn
715
715
716 self._extra = extra and extra.copy() or {}
716 self._extra = extra and extra.copy() or {}
717 if 'branch' not in self._extra:
717 if 'branch' not in self._extra:
718 self._extra['branch'] = 'default'
718 self._extra['branch'] = 'default'
719 elif self._extra.get('branch') == '':
719 elif self._extra.get('branch') == '':
720 self._extra['branch'] = 'default'
720 self._extra['branch'] = 'default'
721
721
722 def __str__(self):
722 def __str__(self):
723 return str(self._parents[0]) + "+"
723 return str(self._parents[0]) + "+"
724
724
725 def __int__(self):
725 def __int__(self):
726 return self._rev
726 return self._rev
727
727
728 def __nonzero__(self):
728 def __nonzero__(self):
729 return True
729 return True
730
730
731 def user(self): return self._user or self._repo.ui.username()
731 def user(self): return self._user or self._repo.ui.username()
732 def date(self): return self._date
732 def date(self): return self._date
733 def description(self): return self._text
733 def description(self): return self._text
734 def files(self): return self.modified()
734 def files(self): return self.modified()
735 def modified(self): return self._status[0]
735 def modified(self): return self._status[0]
736 def added(self): return self._status[1]
736 def added(self): return self._status[1]
737 def removed(self): return self._status[2]
737 def removed(self): return self._status[2]
738 def deleted(self): return self._status[3]
738 def deleted(self): return self._status[3]
739 def unknown(self): return self._status[4]
739 def unknown(self): return self._status[4]
740 def clean(self): return self._status[5]
740 def clean(self): return self._status[5]
741 def branch(self): return self._extra['branch']
741 def branch(self): return self._extra['branch']
742 def extra(self): return self._extra
742 def extra(self): return self._extra
743 def flags(self, f): return self[f].flags()
743 def flags(self, f): return self[f].flags()
744
744
745 def parents(self):
745 def parents(self):
746 """return contexts for each parent changeset"""
746 """return contexts for each parent changeset"""
747 return self._parents
747 return self._parents
748
748
749 def filectx(self, path, filelog=None):
749 def filectx(self, path, filelog=None):
750 """get a file context from the working directory"""
750 """get a file context from the working directory"""
751 return self._filectxfn(self._repo, self, path)
751 return self._filectxfn(self._repo, self, path)
752
752
753 class memfilectx(object):
753 class memfilectx(object):
754 """A memfilectx is a subset of filectx supposed to be built by client
754 """A memfilectx is a subset of filectx supposed to be built by client
755 code and passed to commit functions.
755 code and passed to commit functions.
756 """
756 """
757 def __init__(self, path, data, islink, isexec, copied):
757 def __init__(self, path, data, islink, isexec, copied):
758 """copied is the source file path, or None."""
758 """copied is the source file path, or None."""
759 self._path = path
759 self._path = path
760 self._data = data
760 self._data = data
761 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
761 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
762 self._copied = None
762 self._copied = None
763 if copied:
763 if copied:
764 self._copied = (copied, nullid)
764 self._copied = (copied, nullid)
765
765
766 def __nonzero__(self): return True
766 def __nonzero__(self): return True
767 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
767 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
768 def path(self): return self._path
768 def path(self): return self._path
769 def data(self): return self._data
769 def data(self): return self._data
770 def flags(self): return self._flags
770 def flags(self): return self._flags
771 def isexec(self): return 'x' in self._flags
771 def isexec(self): return 'x' in self._flags
772 def islink(self): return 'l' in self._flags
772 def islink(self): return 'l' in self._flags
773 def renamed(self): return self._copied
773 def renamed(self): return self._copied
774
774
@@ -1,670 +1,658
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8 """
8 """
9
9
10 from node import nullid
10 from node import nullid
11 from i18n import _
11 from i18n import _
12 import struct, os, bisect, stat, util, errno, ignore
12 import struct, os, bisect, stat, util, errno, ignore
13 import cStringIO, osutil, sys
13 import cStringIO, osutil, sys
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 def _finddirs(path):
18 def _finddirs(path):
19 pos = len(path)
19 pos = len(path)
20 while 1:
20 while 1:
21 pos = path.rfind('/', 0, pos)
21 pos = path.rfind('/', 0, pos)
22 if pos == -1:
22 if pos == -1:
23 break
23 break
24 yield path[:pos]
24 yield path[:pos]
25
25
26 class dirstate(object):
26 class dirstate(object):
27
27
28 def __init__(self, opener, ui, root):
28 def __init__(self, opener, ui, root):
29 self._opener = opener
29 self._opener = opener
30 self._root = root
30 self._root = root
31 self._dirty = False
31 self._dirty = False
32 self._dirtypl = False
32 self._dirtypl = False
33 self._ui = ui
33 self._ui = ui
34
34
35 def __getattr__(self, name):
35 def __getattr__(self, name):
36 if name == '_map':
36 if name == '_map':
37 self._read()
37 self._read()
38 return self._map
38 return self._map
39 elif name == '_copymap':
39 elif name == '_copymap':
40 self._read()
40 self._read()
41 return self._copymap
41 return self._copymap
42 elif name == '_foldmap':
42 elif name == '_foldmap':
43 _foldmap = {}
43 _foldmap = {}
44 for name in self._map:
44 for name in self._map:
45 norm = os.path.normcase(os.path.normpath(name))
45 norm = os.path.normcase(os.path.normpath(name))
46 _foldmap[norm] = name
46 _foldmap[norm] = name
47 self._foldmap = _foldmap
47 self._foldmap = _foldmap
48 return self._foldmap
48 return self._foldmap
49 elif name == '_branch':
49 elif name == '_branch':
50 try:
50 try:
51 self._branch = (self._opener("branch").read().strip()
51 self._branch = (self._opener("branch").read().strip()
52 or "default")
52 or "default")
53 except IOError:
53 except IOError:
54 self._branch = "default"
54 self._branch = "default"
55 return self._branch
55 return self._branch
56 elif name == '_pl':
56 elif name == '_pl':
57 self._pl = [nullid, nullid]
57 self._pl = [nullid, nullid]
58 try:
58 try:
59 st = self._opener("dirstate").read(40)
59 st = self._opener("dirstate").read(40)
60 if len(st) == 40:
60 if len(st) == 40:
61 self._pl = st[:20], st[20:40]
61 self._pl = st[:20], st[20:40]
62 except IOError, err:
62 except IOError, err:
63 if err.errno != errno.ENOENT: raise
63 if err.errno != errno.ENOENT: raise
64 return self._pl
64 return self._pl
65 elif name == '_dirs':
65 elif name == '_dirs':
66 dirs = {}
66 dirs = {}
67 for f,s in self._map.items():
67 for f,s in self._map.items():
68 if s[0] != 'r':
68 if s[0] != 'r':
69 for base in _finddirs(f):
69 for base in _finddirs(f):
70 dirs[base] = dirs.get(base, 0) + 1
70 dirs[base] = dirs.get(base, 0) + 1
71 self._dirs = dirs
71 self._dirs = dirs
72 return self._dirs
72 return self._dirs
73 elif name == '_ignore':
73 elif name == '_ignore':
74 files = [self._join('.hgignore')]
74 files = [self._join('.hgignore')]
75 for name, path in self._ui.configitems("ui"):
75 for name, path in self._ui.configitems("ui"):
76 if name == 'ignore' or name.startswith('ignore.'):
76 if name == 'ignore' or name.startswith('ignore.'):
77 files.append(os.path.expanduser(path))
77 files.append(os.path.expanduser(path))
78 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
78 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
79 return self._ignore
79 return self._ignore
80 elif name == '_slash':
80 elif name == '_slash':
81 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
81 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
82 return self._slash
82 return self._slash
83 elif name == '_checklink':
83 elif name == '_checklink':
84 self._checklink = util.checklink(self._root)
84 self._checklink = util.checklink(self._root)
85 return self._checklink
85 return self._checklink
86 elif name == '_checkexec':
86 elif name == '_checkexec':
87 self._checkexec = util.checkexec(self._root)
87 self._checkexec = util.checkexec(self._root)
88 return self._checkexec
88 return self._checkexec
89 elif name == '_checkcase':
89 elif name == '_checkcase':
90 self._checkcase = not util.checkcase(self._join('.hg'))
90 self._checkcase = not util.checkcase(self._join('.hg'))
91 return self._checkcase
91 return self._checkcase
92 elif name == 'normalize':
92 elif name == 'normalize':
93 if self._checkcase:
93 if self._checkcase:
94 self.normalize = self._normalize
94 self.normalize = self._normalize
95 else:
95 else:
96 self.normalize = lambda x: x
96 self.normalize = lambda x: x
97 return self.normalize
97 return self.normalize
98 else:
98 else:
99 raise AttributeError, name
99 raise AttributeError, name
100
100
101 def _join(self, f):
101 def _join(self, f):
102 return os.path.join(self._root, f)
102 return os.path.join(self._root, f)
103
103
104 def flagfunc(self, fallback):
104 def flagfunc(self, fallback):
105 if self._checklink:
105 if self._checklink:
106 if self._checkexec:
106 if self._checkexec:
107 def f(x):
107 def f(x):
108 p = os.path.join(self._root, x)
108 p = os.path.join(self._root, x)
109 if os.path.islink(p):
109 if os.path.islink(p):
110 return 'l'
110 return 'l'
111 if util.is_exec(p):
111 if util.is_exec(p):
112 return 'x'
112 return 'x'
113 return ''
113 return ''
114 return f
114 return f
115 def f(x):
115 def f(x):
116 if os.path.islink(os.path.join(self._root, x)):
116 if os.path.islink(os.path.join(self._root, x)):
117 return 'l'
117 return 'l'
118 if 'x' in fallback(x):
118 if 'x' in fallback(x):
119 return 'x'
119 return 'x'
120 return ''
120 return ''
121 return f
121 return f
122 if self._checkexec:
122 if self._checkexec:
123 def f(x):
123 def f(x):
124 if 'l' in fallback(x):
124 if 'l' in fallback(x):
125 return 'l'
125 return 'l'
126 if util.is_exec(os.path.join(self._root, x)):
126 if util.is_exec(os.path.join(self._root, x)):
127 return 'x'
127 return 'x'
128 return ''
128 return ''
129 return f
129 return f
130 return fallback
130 return fallback
131
131
132 def getcwd(self):
132 def getcwd(self):
133 cwd = os.getcwd()
133 cwd = os.getcwd()
134 if cwd == self._root: return ''
134 if cwd == self._root: return ''
135 # self._root ends with a path separator if self._root is '/' or 'C:\'
135 # self._root ends with a path separator if self._root is '/' or 'C:\'
136 rootsep = self._root
136 rootsep = self._root
137 if not util.endswithsep(rootsep):
137 if not util.endswithsep(rootsep):
138 rootsep += os.sep
138 rootsep += os.sep
139 if cwd.startswith(rootsep):
139 if cwd.startswith(rootsep):
140 return cwd[len(rootsep):]
140 return cwd[len(rootsep):]
141 else:
141 else:
142 # we're outside the repo. return an absolute path.
142 # we're outside the repo. return an absolute path.
143 return cwd
143 return cwd
144
144
145 def pathto(self, f, cwd=None):
145 def pathto(self, f, cwd=None):
146 if cwd is None:
146 if cwd is None:
147 cwd = self.getcwd()
147 cwd = self.getcwd()
148 path = util.pathto(self._root, cwd, f)
148 path = util.pathto(self._root, cwd, f)
149 if self._slash:
149 if self._slash:
150 return util.normpath(path)
150 return util.normpath(path)
151 return path
151 return path
152
152
153 def __getitem__(self, key):
153 def __getitem__(self, key):
154 ''' current states:
154 ''' current states:
155 n normal
155 n normal
156 m needs merging
156 m needs merging
157 r marked for removal
157 r marked for removal
158 a marked for addition
158 a marked for addition
159 ? not tracked'''
159 ? not tracked'''
160 return self._map.get(key, ("?",))[0]
160 return self._map.get(key, ("?",))[0]
161
161
162 def __contains__(self, key):
162 def __contains__(self, key):
163 return key in self._map
163 return key in self._map
164
164
165 def __iter__(self):
165 def __iter__(self):
166 for x in util.sort(self._map):
166 for x in util.sort(self._map):
167 yield x
167 yield x
168
168
169 def parents(self):
169 def parents(self):
170 return self._pl
170 return self._pl
171
171
172 def branch(self):
172 def branch(self):
173 return self._branch
173 return self._branch
174
174
175 def setparents(self, p1, p2=nullid):
175 def setparents(self, p1, p2=nullid):
176 self._dirty = self._dirtypl = True
176 self._dirty = self._dirtypl = True
177 self._pl = p1, p2
177 self._pl = p1, p2
178
178
179 def setbranch(self, branch):
179 def setbranch(self, branch):
180 self._branch = branch
180 self._branch = branch
181 self._opener("branch", "w").write(branch + '\n')
181 self._opener("branch", "w").write(branch + '\n')
182
182
183 def _read(self):
183 def _read(self):
184 self._map = {}
184 self._map = {}
185 self._copymap = {}
185 self._copymap = {}
186 if not self._dirtypl:
186 if not self._dirtypl:
187 self._pl = [nullid, nullid]
187 self._pl = [nullid, nullid]
188 try:
188 try:
189 st = self._opener("dirstate").read()
189 st = self._opener("dirstate").read()
190 except IOError, err:
190 except IOError, err:
191 if err.errno != errno.ENOENT: raise
191 if err.errno != errno.ENOENT: raise
192 return
192 return
193 if not st:
193 if not st:
194 return
194 return
195
195
196 if not self._dirtypl:
196 if not self._dirtypl:
197 self._pl = [st[:20], st[20: 40]]
197 self._pl = [st[:20], st[20: 40]]
198
198
199 # deref fields so they will be local in loop
199 # deref fields so they will be local in loop
200 dmap = self._map
200 dmap = self._map
201 copymap = self._copymap
201 copymap = self._copymap
202 unpack = struct.unpack
202 unpack = struct.unpack
203 e_size = struct.calcsize(_format)
203 e_size = struct.calcsize(_format)
204 pos1 = 40
204 pos1 = 40
205 l = len(st)
205 l = len(st)
206
206
207 # the inner loop
207 # the inner loop
208 while pos1 < l:
208 while pos1 < l:
209 pos2 = pos1 + e_size
209 pos2 = pos1 + e_size
210 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
210 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
211 pos1 = pos2 + e[4]
211 pos1 = pos2 + e[4]
212 f = st[pos2:pos1]
212 f = st[pos2:pos1]
213 if '\0' in f:
213 if '\0' in f:
214 f, c = f.split('\0')
214 f, c = f.split('\0')
215 copymap[f] = c
215 copymap[f] = c
216 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
216 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
217
217
218 def invalidate(self):
218 def invalidate(self):
219 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
219 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
220 if a in self.__dict__:
220 if a in self.__dict__:
221 delattr(self, a)
221 delattr(self, a)
222 self._dirty = False
222 self._dirty = False
223
223
224 def copy(self, source, dest):
224 def copy(self, source, dest):
225 if source == dest:
225 if source == dest:
226 return
226 return
227 self._dirty = True
227 self._dirty = True
228 self._copymap[dest] = source
228 self._copymap[dest] = source
229
229
230 def copied(self, file):
230 def copied(self, file):
231 return self._copymap.get(file, None)
231 return self._copymap.get(file, None)
232
232
233 def copies(self):
233 def copies(self):
234 return self._copymap
234 return self._copymap
235
235
236 def _droppath(self, f):
236 def _droppath(self, f):
237 if self[f] not in "?r" and "_dirs" in self.__dict__:
237 if self[f] not in "?r" and "_dirs" in self.__dict__:
238 dirs = self._dirs
238 dirs = self._dirs
239 for base in _finddirs(f):
239 for base in _finddirs(f):
240 if dirs[base] == 1:
240 if dirs[base] == 1:
241 del dirs[base]
241 del dirs[base]
242 else:
242 else:
243 dirs[base] -= 1
243 dirs[base] -= 1
244
244
245 def _addpath(self, f, check=False):
245 def _addpath(self, f, check=False):
246 oldstate = self[f]
246 oldstate = self[f]
247 if check or oldstate == "r":
247 if check or oldstate == "r":
248 if '\r' in f or '\n' in f:
248 if '\r' in f or '\n' in f:
249 raise util.Abort(
249 raise util.Abort(
250 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
250 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
251 if f in self._dirs:
251 if f in self._dirs:
252 raise util.Abort(_('directory %r already in dirstate') % f)
252 raise util.Abort(_('directory %r already in dirstate') % f)
253 # shadows
253 # shadows
254 for d in _finddirs(f):
254 for d in _finddirs(f):
255 if d in self._dirs:
255 if d in self._dirs:
256 break
256 break
257 if d in self._map and self[d] != 'r':
257 if d in self._map and self[d] != 'r':
258 raise util.Abort(
258 raise util.Abort(
259 _('file %r in dirstate clashes with %r') % (d, f))
259 _('file %r in dirstate clashes with %r') % (d, f))
260 if oldstate in "?r" and "_dirs" in self.__dict__:
260 if oldstate in "?r" and "_dirs" in self.__dict__:
261 dirs = self._dirs
261 dirs = self._dirs
262 for base in _finddirs(f):
262 for base in _finddirs(f):
263 dirs[base] = dirs.get(base, 0) + 1
263 dirs[base] = dirs.get(base, 0) + 1
264
264
265 def normal(self, f):
265 def normal(self, f):
266 'mark a file normal and clean'
266 'mark a file normal and clean'
267 self._dirty = True
267 self._dirty = True
268 self._addpath(f)
268 self._addpath(f)
269 s = os.lstat(self._join(f))
269 s = os.lstat(self._join(f))
270 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
270 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
271 if f in self._copymap:
271 if f in self._copymap:
272 del self._copymap[f]
272 del self._copymap[f]
273
273
274 def normallookup(self, f):
274 def normallookup(self, f):
275 'mark a file normal, but possibly dirty'
275 'mark a file normal, but possibly dirty'
276 if self._pl[1] != nullid and f in self._map:
276 if self._pl[1] != nullid and f in self._map:
277 # if there is a merge going on and the file was either
277 # if there is a merge going on and the file was either
278 # in state 'm' or dirty before being removed, restore that state.
278 # in state 'm' or dirty before being removed, restore that state.
279 entry = self._map[f]
279 entry = self._map[f]
280 if entry[0] == 'r' and entry[2] in (-1, -2):
280 if entry[0] == 'r' and entry[2] in (-1, -2):
281 source = self._copymap.get(f)
281 source = self._copymap.get(f)
282 if entry[2] == -1:
282 if entry[2] == -1:
283 self.merge(f)
283 self.merge(f)
284 elif entry[2] == -2:
284 elif entry[2] == -2:
285 self.normaldirty(f)
285 self.normaldirty(f)
286 if source:
286 if source:
287 self.copy(source, f)
287 self.copy(source, f)
288 return
288 return
289 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
289 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
290 return
290 return
291 self._dirty = True
291 self._dirty = True
292 self._addpath(f)
292 self._addpath(f)
293 self._map[f] = ('n', 0, -1, -1, 0)
293 self._map[f] = ('n', 0, -1, -1, 0)
294 if f in self._copymap:
294 if f in self._copymap:
295 del self._copymap[f]
295 del self._copymap[f]
296
296
297 def normaldirty(self, f):
297 def normaldirty(self, f):
298 'mark a file normal, but dirty'
298 'mark a file normal, but dirty'
299 self._dirty = True
299 self._dirty = True
300 self._addpath(f)
300 self._addpath(f)
301 self._map[f] = ('n', 0, -2, -1, 0)
301 self._map[f] = ('n', 0, -2, -1, 0)
302 if f in self._copymap:
302 if f in self._copymap:
303 del self._copymap[f]
303 del self._copymap[f]
304
304
305 def add(self, f):
305 def add(self, f):
306 'mark a file added'
306 'mark a file added'
307 self._dirty = True
307 self._dirty = True
308 self._addpath(f, True)
308 self._addpath(f, True)
309 self._map[f] = ('a', 0, -1, -1, 0)
309 self._map[f] = ('a', 0, -1, -1, 0)
310 if f in self._copymap:
310 if f in self._copymap:
311 del self._copymap[f]
311 del self._copymap[f]
312
312
313 def remove(self, f):
313 def remove(self, f):
314 'mark a file removed'
314 'mark a file removed'
315 self._dirty = True
315 self._dirty = True
316 self._droppath(f)
316 self._droppath(f)
317 size = 0
317 size = 0
318 if self._pl[1] != nullid and f in self._map:
318 if self._pl[1] != nullid and f in self._map:
319 entry = self._map[f]
319 entry = self._map[f]
320 if entry[0] == 'm':
320 if entry[0] == 'm':
321 size = -1
321 size = -1
322 elif entry[0] == 'n' and entry[2] == -2:
322 elif entry[0] == 'n' and entry[2] == -2:
323 size = -2
323 size = -2
324 self._map[f] = ('r', 0, size, 0, 0)
324 self._map[f] = ('r', 0, size, 0, 0)
325 if size == 0 and f in self._copymap:
325 if size == 0 and f in self._copymap:
326 del self._copymap[f]
326 del self._copymap[f]
327
327
328 def merge(self, f):
328 def merge(self, f):
329 'mark a file merged'
329 'mark a file merged'
330 self._dirty = True
330 self._dirty = True
331 s = os.lstat(self._join(f))
331 s = os.lstat(self._join(f))
332 self._addpath(f)
332 self._addpath(f)
333 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
333 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
334 if f in self._copymap:
334 if f in self._copymap:
335 del self._copymap[f]
335 del self._copymap[f]
336
336
337 def forget(self, f):
337 def forget(self, f):
338 'forget a file'
338 'forget a file'
339 self._dirty = True
339 self._dirty = True
340 try:
340 try:
341 self._droppath(f)
341 self._droppath(f)
342 del self._map[f]
342 del self._map[f]
343 except KeyError:
343 except KeyError:
344 self._ui.warn(_("not in dirstate: %s\n") % f)
344 self._ui.warn(_("not in dirstate: %s\n") % f)
345
345
346 def _normalize(self, path):
346 def _normalize(self, path):
347 normpath = os.path.normcase(os.path.normpath(path))
347 normpath = os.path.normcase(os.path.normpath(path))
348 if normpath in self._foldmap:
348 if normpath in self._foldmap:
349 return self._foldmap[normpath]
349 return self._foldmap[normpath]
350 elif os.path.exists(path):
350 elif os.path.exists(path):
351 self._foldmap[normpath] = util.fspath(path, self._root)
351 self._foldmap[normpath] = util.fspath(path, self._root)
352 return self._foldmap[normpath]
352 return self._foldmap[normpath]
353 else:
353 else:
354 return path
354 return path
355
355
356 def clear(self):
356 def clear(self):
357 self._map = {}
357 self._map = {}
358 if "_dirs" in self.__dict__:
358 if "_dirs" in self.__dict__:
359 delattr(self, "_dirs");
359 delattr(self, "_dirs");
360 self._copymap = {}
360 self._copymap = {}
361 self._pl = [nullid, nullid]
361 self._pl = [nullid, nullid]
362 self._dirty = True
362 self._dirty = True
363
363
364 def rebuild(self, parent, files):
364 def rebuild(self, parent, files):
365 self.clear()
365 self.clear()
366 for f in files:
366 for f in files:
367 if 'x' in files.flags(f):
367 if 'x' in files.flags(f):
368 self._map[f] = ('n', 0777, -1, 0, 0)
368 self._map[f] = ('n', 0777, -1, 0, 0)
369 else:
369 else:
370 self._map[f] = ('n', 0666, -1, 0, 0)
370 self._map[f] = ('n', 0666, -1, 0, 0)
371 self._pl = (parent, nullid)
371 self._pl = (parent, nullid)
372 self._dirty = True
372 self._dirty = True
373
373
374 def write(self):
374 def write(self):
375 if not self._dirty:
375 if not self._dirty:
376 return
376 return
377 st = self._opener("dirstate", "w", atomictemp=True)
377 st = self._opener("dirstate", "w", atomictemp=True)
378
378
379 try:
379 try:
380 gran = int(self._ui.config('dirstate', 'granularity', 1))
380 gran = int(self._ui.config('dirstate', 'granularity', 1))
381 except ValueError:
381 except ValueError:
382 gran = 1
382 gran = 1
383 limit = sys.maxint
383 limit = sys.maxint
384 if gran > 0:
384 if gran > 0:
385 limit = util.fstat(st).st_mtime - gran
385 limit = util.fstat(st).st_mtime - gran
386
386
387 cs = cStringIO.StringIO()
387 cs = cStringIO.StringIO()
388 copymap = self._copymap
388 copymap = self._copymap
389 pack = struct.pack
389 pack = struct.pack
390 write = cs.write
390 write = cs.write
391 write("".join(self._pl))
391 write("".join(self._pl))
392 for f, e in self._map.iteritems():
392 for f, e in self._map.iteritems():
393 if f in copymap:
393 if f in copymap:
394 f = "%s\0%s" % (f, copymap[f])
394 f = "%s\0%s" % (f, copymap[f])
395 if e[3] > limit and e[0] == 'n':
395 if e[3] > limit and e[0] == 'n':
396 e = (e[0], 0, -1, -1, 0)
396 e = (e[0], 0, -1, -1, 0)
397 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
397 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
398 write(e)
398 write(e)
399 write(f)
399 write(f)
400 st.write(cs.getvalue())
400 st.write(cs.getvalue())
401 st.rename()
401 st.rename()
402 self._dirty = self._dirtypl = False
402 self._dirty = self._dirtypl = False
403
403
404 def _filter(self, files):
404 def _filter(self, files):
405 ret = {}
405 ret = {}
406 unknown = []
406 unknown = []
407
407
408 for x in files:
408 for x in files:
409 if x == '.':
409 if x == '.':
410 return self._map.copy()
410 return self._map.copy()
411 if x not in self._map:
411 if x not in self._map:
412 unknown.append(x)
412 unknown.append(x)
413 else:
413 else:
414 ret[x] = self._map[x]
414 ret[x] = self._map[x]
415
415
416 if not unknown:
416 if not unknown:
417 return ret
417 return ret
418
418
419 b = util.sort(self._map)
419 b = util.sort(self._map)
420 blen = len(b)
420 blen = len(b)
421
421
422 for x in unknown:
422 for x in unknown:
423 bs = bisect.bisect(b, "%s%s" % (x, '/'))
423 bs = bisect.bisect(b, "%s%s" % (x, '/'))
424 while bs < blen:
424 while bs < blen:
425 s = b[bs]
425 s = b[bs]
426 if len(s) > len(x) and s.startswith(x):
426 if len(s) > len(x) and s.startswith(x):
427 ret[s] = self._map[s]
427 ret[s] = self._map[s]
428 else:
428 else:
429 break
429 break
430 bs += 1
430 bs += 1
431 return ret
431 return ret
432
432
433 def _supported(self, f, mode, verbose=False):
433 def _supported(self, f, mode, verbose=False):
434 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
434 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
435 return True
435 return True
436 if verbose:
436 if verbose:
437 kind = 'unknown'
437 kind = 'unknown'
438 if stat.S_ISCHR(mode): kind = _('character device')
438 if stat.S_ISCHR(mode): kind = _('character device')
439 elif stat.S_ISBLK(mode): kind = _('block device')
439 elif stat.S_ISBLK(mode): kind = _('block device')
440 elif stat.S_ISFIFO(mode): kind = _('fifo')
440 elif stat.S_ISFIFO(mode): kind = _('fifo')
441 elif stat.S_ISSOCK(mode): kind = _('socket')
441 elif stat.S_ISSOCK(mode): kind = _('socket')
442 elif stat.S_ISDIR(mode): kind = _('directory')
442 elif stat.S_ISDIR(mode): kind = _('directory')
443 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
443 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
444 % (self.pathto(f), kind))
444 % (self.pathto(f), kind))
445 return False
445 return False
446
446
447 def _dirignore(self, f):
447 def _dirignore(self, f):
448 if f == '.':
448 if f == '.':
449 return False
449 return False
450 if self._ignore(f):
450 if self._ignore(f):
451 return True
451 return True
452 for p in _finddirs(f):
452 for p in _finddirs(f):
453 if self._ignore(p):
453 if self._ignore(p):
454 return True
454 return True
455 return False
455 return False
456
456
457 def walk(self, match, unknown, ignored):
457 def walk(self, match, unknown, ignored):
458 '''
458 '''
459 walk recursively through the directory tree, finding all files
459 walk recursively through the directory tree, finding all files
460 matched by the match function
460 matched by the match function
461
461
462 results are yielded in a tuple (src, filename, st), where src
462 results are yielded in a tuple (filename, stat), where stat
463 is one of:
464 'f' the file was found in the directory tree
465 'm' the file was only in the dirstate and not in the tree
466
467 and st is the stat result if the file was found in the directory.
463 and st is the stat result if the file was found in the directory.
468 '''
464 '''
469
465
470 def fwarn(f, msg):
466 def fwarn(f, msg):
471 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
467 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
472 return False
468 return False
473 badfn = fwarn
469 badfn = fwarn
474 if hasattr(match, 'bad'):
470 if hasattr(match, 'bad'):
475 badfn = match.bad
471 badfn = match.bad
476
472
477 # walk all files by default
473 # walk all files by default
478 files = match.files()
474 files = match.files()
479 if not files:
475 if not files:
480 files = ['.']
476 files = ['.']
481 dc = self._map.copy()
477 dc = self._map.copy()
482 else:
478 else:
483 files = util.unique(files)
479 files = util.unique(files)
484 dc = self._filter(files)
480 dc = self._filter(files)
485
481
486 def imatch(file_):
482 def imatch(file_):
487 if file_ not in dc and self._ignore(file_):
483 if file_ not in dc and self._ignore(file_):
488 return False
484 return False
489 return match(file_)
485 return match(file_)
490
486
491 # TODO: don't walk unknown directories if unknown and ignored are False
487 # TODO: don't walk unknown directories if unknown and ignored are False
492 ignore = self._ignore
488 ignore = self._ignore
493 dirignore = self._dirignore
489 dirignore = self._dirignore
494 if ignored:
490 if ignored:
495 imatch = match
491 imatch = match
496 ignore = util.never
492 ignore = util.never
497 dirignore = util.never
493 dirignore = util.never
498
494
499 # self._root may end with a path separator when self._root == '/'
495 # self._root may end with a path separator when self._root == '/'
500 common_prefix_len = len(self._root)
496 common_prefix_len = len(self._root)
501 if not util.endswithsep(self._root):
497 if not util.endswithsep(self._root):
502 common_prefix_len += 1
498 common_prefix_len += 1
503
499
504 normpath = util.normpath
500 normpath = util.normpath
505 listdir = osutil.listdir
501 listdir = osutil.listdir
506 lstat = os.lstat
502 lstat = os.lstat
507 bisect_left = bisect.bisect_left
503 bisect_left = bisect.bisect_left
508 isdir = os.path.isdir
504 isdir = os.path.isdir
509 pconvert = util.pconvert
505 pconvert = util.pconvert
510 join = os.path.join
506 join = os.path.join
511 s_isdir = stat.S_ISDIR
507 s_isdir = stat.S_ISDIR
512 supported = self._supported
508 supported = self._supported
513 _join = self._join
509 _join = self._join
514 known = {'.hg': 1}
510 known = {'.hg': 1}
515
511
516 # recursion free walker, faster than os.walk.
512 # recursion free walker, faster than os.walk.
517 def findfiles(s):
513 def findfiles(s):
518 work = [s]
514 work = [s]
519 wadd = work.append
515 wadd = work.append
520 found = []
516 found = []
521 add = found.append
517 add = found.append
522 if hasattr(match, 'dir'):
518 if hasattr(match, 'dir'):
523 match.dir(normpath(s[common_prefix_len:]))
519 match.dir(normpath(s[common_prefix_len:]))
524 while work:
520 while work:
525 top = work.pop()
521 top = work.pop()
526 entries = listdir(top, stat=True)
522 entries = listdir(top, stat=True)
527 # nd is the top of the repository dir tree
523 # nd is the top of the repository dir tree
528 nd = normpath(top[common_prefix_len:])
524 nd = normpath(top[common_prefix_len:])
529 if nd == '.':
525 if nd == '.':
530 nd = ''
526 nd = ''
531 else:
527 else:
532 # do not recurse into a repo contained in this
528 # do not recurse into a repo contained in this
533 # one. use bisect to find .hg directory so speed
529 # one. use bisect to find .hg directory so speed
534 # is good on big directory.
530 # is good on big directory.
535 names = [e[0] for e in entries]
531 names = [e[0] for e in entries]
536 hg = bisect_left(names, '.hg')
532 hg = bisect_left(names, '.hg')
537 if hg < len(names) and names[hg] == '.hg':
533 if hg < len(names) and names[hg] == '.hg':
538 if isdir(join(top, '.hg')):
534 if isdir(join(top, '.hg')):
539 continue
535 continue
540 for f, kind, st in entries:
536 for f, kind, st in entries:
541 np = pconvert(join(nd, f))
537 np = pconvert(join(nd, f))
542 nn = self.normalize(np)
538 nn = self.normalize(np)
543 if np in known:
539 if np in known:
544 continue
540 continue
545 known[nn] = 1
541 known[nn] = 1
546 p = join(top, f)
542 p = join(top, f)
547 # don't trip over symlinks
543 # don't trip over symlinks
548 if kind == stat.S_IFDIR:
544 if kind == stat.S_IFDIR:
549 if not ignore(np):
545 if not ignore(np):
550 wadd(p)
546 wadd(p)
551 if hasattr(match, 'dir'):
547 if hasattr(match, 'dir'):
552 match.dir(np)
548 match.dir(np)
553 if np in dc and match(np):
549 if np in dc and match(np):
554 add((nn, 'm', st))
550 add((nn, None))
555 elif imatch(np):
551 elif imatch(np):
556 if supported(np, st.st_mode):
552 if supported(np, st.st_mode):
557 add((nn, 'f', st))
553 add((nn, st))
558 elif np in dc:
554 elif np in dc:
559 add((nn, 'm', st))
555 add((nn, None))
560 return util.sort(found)
556 return util.sort(found)
561
557
562 # step one, find all files that match our criteria
558 # step one, find all files that match our criteria
563 for ff in util.sort(files):
559 for ff in util.sort(files):
564 nf = normpath(ff)
560 nf = normpath(ff)
565 nn = self.normalize(nf)
561 nn = self.normalize(nf)
566 f = _join(ff)
562 f = _join(ff)
567 try:
563 try:
568 st = lstat(f)
564 st = lstat(f)
569 except OSError, inst:
565 except OSError, inst:
570 found = False
566 found = False
571 for fn in dc:
567 for fn in dc:
572 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
568 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
573 found = True
569 found = True
574 break
570 break
575 if not found:
571 if not found:
576 if inst.errno != errno.ENOENT:
572 if inst.errno != errno.ENOENT:
577 fwarn(ff, inst.strerror)
573 fwarn(ff, inst.strerror)
578 elif badfn(ff, inst.strerror) and imatch(nf):
574 elif badfn(ff, inst.strerror) and imatch(nf):
579 yield 'f', ff, None
575 yield ff, None
580 continue
576 continue
581 if s_isdir(st.st_mode):
577 if s_isdir(st.st_mode):
582 if not dirignore(nf):
578 if not dirignore(nf):
583 for f, src, st in findfiles(f):
579 for e in findfiles(f):
584 yield src, f, st
580 yield e
585 else:
581 else:
586 if nn in known:
582 if nn in known:
587 continue
583 continue
588 known[nn] = 1
584 known[nn] = 1
589 if match(nf):
585 if match(nf):
590 if supported(ff, st.st_mode, verbose=True):
586 if supported(ff, st.st_mode, verbose=True):
591 yield 'f', nn, st
587 yield nn, st
592 elif ff in dc:
588 elif ff in dc:
593 yield 'm', nf, st
589 yield nf, None
594
590
595 # step two run through anything left in the dc hash and yield
591 # step two run through anything left in the dc hash and yield
596 # if we haven't already seen it
592 # if we haven't already seen it
597 for k in util.sort(dc):
593 for f in util.sort(dc):
598 if k in known:
594 if f in known:
599 continue
595 continue
600 known[k] = 1
596 known[f] = 1
601 if imatch(k):
597 if imatch(f):
602 yield 'm', k, None
598 try:
599 st = lstat(_join(f))
600 if supported(f, st.st_mode):
601 yield f, st
602 continue
603 except OSError, inst:
604 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
605 raise
606 yield f, None
603
607
604 def status(self, match, ignored, clean, unknown):
608 def status(self, match, ignored, clean, unknown):
605 listignored, listclean, listunknown = ignored, clean, unknown
609 listignored, listclean, listunknown = ignored, clean, unknown
606 lookup, modified, added, unknown, ignored = [], [], [], [], []
610 lookup, modified, added, unknown, ignored = [], [], [], [], []
607 removed, deleted, clean = [], [], []
611 removed, deleted, clean = [], [], []
608
612
609 _join = self._join
613 _join = self._join
610 lstat = os.lstat
614 lstat = os.lstat
611 cmap = self._copymap
615 cmap = self._copymap
612 dmap = self._map
616 dmap = self._map
613 ladd = lookup.append
617 ladd = lookup.append
614 madd = modified.append
618 madd = modified.append
615 aadd = added.append
619 aadd = added.append
616 uadd = unknown.append
620 uadd = unknown.append
617 iadd = ignored.append
621 iadd = ignored.append
618 radd = removed.append
622 radd = removed.append
619 dadd = deleted.append
623 dadd = deleted.append
620 cadd = clean.append
624 cadd = clean.append
621
625
622 for src, fn, st in self.walk(match, listunknown, listignored):
626 for fn, st in self.walk(match, listunknown, listignored):
623 if fn not in dmap:
627 if fn not in dmap:
624 if (listignored or match.exact(fn)) and self._dirignore(fn):
628 if (listignored or match.exact(fn)) and self._dirignore(fn):
625 if listignored:
629 if listignored:
626 iadd(fn)
630 iadd(fn)
627 elif listunknown:
631 elif listunknown:
628 uadd(fn)
632 uadd(fn)
629 continue
633 continue
630
634
631 state, mode, size, time, foo = dmap[fn]
635 state, mode, size, time, foo = dmap[fn]
632
636
633 if src == 'm':
637 if not st and state in "nma":
634 nonexistent = True
638 dadd(fn)
635 if not st:
639 elif state == 'n':
636 try:
637 st = lstat(_join(fn))
638 except OSError, inst:
639 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
640 raise
641 st = None
642 # We need to re-check that it is a valid file
643 if st and self._supported(fn, st.st_mode):
644 nonexistent = False
645 if nonexistent and state in "nma":
646 dadd(fn)
647 continue
648 # check the common case first
649 if state == 'n':
650 if not st:
651 st = lstat(_join(fn))
652 if (size >= 0 and
640 if (size >= 0 and
653 (size != st.st_size
641 (size != st.st_size
654 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
642 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
655 or size == -2
643 or size == -2
656 or fn in self._copymap):
644 or fn in self._copymap):
657 madd(fn)
645 madd(fn)
658 elif time != int(st.st_mtime):
646 elif time != int(st.st_mtime):
659 ladd(fn)
647 ladd(fn)
660 elif listclean:
648 elif listclean:
661 cadd(fn)
649 cadd(fn)
662 elif state == 'm':
650 elif state == 'm':
663 madd(fn)
651 madd(fn)
664 elif state == 'a':
652 elif state == 'a':
665 aadd(fn)
653 aadd(fn)
666 elif state == 'r':
654 elif state == 'r':
667 radd(fn)
655 radd(fn)
668
656
669 return (lookup, modified, added, removed, deleted, unknown, ignored,
657 return (lookup, modified, added, removed, deleted, unknown, ignored,
670 clean)
658 clean)
General Comments 0
You need to be logged in to leave comments. Login now