##// END OF EJS Templates
check if a file is ignored before complaining if it is an unsupported type
Benoit Boissinot -
r1396:8c3e2a25 default
parent child Browse files
Show More
@@ -1,426 +1,425 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 87 ignore = self.hgignore()
88 88 if ignore:
89 89 files, self.ignorefunc, anypats = util.matcher(self.root,
90 90 inc=ignore)
91 91 else:
92 92 self.ignorefunc = util.never
93 93 return self.ignorefunc(fn)
94 94
95 95 def __del__(self):
96 96 if self.dirty:
97 97 self.write()
98 98
99 99 def __getitem__(self, key):
100 100 try:
101 101 return self.map[key]
102 102 except TypeError:
103 103 self.read()
104 104 return self[key]
105 105
106 106 def __contains__(self, key):
107 107 if not self.map: self.read()
108 108 return key in self.map
109 109
110 110 def parents(self):
111 111 if not self.pl:
112 112 self.read()
113 113 return self.pl
114 114
115 115 def markdirty(self):
116 116 if not self.dirty:
117 117 self.dirty = 1
118 118
119 119 def setparents(self, p1, p2=nullid):
120 120 if not self.pl:
121 121 self.read()
122 122 self.markdirty()
123 123 self.pl = p1, p2
124 124
125 125 def state(self, key):
126 126 try:
127 127 return self[key][0]
128 128 except KeyError:
129 129 return "?"
130 130
131 131 def read(self):
132 132 if self.map is not None: return self.map
133 133
134 134 self.map = {}
135 135 self.pl = [nullid, nullid]
136 136 try:
137 137 st = self.opener("dirstate").read()
138 138 if not st: return
139 139 except: return
140 140
141 141 self.pl = [st[:20], st[20: 40]]
142 142
143 143 pos = 40
144 144 while pos < len(st):
145 145 e = struct.unpack(">cllll", st[pos:pos+17])
146 146 l = e[4]
147 147 pos += 17
148 148 f = st[pos:pos + l]
149 149 if '\0' in f:
150 150 f, c = f.split('\0')
151 151 self.copies[f] = c
152 152 self.map[f] = e[:4]
153 153 pos += l
154 154
155 155 def copy(self, source, dest):
156 156 self.read()
157 157 self.markdirty()
158 158 self.copies[dest] = source
159 159
160 160 def copied(self, file):
161 161 return self.copies.get(file, None)
162 162
163 163 def update(self, files, state, **kw):
164 164 ''' current states:
165 165 n normal
166 166 m needs merging
167 167 r marked for removal
168 168 a marked for addition'''
169 169
170 170 if not files: return
171 171 self.read()
172 172 self.markdirty()
173 173 for f in files:
174 174 if state == "r":
175 175 self.map[f] = ('r', 0, 0, 0)
176 176 else:
177 177 s = os.lstat(os.path.join(self.root, f))
178 178 st_size = kw.get('st_size', s.st_size)
179 179 st_mtime = kw.get('st_mtime', s.st_mtime)
180 180 self.map[f] = (state, s.st_mode, st_size, st_mtime)
181 181 if self.copies.has_key(f):
182 182 del self.copies[f]
183 183
184 184 def forget(self, files):
185 185 if not files: return
186 186 self.read()
187 187 self.markdirty()
188 188 for f in files:
189 189 try:
190 190 del self.map[f]
191 191 except KeyError:
192 192 self.ui.warn("not in dirstate: %s!\n" % f)
193 193 pass
194 194
195 195 def clear(self):
196 196 self.map = {}
197 197 self.markdirty()
198 198
199 199 def write(self):
200 200 st = self.opener("dirstate", "w")
201 201 st.write("".join(self.pl))
202 202 for f, e in self.map.items():
203 203 c = self.copied(f)
204 204 if c:
205 205 f = f + "\0" + c
206 206 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
207 207 st.write(e + f)
208 208 self.dirty = 0
209 209
210 210 def filterfiles(self, files):
211 211 ret = {}
212 212 unknown = []
213 213
214 214 for x in files:
215 215 if x is '.':
216 216 return self.map.copy()
217 217 if x not in self.map:
218 218 unknown.append(x)
219 219 else:
220 220 ret[x] = self.map[x]
221 221
222 222 if not unknown:
223 223 return ret
224 224
225 225 b = self.map.keys()
226 226 b.sort()
227 227 blen = len(b)
228 228
229 229 for x in unknown:
230 230 bs = bisect.bisect(b, x)
231 231 if bs != 0 and b[bs-1] == x:
232 232 ret[x] = self.map[x]
233 233 continue
234 234 while bs < blen:
235 235 s = b[bs]
236 236 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
237 237 ret[s] = self.map[s]
238 238 else:
239 239 break
240 240 bs += 1
241 241 return ret
242 242
243 243 def walk(self, files=None, match=util.always, dc=None):
244 244 self.read()
245 245
246 246 # walk all files by default
247 247 if not files:
248 248 files = [self.root]
249 249 if not dc:
250 250 dc = self.map.copy()
251 251 elif not dc:
252 252 dc = self.filterfiles(files)
253 253
254 254 def statmatch(file, stat):
255 255 file = util.pconvert(file)
256 256 if file not in dc and self.ignore(file):
257 257 return False
258 258 return match(file)
259 259
260 260 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
261 261
262 262 # walk recursively through the directory tree, finding all files
263 263 # matched by the statmatch function
264 264 #
265 265 # results are yielded in a tuple (src, filename), where src is one of:
266 266 # 'f' the file was found in the directory tree
267 267 # 'm' the file was only in the dirstate and not in the tree
268 268 #
269 269 # dc is an optional arg for the current dirstate. dc is not modified
270 270 # directly by this function, but might be modified by your statmatch call.
271 271 #
272 272 def walkhelper(self, files, statmatch, dc):
273 273 def supported_type(f, st):
274 274 if stat.S_ISREG(st.st_mode):
275 275 return True
276 276 else:
277 277 kind = 'unknown'
278 278 if stat.S_ISCHR(st.st_mode): kind = 'character device'
279 279 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
280 280 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
281 281 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
282 282 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
283 283 elif stat.S_ISDIR(st.st_mode): kind = 'directory'
284 284 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
285 285 util.pathto(self.getcwd(), f),
286 286 kind))
287 287 return False
288 288
289 289 # recursion free walker, faster than os.walk.
290 290 def findfiles(s):
291 291 retfiles = []
292 292 work = [s]
293 293 while work:
294 294 top = work.pop()
295 295 names = os.listdir(top)
296 296 names.sort()
297 297 # nd is the top of the repository dir tree
298 298 nd = util.normpath(top[len(self.root) + 1:])
299 299 if nd == '.': nd = ''
300 300 for f in names:
301 301 np = os.path.join(nd, f)
302 302 if seen(np):
303 303 continue
304 304 p = os.path.join(top, f)
305 305 # don't trip over symlinks
306 306 st = os.lstat(p)
307 307 if stat.S_ISDIR(st.st_mode):
308 308 ds = os.path.join(nd, f +'/')
309 309 if statmatch(ds, st):
310 310 work.append(p)
311 elif supported_type(np, st):
312 if statmatch(np, st):
313 yield util.pconvert(np)
311 elif statmatch(np, st) and supported_type(np, st):
312 yield util.pconvert(np)
314 313
315 314
316 315 known = {'.hg': 1}
317 316 def seen(fn):
318 317 if fn in known: return True
319 318 known[fn] = 1
320 319
321 320 # step one, find all files that match our criteria
322 321 files.sort()
323 322 for ff in util.unique(files):
324 323 f = os.path.join(self.root, ff)
325 324 try:
326 325 st = os.lstat(f)
327 326 except OSError, inst:
328 327 if ff not in dc: self.ui.warn('%s: %s\n' % (
329 328 util.pathto(self.getcwd(), ff),
330 329 inst.strerror))
331 330 continue
332 331 if stat.S_ISDIR(st.st_mode):
333 332 sorted = [ x for x in findfiles(f) ]
334 333 sorted.sort()
335 334 for fl in sorted:
336 335 yield 'f', fl
337 336 else:
338 337 ff = util.normpath(ff)
339 338 if seen(ff):
340 339 continue
341 340 found = False
342 341 self.blockignore = True
343 if supported_type(ff, st) and statmatch(ff, st):
342 if statmatch(ff, st) and supported_type(ff, st):
344 343 found = True
345 344 self.blockignore = False
346 345 if found:
347 346 yield 'f', ff
348 347
349 348 # step two run through anything left in the dc hash and yield
350 349 # if we haven't already seen it
351 350 ks = dc.keys()
352 351 ks.sort()
353 352 for k in ks:
354 353 if not seen(k) and (statmatch(k, None)):
355 354 yield 'm', k
356 355
357 356 def changes(self, files=None, match=util.always):
358 357 self.read()
359 358 if not files:
360 359 files = [self.root]
361 360 dc = self.map.copy()
362 361 else:
363 362 dc = self.filterfiles(files)
364 363 lookup, modified, added, unknown = [], [], [], []
365 364 removed, deleted = [], []
366 365
367 366 # statmatch function to eliminate entries from the dirstate copy
368 367 # and put files into the appropriate array. This gets passed
369 368 # to the walking code
370 369 def statmatch(fn, s):
371 370 fn = util.pconvert(fn)
372 371 def checkappend(l, fn):
373 372 if match is util.always or match(fn):
374 373 l.append(fn)
375 374
376 375 if not s or stat.S_ISDIR(s.st_mode):
377 376 if self.ignore(fn): return False
378 377 return match(fn)
379 378
380 379 c = dc.pop(fn, None)
381 380 if c:
382 381 type, mode, size, time = c
383 382 # check the common case first
384 383 if type == 'n':
385 384 if size != s.st_size or (mode ^ s.st_mode) & 0100:
386 385 checkappend(modified, fn)
387 386 elif time != s.st_mtime:
388 387 checkappend(lookup, fn)
389 388 elif type == 'm':
390 389 checkappend(modified, fn)
391 390 elif type == 'a':
392 391 checkappend(added, fn)
393 392 elif type == 'r':
394 393 checkappend(unknown, fn)
395 394 elif not self.ignore(fn) and match(fn):
396 395 unknown.append(fn)
397 396 # return false because we've already handled all cases above.
398 397 # there's no need for the walking code to process the file
399 398 # any further.
400 399 return False
401 400
402 401 # because our statmatch always returns false, self.walk will only
403 402 # return files in the dirstate map that are not present in the FS.
404 403 # But, we still need to iterate through the results to force the
405 404 # walk to complete
406 405 for src, fn in self.walkhelper(files, statmatch, dc):
407 406 pass
408 407
409 408 # there may be patterns in the .hgignore file that prevent us
410 409 # from examining entire directories in the dirstate map, so we
411 410 # go back and explicitly examine any matching files we've
412 411 # ignored
413 412 unexamined = [fn for fn in dc.iterkeys()
414 413 if self.ignore(fn) and match(fn)]
415 414
416 415 for src, fn in self.walkhelper(unexamined, statmatch, dc):
417 416 pass
418 417
419 418 # anything left in dc didn't exist in the filesystem
420 419 for fn, c in dc.iteritems():
421 420 if not match(fn): continue
422 421 if c[0] == 'r':
423 422 removed.append(fn)
424 423 else:
425 424 deleted.append(fn)
426 425 return (lookup, modified, added, removed + deleted, unknown)
General Comments 0
You need to be logged in to leave comments. Login now