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