##// END OF EJS Templates
Fix walkhelper on windows....
Christian Boos -
r1562:2f97af0b default
parent child Browse files
Show More
@@ -1,411 +1,411
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005 Matt Mackall <mpm@selenic.com>
4 Copyright 2005 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 import struct, os
10 import struct, os
11 from node import *
11 from node import *
12 from i18n import gettext as _
12 from i18n import gettext as _
13 from demandload import *
13 from demandload import *
14 demandload(globals(), "time bisect stat util re errno")
14 demandload(globals(), "time bisect stat util re errno")
15
15
16 class dirstate(object):
16 class dirstate(object):
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.pl = None
23 self.pl = None
24 self.copies = {}
24 self.copies = {}
25 self.ignorefunc = None
25 self.ignorefunc = None
26 self.blockignore = False
26 self.blockignore = False
27
27
28 def wjoin(self, f):
28 def wjoin(self, f):
29 return os.path.join(self.root, f)
29 return os.path.join(self.root, f)
30
30
31 def getcwd(self):
31 def getcwd(self):
32 cwd = os.getcwd()
32 cwd = os.getcwd()
33 if cwd == self.root: return ''
33 if cwd == self.root: return ''
34 return cwd[len(self.root) + 1:]
34 return cwd[len(self.root) + 1:]
35
35
36 def hgignore(self):
36 def hgignore(self):
37 '''return the contents of .hgignore as a list of patterns.
37 '''return the contents of .hgignore as a list of patterns.
38
38
39 trailing white space is dropped.
39 trailing white space is dropped.
40 the escape character is backslash.
40 the escape character is backslash.
41 comments start with #.
41 comments start with #.
42 empty lines are skipped.
42 empty lines are skipped.
43
43
44 lines can be of the following formats:
44 lines can be of the following formats:
45
45
46 syntax: regexp # defaults following lines to non-rooted regexps
46 syntax: regexp # defaults following lines to non-rooted regexps
47 syntax: glob # defaults following lines to non-rooted globs
47 syntax: glob # defaults following lines to non-rooted globs
48 re:pattern # non-rooted regular expression
48 re:pattern # non-rooted regular expression
49 glob:pattern # non-rooted glob
49 glob:pattern # non-rooted glob
50 pattern # pattern of the current default type'''
50 pattern # pattern of the current default type'''
51 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
51 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
52 def parselines(fp):
52 def parselines(fp):
53 for line in fp:
53 for line in fp:
54 escape = False
54 escape = False
55 for i in xrange(len(line)):
55 for i in xrange(len(line)):
56 if escape: escape = False
56 if escape: escape = False
57 elif line[i] == '\\': escape = True
57 elif line[i] == '\\': escape = True
58 elif line[i] == '#': break
58 elif line[i] == '#': break
59 line = line[:i].rstrip()
59 line = line[:i].rstrip()
60 if line: yield line
60 if line: yield line
61 pats = []
61 pats = []
62 try:
62 try:
63 fp = open(self.wjoin('.hgignore'))
63 fp = open(self.wjoin('.hgignore'))
64 syntax = 'relre:'
64 syntax = 'relre:'
65 for line in parselines(fp):
65 for line in parselines(fp):
66 if line.startswith('syntax:'):
66 if line.startswith('syntax:'):
67 s = line[7:].strip()
67 s = line[7:].strip()
68 try:
68 try:
69 syntax = syntaxes[s]
69 syntax = syntaxes[s]
70 except KeyError:
70 except KeyError:
71 self.ui.warn(_("ignoring invalid syntax '%s'\n") % s)
71 self.ui.warn(_("ignoring invalid syntax '%s'\n") % s)
72 continue
72 continue
73 pat = syntax + line
73 pat = syntax + line
74 for s in syntaxes.values():
74 for s in syntaxes.values():
75 if line.startswith(s):
75 if line.startswith(s):
76 pat = line
76 pat = line
77 break
77 break
78 pats.append(pat)
78 pats.append(pat)
79 except IOError: pass
79 except IOError: pass
80 return pats
80 return pats
81
81
82 def ignore(self, fn):
82 def ignore(self, fn):
83 '''default match function used by dirstate and localrepository.
83 '''default match function used by dirstate and localrepository.
84 this honours the .hgignore file, and nothing more.'''
84 this honours the .hgignore file, and nothing more.'''
85 if self.blockignore:
85 if self.blockignore:
86 return False
86 return False
87 if not self.ignorefunc:
87 if not self.ignorefunc:
88 ignore = self.hgignore()
88 ignore = self.hgignore()
89 if ignore:
89 if ignore:
90 files, self.ignorefunc, anypats = util.matcher(self.root,
90 files, self.ignorefunc, anypats = util.matcher(self.root,
91 inc=ignore)
91 inc=ignore)
92 else:
92 else:
93 self.ignorefunc = util.never
93 self.ignorefunc = util.never
94 return self.ignorefunc(fn)
94 return self.ignorefunc(fn)
95
95
96 def __del__(self):
96 def __del__(self):
97 if self.dirty:
97 if self.dirty:
98 self.write()
98 self.write()
99
99
100 def __getitem__(self, key):
100 def __getitem__(self, key):
101 try:
101 try:
102 return self.map[key]
102 return self.map[key]
103 except TypeError:
103 except TypeError:
104 self.lazyread()
104 self.lazyread()
105 return self[key]
105 return self[key]
106
106
107 def __contains__(self, key):
107 def __contains__(self, key):
108 self.lazyread()
108 self.lazyread()
109 return key in self.map
109 return key in self.map
110
110
111 def parents(self):
111 def parents(self):
112 self.lazyread()
112 self.lazyread()
113 return self.pl
113 return self.pl
114
114
115 def markdirty(self):
115 def markdirty(self):
116 if not self.dirty:
116 if not self.dirty:
117 self.dirty = 1
117 self.dirty = 1
118
118
119 def setparents(self, p1, p2=nullid):
119 def setparents(self, p1, p2=nullid):
120 self.lazyread()
120 self.lazyread()
121 self.markdirty()
121 self.markdirty()
122 self.pl = p1, p2
122 self.pl = p1, p2
123
123
124 def state(self, key):
124 def state(self, key):
125 try:
125 try:
126 return self[key][0]
126 return self[key][0]
127 except KeyError:
127 except KeyError:
128 return "?"
128 return "?"
129
129
130 def lazyread(self):
130 def lazyread(self):
131 if self.map is None:
131 if self.map is None:
132 self.read()
132 self.read()
133
133
134 def read(self):
134 def read(self):
135 self.map = {}
135 self.map = {}
136 self.pl = [nullid, nullid]
136 self.pl = [nullid, nullid]
137 try:
137 try:
138 st = self.opener("dirstate").read()
138 st = self.opener("dirstate").read()
139 if not st: return
139 if not st: return
140 except: return
140 except: return
141
141
142 self.pl = [st[:20], st[20: 40]]
142 self.pl = [st[:20], st[20: 40]]
143
143
144 pos = 40
144 pos = 40
145 while pos < len(st):
145 while pos < len(st):
146 e = struct.unpack(">cllll", st[pos:pos+17])
146 e = struct.unpack(">cllll", st[pos:pos+17])
147 l = e[4]
147 l = e[4]
148 pos += 17
148 pos += 17
149 f = st[pos:pos + l]
149 f = st[pos:pos + l]
150 if '\0' in f:
150 if '\0' in f:
151 f, c = f.split('\0')
151 f, c = f.split('\0')
152 self.copies[f] = c
152 self.copies[f] = c
153 self.map[f] = e[:4]
153 self.map[f] = e[:4]
154 pos += l
154 pos += l
155
155
156 def copy(self, source, dest):
156 def copy(self, source, dest):
157 self.lazyread()
157 self.lazyread()
158 self.markdirty()
158 self.markdirty()
159 self.copies[dest] = source
159 self.copies[dest] = source
160
160
161 def copied(self, file):
161 def copied(self, file):
162 return self.copies.get(file, None)
162 return self.copies.get(file, None)
163
163
164 def update(self, files, state, **kw):
164 def update(self, files, state, **kw):
165 ''' current states:
165 ''' current states:
166 n normal
166 n normal
167 m needs merging
167 m needs merging
168 r marked for removal
168 r marked for removal
169 a marked for addition'''
169 a marked for addition'''
170
170
171 if not files: return
171 if not files: return
172 self.lazyread()
172 self.lazyread()
173 self.markdirty()
173 self.markdirty()
174 for f in files:
174 for f in files:
175 if state == "r":
175 if state == "r":
176 self.map[f] = ('r', 0, 0, 0)
176 self.map[f] = ('r', 0, 0, 0)
177 else:
177 else:
178 s = os.lstat(self.wjoin(f))
178 s = os.lstat(self.wjoin(f))
179 st_size = kw.get('st_size', s.st_size)
179 st_size = kw.get('st_size', s.st_size)
180 st_mtime = kw.get('st_mtime', s.st_mtime)
180 st_mtime = kw.get('st_mtime', s.st_mtime)
181 self.map[f] = (state, s.st_mode, st_size, st_mtime)
181 self.map[f] = (state, s.st_mode, st_size, st_mtime)
182 if self.copies.has_key(f):
182 if self.copies.has_key(f):
183 del self.copies[f]
183 del self.copies[f]
184
184
185 def forget(self, files):
185 def forget(self, files):
186 if not files: return
186 if not files: return
187 self.lazyread()
187 self.lazyread()
188 self.markdirty()
188 self.markdirty()
189 for f in files:
189 for f in files:
190 try:
190 try:
191 del self.map[f]
191 del self.map[f]
192 except KeyError:
192 except KeyError:
193 self.ui.warn(_("not in dirstate: %s!\n") % f)
193 self.ui.warn(_("not in dirstate: %s!\n") % f)
194 pass
194 pass
195
195
196 def clear(self):
196 def clear(self):
197 self.map = {}
197 self.map = {}
198 self.markdirty()
198 self.markdirty()
199
199
200 def write(self):
200 def write(self):
201 st = self.opener("dirstate", "w", atomic=True)
201 st = self.opener("dirstate", "w", atomic=True)
202 st.write("".join(self.pl))
202 st.write("".join(self.pl))
203 for f, e in self.map.items():
203 for f, e in self.map.items():
204 c = self.copied(f)
204 c = self.copied(f)
205 if c:
205 if c:
206 f = f + "\0" + c
206 f = f + "\0" + c
207 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
207 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
208 st.write(e + f)
208 st.write(e + f)
209 self.dirty = 0
209 self.dirty = 0
210
210
211 def filterfiles(self, files):
211 def filterfiles(self, files):
212 ret = {}
212 ret = {}
213 unknown = []
213 unknown = []
214
214
215 for x in files:
215 for x in files:
216 if x == '.':
216 if x == '.':
217 return self.map.copy()
217 return self.map.copy()
218 if x not in self.map:
218 if x not in self.map:
219 unknown.append(x)
219 unknown.append(x)
220 else:
220 else:
221 ret[x] = self.map[x]
221 ret[x] = self.map[x]
222
222
223 if not unknown:
223 if not unknown:
224 return ret
224 return ret
225
225
226 b = self.map.keys()
226 b = self.map.keys()
227 b.sort()
227 b.sort()
228 blen = len(b)
228 blen = len(b)
229
229
230 for x in unknown:
230 for x in unknown:
231 bs = bisect.bisect(b, x)
231 bs = bisect.bisect(b, x)
232 if bs != 0 and b[bs-1] == x:
232 if bs != 0 and b[bs-1] == x:
233 ret[x] = self.map[x]
233 ret[x] = self.map[x]
234 continue
234 continue
235 while bs < blen:
235 while bs < blen:
236 s = b[bs]
236 s = b[bs]
237 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
237 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
238 ret[s] = self.map[s]
238 ret[s] = self.map[s]
239 else:
239 else:
240 break
240 break
241 bs += 1
241 bs += 1
242 return ret
242 return ret
243
243
244 def supported_type(self, f, st, verbose=False):
244 def supported_type(self, f, st, verbose=False):
245 if stat.S_ISREG(st.st_mode):
245 if stat.S_ISREG(st.st_mode):
246 return True
246 return True
247 if verbose:
247 if verbose:
248 kind = 'unknown'
248 kind = 'unknown'
249 if stat.S_ISCHR(st.st_mode): kind = _('character device')
249 if stat.S_ISCHR(st.st_mode): kind = _('character device')
250 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
250 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
251 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
251 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
252 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
252 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
253 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
253 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
254 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
254 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
255 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
255 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
256 util.pathto(self.getcwd(), f),
256 util.pathto(self.getcwd(), f),
257 kind))
257 kind))
258 return False
258 return False
259
259
260 def statwalk(self, files=None, match=util.always, dc=None):
260 def statwalk(self, files=None, match=util.always, dc=None):
261 self.lazyread()
261 self.lazyread()
262
262
263 # walk all files by default
263 # walk all files by default
264 if not files:
264 if not files:
265 files = [self.root]
265 files = [self.root]
266 if not dc:
266 if not dc:
267 dc = self.map.copy()
267 dc = self.map.copy()
268 elif not dc:
268 elif not dc:
269 dc = self.filterfiles(files)
269 dc = self.filterfiles(files)
270
270
271 def statmatch(file, stat):
271 def statmatch(file, stat):
272 file = util.pconvert(file)
272 file = util.pconvert(file)
273 if file not in dc and self.ignore(file):
273 if file not in dc and self.ignore(file):
274 return False
274 return False
275 return match(file)
275 return match(file)
276
276
277 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
277 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
278
278
279 def walk(self, files=None, match=util.always, dc=None):
279 def walk(self, files=None, match=util.always, dc=None):
280 # filter out the stat
280 # filter out the stat
281 for src, f, st in self.statwalk(files, match, dc):
281 for src, f, st in self.statwalk(files, match, dc):
282 yield src, f
282 yield src, f
283
283
284 # walk recursively through the directory tree, finding all files
284 # walk recursively through the directory tree, finding all files
285 # matched by the statmatch function
285 # matched by the statmatch function
286 #
286 #
287 # results are yielded in a tuple (src, filename, st), where src
287 # results are yielded in a tuple (src, filename, st), where src
288 # is one of:
288 # is one of:
289 # 'f' the file was found in the directory tree
289 # 'f' the file was found in the directory tree
290 # 'm' the file was only in the dirstate and not in the tree
290 # 'm' the file was only in the dirstate and not in the tree
291 # and st is the stat result if the file was found in the directory.
291 # and st is the stat result if the file was found in the directory.
292 #
292 #
293 # dc is an optional arg for the current dirstate. dc is not modified
293 # dc is an optional arg for the current dirstate. dc is not modified
294 # directly by this function, but might be modified by your statmatch call.
294 # directly by this function, but might be modified by your statmatch call.
295 #
295 #
296 def walkhelper(self, files, statmatch, dc):
296 def walkhelper(self, files, statmatch, dc):
297 # recursion free walker, faster than os.walk.
297 # recursion free walker, faster than os.walk.
298 def findfiles(s):
298 def findfiles(s):
299 work = [s]
299 work = [s]
300 while work:
300 while work:
301 top = work.pop()
301 top = work.pop()
302 names = os.listdir(top)
302 names = os.listdir(top)
303 names.sort()
303 names.sort()
304 # nd is the top of the repository dir tree
304 # nd is the top of the repository dir tree
305 nd = util.normpath(top[len(self.root) + 1:])
305 nd = util.normpath(top[len(self.root) + 1:])
306 if nd == '.': nd = ''
306 if nd == '.': nd = ''
307 for f in names:
307 for f in names:
308 np = os.path.join(nd, f)
308 np = util.pconvert(os.path.join(nd, f))
309 if seen(np):
309 if seen(np):
310 continue
310 continue
311 p = os.path.join(top, f)
311 p = os.path.join(top, f)
312 # don't trip over symlinks
312 # don't trip over symlinks
313 st = os.lstat(p)
313 st = os.lstat(p)
314 if stat.S_ISDIR(st.st_mode):
314 if stat.S_ISDIR(st.st_mode):
315 ds = os.path.join(nd, f +'/')
315 ds = os.path.join(nd, f +'/')
316 if statmatch(ds, st):
316 if statmatch(ds, st):
317 work.append(p)
317 work.append(p)
318 if statmatch(np, st) and np in dc:
318 if statmatch(np, st) and np in dc:
319 yield 'm', util.pconvert(np), st
319 yield 'm', np, st
320 elif statmatch(np, st):
320 elif statmatch(np, st):
321 if self.supported_type(np, st):
321 if self.supported_type(np, st):
322 yield 'f', util.pconvert(np), st
322 yield 'f', np, st
323 elif np in dc:
323 elif np in dc:
324 yield 'm', util.pconvert(np), st
324 yield 'm', np, st
325
325
326 known = {'.hg': 1}
326 known = {'.hg': 1}
327 def seen(fn):
327 def seen(fn):
328 if fn in known: return True
328 if fn in known: return True
329 known[fn] = 1
329 known[fn] = 1
330
330
331 # step one, find all files that match our criteria
331 # step one, find all files that match our criteria
332 files.sort()
332 files.sort()
333 for ff in util.unique(files):
333 for ff in util.unique(files):
334 f = self.wjoin(ff)
334 f = self.wjoin(ff)
335 try:
335 try:
336 st = os.lstat(f)
336 st = os.lstat(f)
337 except OSError, inst:
337 except OSError, inst:
338 if ff not in dc: self.ui.warn('%s: %s\n' % (
338 if ff not in dc: self.ui.warn('%s: %s\n' % (
339 util.pathto(self.getcwd(), ff),
339 util.pathto(self.getcwd(), ff),
340 inst.strerror))
340 inst.strerror))
341 continue
341 continue
342 if stat.S_ISDIR(st.st_mode):
342 if stat.S_ISDIR(st.st_mode):
343 cmp1 = (lambda x, y: cmp(x[1], y[1]))
343 cmp1 = (lambda x, y: cmp(x[1], y[1]))
344 sorted = [ x for x in findfiles(f) ]
344 sorted = [ x for x in findfiles(f) ]
345 sorted.sort(cmp1)
345 sorted.sort(cmp1)
346 for e in sorted:
346 for e in sorted:
347 yield e
347 yield e
348 else:
348 else:
349 ff = util.normpath(ff)
349 ff = util.normpath(ff)
350 if seen(ff):
350 if seen(ff):
351 continue
351 continue
352 self.blockignore = True
352 self.blockignore = True
353 if statmatch(ff, st):
353 if statmatch(ff, st):
354 if self.supported_type(ff, st, verbose=True):
354 if self.supported_type(ff, st, verbose=True):
355 yield 'f', ff, st
355 yield 'f', ff, st
356 elif ff in dc:
356 elif ff in dc:
357 yield 'm', ff, st
357 yield 'm', ff, st
358 self.blockignore = False
358 self.blockignore = False
359
359
360 # step two run through anything left in the dc hash and yield
360 # step two run through anything left in the dc hash and yield
361 # if we haven't already seen it
361 # if we haven't already seen it
362 ks = dc.keys()
362 ks = dc.keys()
363 ks.sort()
363 ks.sort()
364 for k in ks:
364 for k in ks:
365 if not seen(k) and (statmatch(k, None)):
365 if not seen(k) and (statmatch(k, None)):
366 yield 'm', k, None
366 yield 'm', k, None
367
367
368 def changes(self, files=None, match=util.always):
368 def changes(self, files=None, match=util.always):
369 lookup, modified, added, unknown = [], [], [], []
369 lookup, modified, added, unknown = [], [], [], []
370 removed, deleted = [], []
370 removed, deleted = [], []
371
371
372 for src, fn, st in self.statwalk(files, match):
372 for src, fn, st in self.statwalk(files, match):
373 try:
373 try:
374 type, mode, size, time = self[fn]
374 type, mode, size, time = self[fn]
375 except KeyError:
375 except KeyError:
376 unknown.append(fn)
376 unknown.append(fn)
377 continue
377 continue
378 if src == 'm':
378 if src == 'm':
379 nonexistent = True
379 nonexistent = True
380 if not st:
380 if not st:
381 try:
381 try:
382 f = self.wjoin(fn)
382 f = self.wjoin(fn)
383 st = os.lstat(f)
383 st = os.lstat(f)
384 except OSError, inst:
384 except OSError, inst:
385 if inst.errno != errno.ENOENT:
385 if inst.errno != errno.ENOENT:
386 raise
386 raise
387 st = None
387 st = None
388 # We need to re-check that it is a valid file
388 # We need to re-check that it is a valid file
389 if st and self.supported_type(fn, st):
389 if st and self.supported_type(fn, st):
390 nonexistent = False
390 nonexistent = False
391 # XXX: what to do with file no longer present in the fs
391 # XXX: what to do with file no longer present in the fs
392 # who are not removed in the dirstate ?
392 # who are not removed in the dirstate ?
393 if nonexistent and type in "nm":
393 if nonexistent and type in "nm":
394 deleted.append(fn)
394 deleted.append(fn)
395 continue
395 continue
396 # check the common case first
396 # check the common case first
397 if type == 'n':
397 if type == 'n':
398 if not st:
398 if not st:
399 st = os.stat(fn)
399 st = os.stat(fn)
400 if size != st.st_size or (mode ^ st.st_mode) & 0100:
400 if size != st.st_size or (mode ^ st.st_mode) & 0100:
401 modified.append(fn)
401 modified.append(fn)
402 elif time != st.st_mtime:
402 elif time != st.st_mtime:
403 lookup.append(fn)
403 lookup.append(fn)
404 elif type == 'm':
404 elif type == 'm':
405 modified.append(fn)
405 modified.append(fn)
406 elif type == 'a':
406 elif type == 'a':
407 added.append(fn)
407 added.append(fn)
408 elif type == 'r':
408 elif type == 'r':
409 removed.append(fn)
409 removed.append(fn)
410
410
411 return (lookup, modified, added, removed + deleted, unknown)
411 return (lookup, modified, added, removed + deleted, unknown)
General Comments 0
You need to be logged in to leave comments. Login now