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