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