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