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