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