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