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