##// END OF EJS Templates
dirstate: lazify initdirs
Matt Mackall -
r4605:9da7a4da default
parent child Browse files
Show More
@@ -1,589 +1,583 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 Copyright 2005, 2006 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 *
10 from node import *
11 from i18n import _
11 from i18n import _
12 import struct, os, time, bisect, stat, strutil, util, re, errno
12 import struct, os, time, bisect, stat, strutil, util, re, errno
13 import cStringIO
13 import cStringIO
14
14
15 class dirstate(object):
15 class dirstate(object):
16 format = ">cllll"
16 format = ">cllll"
17
17
18 def __init__(self, opener, ui, root):
18 def __init__(self, opener, ui, root):
19 self.opener = opener
19 self.opener = opener
20 self.root = root
20 self.root = root
21 self.dirty = 0
21 self.dirty = 0
22 self.ui = ui
22 self.ui = ui
23 self.dirs = None
24 self.ignorefunc = None
23 self.ignorefunc = None
25 self._slash = None
24 self._slash = None
26
25
27 def __getattr__(self, name):
26 def __getattr__(self, name):
28 if name == 'map':
27 if name == 'map':
29 self.read()
28 self.read()
30 return self.map
29 return self.map
31 elif name == 'copymap':
30 elif name == 'copymap':
32 self.read()
31 self.read()
33 return self.copymap
32 return self.copymap
34 elif name == '_branch':
33 elif name == '_branch':
35 try:
34 try:
36 self._branch = self.opener("branch").read().strip()\
35 self._branch = self.opener("branch").read().strip()\
37 or "default"
36 or "default"
38 except IOError:
37 except IOError:
39 self._branch = "default"
38 self._branch = "default"
40 return self._branch
39 return self._branch
41 elif name == 'pl':
40 elif name == 'pl':
42 self.pl = [nullid, nullid]
41 self.pl = [nullid, nullid]
43 try:
42 try:
44 st = self.opener("dirstate").read(40)
43 st = self.opener("dirstate").read(40)
45 if len(st) == 40:
44 if len(st) == 40:
46 self.pl = st[:20], st[20:40]
45 self.pl = st[:20], st[20:40]
47 except IOError, err:
46 except IOError, err:
48 if err.errno != errno.ENOENT: raise
47 if err.errno != errno.ENOENT: raise
49 return self.pl
48 return self.pl
49 elif name == 'dirs':
50 self.dirs = {}
51 for f in self.map:
52 self.updatedirs(f, 1)
53 return self.dirs
50 else:
54 else:
51 raise AttributeError, name
55 raise AttributeError, name
52
56
53 def wjoin(self, f):
57 def wjoin(self, f):
54 return os.path.join(self.root, f)
58 return os.path.join(self.root, f)
55
59
56 def getcwd(self):
60 def getcwd(self):
57 cwd = os.getcwd()
61 cwd = os.getcwd()
58 if cwd == self.root: return ''
62 if cwd == self.root: return ''
59 # self.root ends with a path separator if self.root is '/' or 'C:\'
63 # self.root ends with a path separator if self.root is '/' or 'C:\'
60 rootsep = self.root
64 rootsep = self.root
61 if not rootsep.endswith(os.sep):
65 if not rootsep.endswith(os.sep):
62 rootsep += os.sep
66 rootsep += os.sep
63 if cwd.startswith(rootsep):
67 if cwd.startswith(rootsep):
64 return cwd[len(rootsep):]
68 return cwd[len(rootsep):]
65 else:
69 else:
66 # we're outside the repo. return an absolute path.
70 # we're outside the repo. return an absolute path.
67 return cwd
71 return cwd
68
72
69 def pathto(self, f, cwd=None):
73 def pathto(self, f, cwd=None):
70 if cwd is None:
74 if cwd is None:
71 cwd = self.getcwd()
75 cwd = self.getcwd()
72 path = util.pathto(self.root, cwd, f)
76 path = util.pathto(self.root, cwd, f)
73 if self._slash is None:
77 if self._slash is None:
74 self._slash = self.ui.configbool('ui', 'slash') and os.sep != '/'
78 self._slash = self.ui.configbool('ui', 'slash') and os.sep != '/'
75 if self._slash:
79 if self._slash:
76 path = path.replace(os.sep, '/')
80 path = path.replace(os.sep, '/')
77 return path
81 return path
78
82
79 def hgignore(self):
83 def hgignore(self):
80 '''return the contents of .hgignore files as a list of patterns.
84 '''return the contents of .hgignore files as a list of patterns.
81
85
82 the files parsed for patterns include:
86 the files parsed for patterns include:
83 .hgignore in the repository root
87 .hgignore in the repository root
84 any additional files specified in the [ui] section of ~/.hgrc
88 any additional files specified in the [ui] section of ~/.hgrc
85
89
86 trailing white space is dropped.
90 trailing white space is dropped.
87 the escape character is backslash.
91 the escape character is backslash.
88 comments start with #.
92 comments start with #.
89 empty lines are skipped.
93 empty lines are skipped.
90
94
91 lines can be of the following formats:
95 lines can be of the following formats:
92
96
93 syntax: regexp # defaults following lines to non-rooted regexps
97 syntax: regexp # defaults following lines to non-rooted regexps
94 syntax: glob # defaults following lines to non-rooted globs
98 syntax: glob # defaults following lines to non-rooted globs
95 re:pattern # non-rooted regular expression
99 re:pattern # non-rooted regular expression
96 glob:pattern # non-rooted glob
100 glob:pattern # non-rooted glob
97 pattern # pattern of the current default type'''
101 pattern # pattern of the current default type'''
98 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
102 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
99 def parselines(fp):
103 def parselines(fp):
100 for line in fp:
104 for line in fp:
101 if not line.endswith('\n'):
105 if not line.endswith('\n'):
102 line += '\n'
106 line += '\n'
103 escape = False
107 escape = False
104 for i in xrange(len(line)):
108 for i in xrange(len(line)):
105 if escape: escape = False
109 if escape: escape = False
106 elif line[i] == '\\': escape = True
110 elif line[i] == '\\': escape = True
107 elif line[i] == '#': break
111 elif line[i] == '#': break
108 line = line[:i].rstrip()
112 line = line[:i].rstrip()
109 if line: yield line
113 if line: yield line
110 repoignore = self.wjoin('.hgignore')
114 repoignore = self.wjoin('.hgignore')
111 files = [repoignore]
115 files = [repoignore]
112 files.extend(self.ui.hgignorefiles())
116 files.extend(self.ui.hgignorefiles())
113 pats = {}
117 pats = {}
114 for f in files:
118 for f in files:
115 try:
119 try:
116 pats[f] = []
120 pats[f] = []
117 fp = open(f)
121 fp = open(f)
118 syntax = 'relre:'
122 syntax = 'relre:'
119 for line in parselines(fp):
123 for line in parselines(fp):
120 if line.startswith('syntax:'):
124 if line.startswith('syntax:'):
121 s = line[7:].strip()
125 s = line[7:].strip()
122 try:
126 try:
123 syntax = syntaxes[s]
127 syntax = syntaxes[s]
124 except KeyError:
128 except KeyError:
125 self.ui.warn(_("%s: ignoring invalid "
129 self.ui.warn(_("%s: ignoring invalid "
126 "syntax '%s'\n") % (f, s))
130 "syntax '%s'\n") % (f, s))
127 continue
131 continue
128 pat = syntax + line
132 pat = syntax + line
129 for s in syntaxes.values():
133 for s in syntaxes.values():
130 if line.startswith(s):
134 if line.startswith(s):
131 pat = line
135 pat = line
132 break
136 break
133 pats[f].append(pat)
137 pats[f].append(pat)
134 except IOError, inst:
138 except IOError, inst:
135 if f != repoignore:
139 if f != repoignore:
136 self.ui.warn(_("skipping unreadable ignore file"
140 self.ui.warn(_("skipping unreadable ignore file"
137 " '%s': %s\n") % (f, inst.strerror))
141 " '%s': %s\n") % (f, inst.strerror))
138 return pats
142 return pats
139
143
140 def ignore(self, fn):
144 def ignore(self, fn):
141 '''default match function used by dirstate and
145 '''default match function used by dirstate and
142 localrepository. this honours the repository .hgignore file
146 localrepository. this honours the repository .hgignore file
143 and any other files specified in the [ui] section of .hgrc.'''
147 and any other files specified in the [ui] section of .hgrc.'''
144 if not self.ignorefunc:
148 if not self.ignorefunc:
145 ignore = self.hgignore()
149 ignore = self.hgignore()
146 allpats = []
150 allpats = []
147 [allpats.extend(patlist) for patlist in ignore.values()]
151 [allpats.extend(patlist) for patlist in ignore.values()]
148 if allpats:
152 if allpats:
149 try:
153 try:
150 files, self.ignorefunc, anypats = (
154 files, self.ignorefunc, anypats = (
151 util.matcher(self.root, inc=allpats, src='.hgignore'))
155 util.matcher(self.root, inc=allpats, src='.hgignore'))
152 except util.Abort:
156 except util.Abort:
153 # Re-raise an exception where the src is the right file
157 # Re-raise an exception where the src is the right file
154 for f, patlist in ignore.items():
158 for f, patlist in ignore.items():
155 files, self.ignorefunc, anypats = (
159 files, self.ignorefunc, anypats = (
156 util.matcher(self.root, inc=patlist, src=f))
160 util.matcher(self.root, inc=patlist, src=f))
157 else:
161 else:
158 self.ignorefunc = util.never
162 self.ignorefunc = util.never
159 return self.ignorefunc(fn)
163 return self.ignorefunc(fn)
160
164
161 def __del__(self):
165 def __del__(self):
162 if self.dirty:
166 if self.dirty:
163 self.write()
167 self.write()
164
168
165 def __getitem__(self, key):
169 def __getitem__(self, key):
166 return self.map[key]
170 return self.map[key]
167
171
168 _unknown = ('?', 0, 0, 0)
172 _unknown = ('?', 0, 0, 0)
169
173
170 def get(self, key):
174 def get(self, key):
171 try:
175 try:
172 return self[key]
176 return self[key]
173 except KeyError:
177 except KeyError:
174 return self._unknown
178 return self._unknown
175
179
176 def __contains__(self, key):
180 def __contains__(self, key):
177 return key in self.map
181 return key in self.map
178
182
179 def parents(self):
183 def parents(self):
180 return self.pl
184 return self.pl
181
185
182 def branch(self):
186 def branch(self):
183 return self._branch
187 return self._branch
184
188
185 def markdirty(self):
189 def markdirty(self):
186 if not self.dirty:
190 if not self.dirty:
187 self.dirty = 1
191 self.dirty = 1
188
192
189 def setparents(self, p1, p2=nullid):
193 def setparents(self, p1, p2=nullid):
190 self.markdirty()
194 self.markdirty()
191 self.pl = p1, p2
195 self.pl = p1, p2
192
196
193 def setbranch(self, branch):
197 def setbranch(self, branch):
194 self._branch = branch
198 self._branch = branch
195 self.opener("branch", "w").write(branch + '\n')
199 self.opener("branch", "w").write(branch + '\n')
196
200
197 def state(self, key):
201 def state(self, key):
198 try:
202 try:
199 return self[key][0]
203 return self[key][0]
200 except KeyError:
204 except KeyError:
201 return "?"
205 return "?"
202
206
203 def parse(self, st):
207 def parse(self, st):
204 self.pl = [st[:20], st[20: 40]]
208 self.pl = [st[:20], st[20: 40]]
205
209
206 # deref fields so they will be local in loop
210 # deref fields so they will be local in loop
207 map = self.map
211 map = self.map
208 copymap = self.copymap
212 copymap = self.copymap
209 format = self.format
213 format = self.format
210 unpack = struct.unpack
214 unpack = struct.unpack
211
215
212 pos = 40
216 pos = 40
213 e_size = struct.calcsize(format)
217 e_size = struct.calcsize(format)
214
218
215 while pos < len(st):
219 while pos < len(st):
216 newpos = pos + e_size
220 newpos = pos + e_size
217 e = unpack(format, st[pos:newpos])
221 e = unpack(format, st[pos:newpos])
218 l = e[4]
222 l = e[4]
219 pos = newpos
223 pos = newpos
220 newpos = pos + l
224 newpos = pos + l
221 f = st[pos:newpos]
225 f = st[pos:newpos]
222 if '\0' in f:
226 if '\0' in f:
223 f, c = f.split('\0')
227 f, c = f.split('\0')
224 copymap[f] = c
228 copymap[f] = c
225 map[f] = e[:4]
229 map[f] = e[:4]
226 pos = newpos
230 pos = newpos
227
231
228 def read(self):
232 def read(self):
229 self.map = {}
233 self.map = {}
230 self.copymap = {}
234 self.copymap = {}
231 self.pl = [nullid, nullid]
235 self.pl = [nullid, nullid]
232 try:
236 try:
233 st = self.opener("dirstate").read()
237 st = self.opener("dirstate").read()
234 if st:
238 if st:
235 self.parse(st)
239 self.parse(st)
236 except IOError, err:
240 except IOError, err:
237 if err.errno != errno.ENOENT: raise
241 if err.errno != errno.ENOENT: raise
238
242
239 def reload(self):
243 def reload(self):
240 def mtime():
244 def mtime():
241 m = self.map and self.map.get('.hgignore')
245 m = self.map and self.map.get('.hgignore')
242 return m and m[-1]
246 return m and m[-1]
243
247
244 old_mtime = self.ignorefunc and mtime()
248 old_mtime = self.ignorefunc and mtime()
245 self.read()
249 self.read()
246 if old_mtime != mtime():
250 if old_mtime != mtime():
247 self.ignorefunc = None
251 self.ignorefunc = None
248
252
249 def copy(self, source, dest):
253 def copy(self, source, dest):
250 self.markdirty()
254 self.markdirty()
251 self.copymap[dest] = source
255 self.copymap[dest] = source
252
256
253 def copied(self, file):
257 def copied(self, file):
254 return self.copymap.get(file, None)
258 return self.copymap.get(file, None)
255
259
256 def copies(self):
260 def copies(self):
257 return self.copymap
261 return self.copymap
258
262
259 def initdirs(self):
260 if self.dirs is None:
261 self.dirs = {}
262 for f in self.map:
263 self.updatedirs(f, 1)
264
265 def updatedirs(self, path, delta):
263 def updatedirs(self, path, delta):
266 if self.dirs is not None:
264 for c in strutil.findall(path, '/'):
267 for c in strutil.findall(path, '/'):
265 pc = path[:c]
268 pc = path[:c]
266 self.dirs.setdefault(pc, 0)
269 self.dirs.setdefault(pc, 0)
267 self.dirs[pc] += delta
270 self.dirs[pc] += delta
271
268
272 def checkinterfering(self, files):
269 def checkinterfering(self, files):
273 def prefixes(f):
270 def prefixes(f):
274 for c in strutil.rfindall(f, '/'):
271 for c in strutil.rfindall(f, '/'):
275 yield f[:c]
272 yield f[:c]
276 self.initdirs()
277 seendirs = {}
273 seendirs = {}
278 for f in files:
274 for f in files:
279 # shadows
275 # shadows
280 if self.dirs.get(f):
276 if self.dirs.get(f):
281 raise util.Abort(_('directory named %r already in dirstate') %
277 raise util.Abort(_('directory named %r already in dirstate') %
282 f)
278 f)
283 for d in prefixes(f):
279 for d in prefixes(f):
284 if d in seendirs:
280 if d in seendirs:
285 break
281 break
286 if d in self.map:
282 if d in self.map:
287 raise util.Abort(_('file named %r already in dirstate') %
283 raise util.Abort(_('file named %r already in dirstate') %
288 d)
284 d)
289 seendirs[d] = True
285 seendirs[d] = True
290 # disallowed
286 # disallowed
291 if '\r' in f or '\n' in f:
287 if '\r' in f or '\n' in f:
292 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
288 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
293
289
294 def update(self, files, state, **kw):
290 def update(self, files, state, **kw):
295 ''' current states:
291 ''' current states:
296 n normal
292 n normal
297 m needs merging
293 m needs merging
298 r marked for removal
294 r marked for removal
299 a marked for addition'''
295 a marked for addition'''
300
296
301 if not files: return
297 if not files: return
302 self.markdirty()
298 self.markdirty()
303 if state == "a":
299 if state == "a":
304 self.initdirs()
305 self.checkinterfering(files)
300 self.checkinterfering(files)
306 for f in files:
301 for f in files:
307 if state == "r":
302 if state == "r":
308 self.map[f] = ('r', 0, 0, 0)
303 self.map[f] = ('r', 0, 0, 0)
309 self.updatedirs(f, -1)
304 self.updatedirs(f, -1)
310 else:
305 else:
311 if state == "a":
306 if state == "a":
312 self.updatedirs(f, 1)
307 self.updatedirs(f, 1)
313 s = os.lstat(self.wjoin(f))
308 s = os.lstat(self.wjoin(f))
314 st_size = kw.get('st_size', s.st_size)
309 st_size = kw.get('st_size', s.st_size)
315 st_mtime = kw.get('st_mtime', s.st_mtime)
310 st_mtime = kw.get('st_mtime', s.st_mtime)
316 self.map[f] = (state, s.st_mode, st_size, st_mtime)
311 self.map[f] = (state, s.st_mode, st_size, st_mtime)
317 if self.copymap.has_key(f):
312 if self.copymap.has_key(f):
318 del self.copymap[f]
313 del self.copymap[f]
319
314
320 def forget(self, files):
315 def forget(self, files):
321 if not files: return
316 if not files: return
322 self.markdirty()
317 self.markdirty()
323 self.initdirs()
324 for f in files:
318 for f in files:
325 try:
319 try:
326 del self.map[f]
320 del self.map[f]
327 self.updatedirs(f, -1)
321 self.updatedirs(f, -1)
328 except KeyError:
322 except KeyError:
329 self.ui.warn(_("not in dirstate: %s!\n") % f)
323 self.ui.warn(_("not in dirstate: %s!\n") % f)
330 pass
324 pass
331
325
332 def clear(self):
326 def clear(self):
333 self.map = {}
327 self.map = {}
334 self.copymap = {}
328 self.copymap = {}
335 self.dirs = None
329 self.dirs = None
336 self.markdirty()
330 self.markdirty()
337
331
338 def rebuild(self, parent, files):
332 def rebuild(self, parent, files):
339 self.clear()
333 self.clear()
340 for f in files:
334 for f in files:
341 if files.execf(f):
335 if files.execf(f):
342 self.map[f] = ('n', 0777, -1, 0)
336 self.map[f] = ('n', 0777, -1, 0)
343 else:
337 else:
344 self.map[f] = ('n', 0666, -1, 0)
338 self.map[f] = ('n', 0666, -1, 0)
345 self.pl = (parent, nullid)
339 self.pl = (parent, nullid)
346 self.markdirty()
340 self.markdirty()
347
341
348 def write(self):
342 def write(self):
349 if not self.dirty:
343 if not self.dirty:
350 return
344 return
351 cs = cStringIO.StringIO()
345 cs = cStringIO.StringIO()
352 cs.write("".join(self.pl))
346 cs.write("".join(self.pl))
353 for f, e in self.map.iteritems():
347 for f, e in self.map.iteritems():
354 c = self.copied(f)
348 c = self.copied(f)
355 if c:
349 if c:
356 f = f + "\0" + c
350 f = f + "\0" + c
357 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
351 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
358 cs.write(e)
352 cs.write(e)
359 cs.write(f)
353 cs.write(f)
360 st = self.opener("dirstate", "w", atomictemp=True)
354 st = self.opener("dirstate", "w", atomictemp=True)
361 st.write(cs.getvalue())
355 st.write(cs.getvalue())
362 st.rename()
356 st.rename()
363 self.dirty = 0
357 self.dirty = 0
364
358
365 def filterfiles(self, files):
359 def filterfiles(self, files):
366 ret = {}
360 ret = {}
367 unknown = []
361 unknown = []
368
362
369 for x in files:
363 for x in files:
370 if x == '.':
364 if x == '.':
371 return self.map.copy()
365 return self.map.copy()
372 if x not in self.map:
366 if x not in self.map:
373 unknown.append(x)
367 unknown.append(x)
374 else:
368 else:
375 ret[x] = self.map[x]
369 ret[x] = self.map[x]
376
370
377 if not unknown:
371 if not unknown:
378 return ret
372 return ret
379
373
380 b = self.map.keys()
374 b = self.map.keys()
381 b.sort()
375 b.sort()
382 blen = len(b)
376 blen = len(b)
383
377
384 for x in unknown:
378 for x in unknown:
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
379 bs = bisect.bisect(b, "%s%s" % (x, '/'))
386 while bs < blen:
380 while bs < blen:
387 s = b[bs]
381 s = b[bs]
388 if len(s) > len(x) and s.startswith(x):
382 if len(s) > len(x) and s.startswith(x):
389 ret[s] = self.map[s]
383 ret[s] = self.map[s]
390 else:
384 else:
391 break
385 break
392 bs += 1
386 bs += 1
393 return ret
387 return ret
394
388
395 def supported_type(self, f, st, verbose=False):
389 def supported_type(self, f, st, verbose=False):
396 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
390 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
397 return True
391 return True
398 if verbose:
392 if verbose:
399 kind = 'unknown'
393 kind = 'unknown'
400 if stat.S_ISCHR(st.st_mode): kind = _('character device')
394 if stat.S_ISCHR(st.st_mode): kind = _('character device')
401 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
395 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
402 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
396 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
403 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
397 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
404 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
398 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
405 self.ui.warn(_('%s: unsupported file type (type is %s)\n')
399 self.ui.warn(_('%s: unsupported file type (type is %s)\n')
406 % (self.pathto(f), kind))
400 % (self.pathto(f), kind))
407 return False
401 return False
408
402
409 def walk(self, files=None, match=util.always, badmatch=None):
403 def walk(self, files=None, match=util.always, badmatch=None):
410 # filter out the stat
404 # filter out the stat
411 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
405 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
412 yield src, f
406 yield src, f
413
407
414 def statwalk(self, files=None, match=util.always, ignored=False,
408 def statwalk(self, files=None, match=util.always, ignored=False,
415 badmatch=None, directories=False):
409 badmatch=None, directories=False):
416 '''
410 '''
417 walk recursively through the directory tree, finding all files
411 walk recursively through the directory tree, finding all files
418 matched by the match function
412 matched by the match function
419
413
420 results are yielded in a tuple (src, filename, st), where src
414 results are yielded in a tuple (src, filename, st), where src
421 is one of:
415 is one of:
422 'f' the file was found in the directory tree
416 'f' the file was found in the directory tree
423 'd' the file is a directory of the tree
417 'd' the file is a directory of the tree
424 'm' the file was only in the dirstate and not in the tree
418 'm' the file was only in the dirstate and not in the tree
425 'b' file was not found and matched badmatch
419 'b' file was not found and matched badmatch
426
420
427 and st is the stat result if the file was found in the directory.
421 and st is the stat result if the file was found in the directory.
428 '''
422 '''
429
423
430 # walk all files by default
424 # walk all files by default
431 if not files:
425 if not files:
432 files = ['.']
426 files = ['.']
433 dc = self.map.copy()
427 dc = self.map.copy()
434 else:
428 else:
435 files = util.unique(files)
429 files = util.unique(files)
436 dc = self.filterfiles(files)
430 dc = self.filterfiles(files)
437
431
438 def imatch(file_):
432 def imatch(file_):
439 if file_ not in dc and self.ignore(file_):
433 if file_ not in dc and self.ignore(file_):
440 return False
434 return False
441 return match(file_)
435 return match(file_)
442
436
443 ignore = self.ignore
437 ignore = self.ignore
444 if ignored:
438 if ignored:
445 imatch = match
439 imatch = match
446 ignore = util.never
440 ignore = util.never
447
441
448 # self.root may end with a path separator when self.root == '/'
442 # self.root may end with a path separator when self.root == '/'
449 common_prefix_len = len(self.root)
443 common_prefix_len = len(self.root)
450 if not self.root.endswith(os.sep):
444 if not self.root.endswith(os.sep):
451 common_prefix_len += 1
445 common_prefix_len += 1
452 # recursion free walker, faster than os.walk.
446 # recursion free walker, faster than os.walk.
453 def findfiles(s):
447 def findfiles(s):
454 work = [s]
448 work = [s]
455 if directories:
449 if directories:
456 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
450 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
457 while work:
451 while work:
458 top = work.pop()
452 top = work.pop()
459 names = os.listdir(top)
453 names = os.listdir(top)
460 names.sort()
454 names.sort()
461 # nd is the top of the repository dir tree
455 # nd is the top of the repository dir tree
462 nd = util.normpath(top[common_prefix_len:])
456 nd = util.normpath(top[common_prefix_len:])
463 if nd == '.':
457 if nd == '.':
464 nd = ''
458 nd = ''
465 else:
459 else:
466 # do not recurse into a repo contained in this
460 # do not recurse into a repo contained in this
467 # one. use bisect to find .hg directory so speed
461 # one. use bisect to find .hg directory so speed
468 # is good on big directory.
462 # is good on big directory.
469 hg = bisect.bisect_left(names, '.hg')
463 hg = bisect.bisect_left(names, '.hg')
470 if hg < len(names) and names[hg] == '.hg':
464 if hg < len(names) and names[hg] == '.hg':
471 if os.path.isdir(os.path.join(top, '.hg')):
465 if os.path.isdir(os.path.join(top, '.hg')):
472 continue
466 continue
473 for f in names:
467 for f in names:
474 np = util.pconvert(os.path.join(nd, f))
468 np = util.pconvert(os.path.join(nd, f))
475 if seen(np):
469 if seen(np):
476 continue
470 continue
477 p = os.path.join(top, f)
471 p = os.path.join(top, f)
478 # don't trip over symlinks
472 # don't trip over symlinks
479 st = os.lstat(p)
473 st = os.lstat(p)
480 if stat.S_ISDIR(st.st_mode):
474 if stat.S_ISDIR(st.st_mode):
481 if not ignore(np):
475 if not ignore(np):
482 work.append(p)
476 work.append(p)
483 if directories:
477 if directories:
484 yield 'd', np, st
478 yield 'd', np, st
485 if imatch(np) and np in dc:
479 if imatch(np) and np in dc:
486 yield 'm', np, st
480 yield 'm', np, st
487 elif imatch(np):
481 elif imatch(np):
488 if self.supported_type(np, st):
482 if self.supported_type(np, st):
489 yield 'f', np, st
483 yield 'f', np, st
490 elif np in dc:
484 elif np in dc:
491 yield 'm', np, st
485 yield 'm', np, st
492
486
493 known = {'.hg': 1}
487 known = {'.hg': 1}
494 def seen(fn):
488 def seen(fn):
495 if fn in known: return True
489 if fn in known: return True
496 known[fn] = 1
490 known[fn] = 1
497
491
498 # step one, find all files that match our criteria
492 # step one, find all files that match our criteria
499 files.sort()
493 files.sort()
500 for ff in files:
494 for ff in files:
501 nf = util.normpath(ff)
495 nf = util.normpath(ff)
502 f = self.wjoin(ff)
496 f = self.wjoin(ff)
503 try:
497 try:
504 st = os.lstat(f)
498 st = os.lstat(f)
505 except OSError, inst:
499 except OSError, inst:
506 found = False
500 found = False
507 for fn in dc:
501 for fn in dc:
508 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
502 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
509 found = True
503 found = True
510 break
504 break
511 if not found:
505 if not found:
512 if inst.errno != errno.ENOENT or not badmatch:
506 if inst.errno != errno.ENOENT or not badmatch:
513 self.ui.warn('%s: %s\n' % (self.pathto(ff),
507 self.ui.warn('%s: %s\n' % (self.pathto(ff),
514 inst.strerror))
508 inst.strerror))
515 elif badmatch and badmatch(ff) and imatch(nf):
509 elif badmatch and badmatch(ff) and imatch(nf):
516 yield 'b', ff, None
510 yield 'b', ff, None
517 continue
511 continue
518 if stat.S_ISDIR(st.st_mode):
512 if stat.S_ISDIR(st.st_mode):
519 cmp1 = (lambda x, y: cmp(x[1], y[1]))
513 cmp1 = (lambda x, y: cmp(x[1], y[1]))
520 sorted_ = [ x for x in findfiles(f) ]
514 sorted_ = [ x for x in findfiles(f) ]
521 sorted_.sort(cmp1)
515 sorted_.sort(cmp1)
522 for e in sorted_:
516 for e in sorted_:
523 yield e
517 yield e
524 else:
518 else:
525 if not seen(nf) and match(nf):
519 if not seen(nf) and match(nf):
526 if self.supported_type(ff, st, verbose=True):
520 if self.supported_type(ff, st, verbose=True):
527 yield 'f', nf, st
521 yield 'f', nf, st
528 elif ff in dc:
522 elif ff in dc:
529 yield 'm', nf, st
523 yield 'm', nf, st
530
524
531 # step two run through anything left in the dc hash and yield
525 # step two run through anything left in the dc hash and yield
532 # if we haven't already seen it
526 # if we haven't already seen it
533 ks = dc.keys()
527 ks = dc.keys()
534 ks.sort()
528 ks.sort()
535 for k in ks:
529 for k in ks:
536 if not seen(k) and imatch(k):
530 if not seen(k) and imatch(k):
537 yield 'm', k, None
531 yield 'm', k, None
538
532
539 def status(self, files=None, match=util.always, list_ignored=False,
533 def status(self, files=None, match=util.always, list_ignored=False,
540 list_clean=False):
534 list_clean=False):
541 lookup, modified, added, unknown, ignored = [], [], [], [], []
535 lookup, modified, added, unknown, ignored = [], [], [], [], []
542 removed, deleted, clean = [], [], []
536 removed, deleted, clean = [], [], []
543
537
544 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
538 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
545 try:
539 try:
546 type_, mode, size, time = self[fn]
540 type_, mode, size, time = self[fn]
547 except KeyError:
541 except KeyError:
548 if list_ignored and self.ignore(fn):
542 if list_ignored and self.ignore(fn):
549 ignored.append(fn)
543 ignored.append(fn)
550 else:
544 else:
551 unknown.append(fn)
545 unknown.append(fn)
552 continue
546 continue
553 if src == 'm':
547 if src == 'm':
554 nonexistent = True
548 nonexistent = True
555 if not st:
549 if not st:
556 try:
550 try:
557 st = os.lstat(self.wjoin(fn))
551 st = os.lstat(self.wjoin(fn))
558 except OSError, inst:
552 except OSError, inst:
559 if inst.errno != errno.ENOENT:
553 if inst.errno != errno.ENOENT:
560 raise
554 raise
561 st = None
555 st = None
562 # We need to re-check that it is a valid file
556 # We need to re-check that it is a valid file
563 if st and self.supported_type(fn, st):
557 if st and self.supported_type(fn, st):
564 nonexistent = False
558 nonexistent = False
565 # XXX: what to do with file no longer present in the fs
559 # XXX: what to do with file no longer present in the fs
566 # who are not removed in the dirstate ?
560 # who are not removed in the dirstate ?
567 if nonexistent and type_ in "nm":
561 if nonexistent and type_ in "nm":
568 deleted.append(fn)
562 deleted.append(fn)
569 continue
563 continue
570 # check the common case first
564 # check the common case first
571 if type_ == 'n':
565 if type_ == 'n':
572 if not st:
566 if not st:
573 st = os.lstat(self.wjoin(fn))
567 st = os.lstat(self.wjoin(fn))
574 if size >= 0 and (size != st.st_size
568 if size >= 0 and (size != st.st_size
575 or (mode ^ st.st_mode) & 0100):
569 or (mode ^ st.st_mode) & 0100):
576 modified.append(fn)
570 modified.append(fn)
577 elif time != int(st.st_mtime):
571 elif time != int(st.st_mtime):
578 lookup.append(fn)
572 lookup.append(fn)
579 elif list_clean:
573 elif list_clean:
580 clean.append(fn)
574 clean.append(fn)
581 elif type_ == 'm':
575 elif type_ == 'm':
582 modified.append(fn)
576 modified.append(fn)
583 elif type_ == 'a':
577 elif type_ == 'a':
584 added.append(fn)
578 added.append(fn)
585 elif type_ == 'r':
579 elif type_ == 'r':
586 removed.append(fn)
580 removed.append(fn)
587
581
588 return (lookup, modified, added, removed, deleted, unknown, ignored,
582 return (lookup, modified, added, removed, deleted, unknown, ignored,
589 clean)
583 clean)
General Comments 0
You need to be logged in to leave comments. Login now