##// END OF EJS Templates
Yield directories in dirstate.statwalk()
Emanuele Aina -
r4146:e287d61d default
parent child Browse files
Show More
@@ -1,529 +1,534
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005, 2006 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 from node import *
11 11 from i18n import _
12 12 import struct, os, time, bisect, stat, strutil, util, re, errno
13 13
14 14 class dirstate(object):
15 15 format = ">cllll"
16 16
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.dirs = None
25 25 self.copymap = {}
26 26 self.ignorefunc = None
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 # self.root ends with a path separator if self.root is '/' or 'C:\'
35 35 common_prefix_len = len(self.root)
36 36 if not self.root.endswith(os.sep):
37 37 common_prefix_len += 1
38 38 return cwd[common_prefix_len:]
39 39
40 40 def hgignore(self):
41 41 '''return the contents of .hgignore files as a list of patterns.
42 42
43 43 the files parsed for patterns include:
44 44 .hgignore in the repository root
45 45 any additional files specified in the [ui] section of ~/.hgrc
46 46
47 47 trailing white space is dropped.
48 48 the escape character is backslash.
49 49 comments start with #.
50 50 empty lines are skipped.
51 51
52 52 lines can be of the following formats:
53 53
54 54 syntax: regexp # defaults following lines to non-rooted regexps
55 55 syntax: glob # defaults following lines to non-rooted globs
56 56 re:pattern # non-rooted regular expression
57 57 glob:pattern # non-rooted glob
58 58 pattern # pattern of the current default type'''
59 59 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
60 60 def parselines(fp):
61 61 for line in fp:
62 62 escape = False
63 63 for i in xrange(len(line)):
64 64 if escape: escape = False
65 65 elif line[i] == '\\': escape = True
66 66 elif line[i] == '#': break
67 67 line = line[:i].rstrip()
68 68 if line: yield line
69 69 repoignore = self.wjoin('.hgignore')
70 70 files = [repoignore]
71 71 files.extend(self.ui.hgignorefiles())
72 72 pats = {}
73 73 for f in files:
74 74 try:
75 75 pats[f] = []
76 76 fp = open(f)
77 77 syntax = 'relre:'
78 78 for line in parselines(fp):
79 79 if line.startswith('syntax:'):
80 80 s = line[7:].strip()
81 81 try:
82 82 syntax = syntaxes[s]
83 83 except KeyError:
84 84 self.ui.warn(_("%s: ignoring invalid "
85 85 "syntax '%s'\n") % (f, s))
86 86 continue
87 87 pat = syntax + line
88 88 for s in syntaxes.values():
89 89 if line.startswith(s):
90 90 pat = line
91 91 break
92 92 pats[f].append(pat)
93 93 except IOError, inst:
94 94 if f != repoignore:
95 95 self.ui.warn(_("skipping unreadable ignore file"
96 96 " '%s': %s\n") % (f, inst.strerror))
97 97 return pats
98 98
99 99 def ignore(self, fn):
100 100 '''default match function used by dirstate and
101 101 localrepository. this honours the repository .hgignore file
102 102 and any other files specified in the [ui] section of .hgrc.'''
103 103 if not self.ignorefunc:
104 104 ignore = self.hgignore()
105 105 allpats = []
106 106 [allpats.extend(patlist) for patlist in ignore.values()]
107 107 if allpats:
108 108 try:
109 109 files, self.ignorefunc, anypats = (
110 110 util.matcher(self.root, inc=allpats, src='.hgignore'))
111 111 except util.Abort:
112 112 # Re-raise an exception where the src is the right file
113 113 for f, patlist in ignore.items():
114 114 files, self.ignorefunc, anypats = (
115 115 util.matcher(self.root, inc=patlist, src=f))
116 116 else:
117 117 self.ignorefunc = util.never
118 118 return self.ignorefunc(fn)
119 119
120 120 def __del__(self):
121 121 if self.dirty:
122 122 self.write()
123 123
124 124 def __getitem__(self, key):
125 125 try:
126 126 return self.map[key]
127 127 except TypeError:
128 128 self.lazyread()
129 129 return self[key]
130 130
131 131 def __contains__(self, key):
132 132 self.lazyread()
133 133 return key in self.map
134 134
135 135 def parents(self):
136 136 self.lazyread()
137 137 return self.pl
138 138
139 139 def markdirty(self):
140 140 if not self.dirty:
141 141 self.dirty = 1
142 142
143 143 def setparents(self, p1, p2=nullid):
144 144 self.lazyread()
145 145 self.markdirty()
146 146 self.pl = p1, p2
147 147
148 148 def state(self, key):
149 149 try:
150 150 return self[key][0]
151 151 except KeyError:
152 152 return "?"
153 153
154 154 def lazyread(self):
155 155 if self.map is None:
156 156 self.read()
157 157
158 158 def parse(self, st):
159 159 self.pl = [st[:20], st[20: 40]]
160 160
161 161 # deref fields so they will be local in loop
162 162 map = self.map
163 163 copymap = self.copymap
164 164 format = self.format
165 165 unpack = struct.unpack
166 166
167 167 pos = 40
168 168 e_size = struct.calcsize(format)
169 169
170 170 while pos < len(st):
171 171 newpos = pos + e_size
172 172 e = unpack(format, st[pos:newpos])
173 173 l = e[4]
174 174 pos = newpos
175 175 newpos = pos + l
176 176 f = st[pos:newpos]
177 177 if '\0' in f:
178 178 f, c = f.split('\0')
179 179 copymap[f] = c
180 180 map[f] = e[:4]
181 181 pos = newpos
182 182
183 183 def read(self):
184 184 self.map = {}
185 185 self.pl = [nullid, nullid]
186 186 try:
187 187 st = self.opener("dirstate").read()
188 188 if st:
189 189 self.parse(st)
190 190 except IOError, err:
191 191 if err.errno != errno.ENOENT: raise
192 192
193 193 def copy(self, source, dest):
194 194 self.lazyread()
195 195 self.markdirty()
196 196 self.copymap[dest] = source
197 197
198 198 def copied(self, file):
199 199 return self.copymap.get(file, None)
200 200
201 201 def copies(self):
202 202 return self.copymap
203 203
204 204 def initdirs(self):
205 205 if self.dirs is None:
206 206 self.dirs = {}
207 207 for f in self.map:
208 208 self.updatedirs(f, 1)
209 209
210 210 def updatedirs(self, path, delta):
211 211 if self.dirs is not None:
212 212 for c in strutil.findall(path, '/'):
213 213 pc = path[:c]
214 214 self.dirs.setdefault(pc, 0)
215 215 self.dirs[pc] += delta
216 216
217 217 def checkinterfering(self, files):
218 218 def prefixes(f):
219 219 for c in strutil.rfindall(f, '/'):
220 220 yield f[:c]
221 221 self.lazyread()
222 222 self.initdirs()
223 223 seendirs = {}
224 224 for f in files:
225 225 # shadows
226 226 if self.dirs.get(f):
227 227 raise util.Abort(_('directory named %r already in dirstate') %
228 228 f)
229 229 for d in prefixes(f):
230 230 if d in seendirs:
231 231 break
232 232 if d in self.map:
233 233 raise util.Abort(_('file named %r already in dirstate') %
234 234 d)
235 235 seendirs[d] = True
236 236 # disallowed
237 237 if '\r' in f or '\n' in f:
238 238 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
239 239
240 240 def update(self, files, state, **kw):
241 241 ''' current states:
242 242 n normal
243 243 m needs merging
244 244 r marked for removal
245 245 a marked for addition'''
246 246
247 247 if not files: return
248 248 self.lazyread()
249 249 self.markdirty()
250 250 if state == "a":
251 251 self.initdirs()
252 252 self.checkinterfering(files)
253 253 for f in files:
254 254 if state == "r":
255 255 self.map[f] = ('r', 0, 0, 0)
256 256 self.updatedirs(f, -1)
257 257 else:
258 258 if state == "a":
259 259 self.updatedirs(f, 1)
260 260 s = os.lstat(self.wjoin(f))
261 261 st_size = kw.get('st_size', s.st_size)
262 262 st_mtime = kw.get('st_mtime', s.st_mtime)
263 263 self.map[f] = (state, s.st_mode, st_size, st_mtime)
264 264 if self.copymap.has_key(f):
265 265 del self.copymap[f]
266 266
267 267 def forget(self, files):
268 268 if not files: return
269 269 self.lazyread()
270 270 self.markdirty()
271 271 self.initdirs()
272 272 for f in files:
273 273 try:
274 274 del self.map[f]
275 275 self.updatedirs(f, -1)
276 276 except KeyError:
277 277 self.ui.warn(_("not in dirstate: %s!\n") % f)
278 278 pass
279 279
280 280 def clear(self):
281 281 self.map = {}
282 282 self.copymap = {}
283 283 self.dirs = None
284 284 self.markdirty()
285 285
286 286 def rebuild(self, parent, files):
287 287 self.clear()
288 288 for f in files:
289 289 if files.execf(f):
290 290 self.map[f] = ('n', 0777, -1, 0)
291 291 else:
292 292 self.map[f] = ('n', 0666, -1, 0)
293 293 self.pl = (parent, nullid)
294 294 self.markdirty()
295 295
296 296 def write(self):
297 297 if not self.dirty:
298 298 return
299 299 st = self.opener("dirstate", "w", atomic=True)
300 300 st.write("".join(self.pl))
301 301 for f, e in self.map.items():
302 302 c = self.copied(f)
303 303 if c:
304 304 f = f + "\0" + c
305 305 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
306 306 st.write(e + f)
307 307 self.dirty = 0
308 308
309 309 def filterfiles(self, files):
310 310 ret = {}
311 311 unknown = []
312 312
313 313 for x in files:
314 314 if x == '.':
315 315 return self.map.copy()
316 316 if x not in self.map:
317 317 unknown.append(x)
318 318 else:
319 319 ret[x] = self.map[x]
320 320
321 321 if not unknown:
322 322 return ret
323 323
324 324 b = self.map.keys()
325 325 b.sort()
326 326 blen = len(b)
327 327
328 328 for x in unknown:
329 329 bs = bisect.bisect(b, "%s%s" % (x, '/'))
330 330 while bs < blen:
331 331 s = b[bs]
332 332 if len(s) > len(x) and s.startswith(x):
333 333 ret[s] = self.map[s]
334 334 else:
335 335 break
336 336 bs += 1
337 337 return ret
338 338
339 339 def supported_type(self, f, st, verbose=False):
340 340 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
341 341 return True
342 342 if verbose:
343 343 kind = 'unknown'
344 344 if stat.S_ISCHR(st.st_mode): kind = _('character device')
345 345 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
346 346 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
347 347 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
348 348 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
349 349 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
350 350 util.pathto(self.getcwd(), f),
351 351 kind))
352 352 return False
353 353
354 354 def walk(self, files=None, match=util.always, badmatch=None):
355 355 # filter out the stat
356 356 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
357 357 yield src, f
358 358
359 359 def statwalk(self, files=None, match=util.always, ignored=False,
360 badmatch=None):
360 badmatch=None, directories=False):
361 361 '''
362 362 walk recursively through the directory tree, finding all files
363 363 matched by the match function
364 364
365 365 results are yielded in a tuple (src, filename, st), where src
366 366 is one of:
367 367 'f' the file was found in the directory tree
368 'd' the file is a directory of the tree
368 369 'm' the file was only in the dirstate and not in the tree
369 370 'b' file was not found and matched badmatch
370 371
371 372 and st is the stat result if the file was found in the directory.
372 373 '''
373 374 self.lazyread()
374 375
375 376 # walk all files by default
376 377 if not files:
377 378 files = [self.root]
378 379 dc = self.map.copy()
379 380 else:
380 381 files = util.unique(files)
381 382 dc = self.filterfiles(files)
382 383
383 384 def imatch(file_):
384 385 if file_ not in dc and self.ignore(file_):
385 386 return False
386 387 return match(file_)
387 388
388 389 if ignored: imatch = match
389 390
390 391 # self.root may end with a path separator when self.root == '/'
391 392 common_prefix_len = len(self.root)
392 393 if not self.root.endswith(os.sep):
393 394 common_prefix_len += 1
394 395 # recursion free walker, faster than os.walk.
395 396 def findfiles(s):
396 397 work = [s]
398 if directories:
399 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
397 400 while work:
398 401 top = work.pop()
399 402 names = os.listdir(top)
400 403 names.sort()
401 404 # nd is the top of the repository dir tree
402 405 nd = util.normpath(top[common_prefix_len:])
403 406 if nd == '.':
404 407 nd = ''
405 408 else:
406 409 # do not recurse into a repo contained in this
407 410 # one. use bisect to find .hg directory so speed
408 411 # is good on big directory.
409 412 hg = bisect.bisect_left(names, '.hg')
410 413 if hg < len(names) and names[hg] == '.hg':
411 414 if os.path.isdir(os.path.join(top, '.hg')):
412 415 continue
413 416 for f in names:
414 417 np = util.pconvert(os.path.join(nd, f))
415 418 if seen(np):
416 419 continue
417 420 p = os.path.join(top, f)
418 421 # don't trip over symlinks
419 422 st = os.lstat(p)
420 423 if stat.S_ISDIR(st.st_mode):
421 424 ds = util.pconvert(os.path.join(nd, f +'/'))
422 425 if imatch(ds):
423 426 work.append(p)
427 if directories:
428 yield 'd', np, st
424 429 if imatch(np) and np in dc:
425 430 yield 'm', np, st
426 431 elif imatch(np):
427 432 if self.supported_type(np, st):
428 433 yield 'f', np, st
429 434 elif np in dc:
430 435 yield 'm', np, st
431 436
432 437 known = {'.hg': 1}
433 438 def seen(fn):
434 439 if fn in known: return True
435 440 known[fn] = 1
436 441
437 442 # step one, find all files that match our criteria
438 443 files.sort()
439 444 for ff in files:
440 445 nf = util.normpath(ff)
441 446 f = self.wjoin(ff)
442 447 try:
443 448 st = os.lstat(f)
444 449 except OSError, inst:
445 450 found = False
446 451 for fn in dc:
447 452 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
448 453 found = True
449 454 break
450 455 if not found:
451 456 if inst.errno != errno.ENOENT or not badmatch:
452 457 self.ui.warn('%s: %s\n' % (
453 458 util.pathto(self.getcwd(), ff),
454 459 inst.strerror))
455 460 elif badmatch and badmatch(ff) and imatch(nf):
456 461 yield 'b', ff, None
457 462 continue
458 463 if stat.S_ISDIR(st.st_mode):
459 464 cmp1 = (lambda x, y: cmp(x[1], y[1]))
460 465 sorted_ = [ x for x in findfiles(f) ]
461 466 sorted_.sort(cmp1)
462 467 for e in sorted_:
463 468 yield e
464 469 else:
465 470 if not seen(nf) and match(nf):
466 471 if self.supported_type(ff, st, verbose=True):
467 472 yield 'f', nf, st
468 473 elif ff in dc:
469 474 yield 'm', nf, st
470 475
471 476 # step two run through anything left in the dc hash and yield
472 477 # if we haven't already seen it
473 478 ks = dc.keys()
474 479 ks.sort()
475 480 for k in ks:
476 481 if not seen(k) and imatch(k):
477 482 yield 'm', k, None
478 483
479 484 def status(self, files=None, match=util.always, list_ignored=False,
480 485 list_clean=False):
481 486 lookup, modified, added, unknown, ignored = [], [], [], [], []
482 487 removed, deleted, clean = [], [], []
483 488
484 489 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
485 490 try:
486 491 type_, mode, size, time = self[fn]
487 492 except KeyError:
488 493 if list_ignored and self.ignore(fn):
489 494 ignored.append(fn)
490 495 else:
491 496 unknown.append(fn)
492 497 continue
493 498 if src == 'm':
494 499 nonexistent = True
495 500 if not st:
496 501 try:
497 502 st = os.lstat(self.wjoin(fn))
498 503 except OSError, inst:
499 504 if inst.errno != errno.ENOENT:
500 505 raise
501 506 st = None
502 507 # We need to re-check that it is a valid file
503 508 if st and self.supported_type(fn, st):
504 509 nonexistent = False
505 510 # XXX: what to do with file no longer present in the fs
506 511 # who are not removed in the dirstate ?
507 512 if nonexistent and type_ in "nm":
508 513 deleted.append(fn)
509 514 continue
510 515 # check the common case first
511 516 if type_ == 'n':
512 517 if not st:
513 518 st = os.lstat(self.wjoin(fn))
514 519 if size >= 0 and (size != st.st_size
515 520 or (mode ^ st.st_mode) & 0100):
516 521 modified.append(fn)
517 522 elif time != int(st.st_mtime):
518 523 lookup.append(fn)
519 524 elif list_clean:
520 525 clean.append(fn)
521 526 elif type_ == 'm':
522 527 modified.append(fn)
523 528 elif type_ == 'a':
524 529 added.append(fn)
525 530 elif type_ == 'r':
526 531 removed.append(fn)
527 532
528 533 return (lookup, modified, added, removed, deleted, unknown, ignored,
529 534 clean)
General Comments 0
You need to be logged in to leave comments. Login now