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