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