##// END OF EJS Templates
dirstate: don't complain about 0-length files
Matt Mackall -
r8716:f3322bb2 default
parent child Browse files
Show More
@@ -1,602 +1,603 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import nullid
9 9 from i18n import _
10 10 import util, ignore, osutil, parsers
11 11 import struct, os, stat, errno
12 12 import cStringIO, sys
13 13
14 14 _unknown = ('?', 0, 0, 0)
15 15 _format = ">cllll"
16 16 propertycache = util.propertycache
17 17
18 18 def _finddirs(path):
19 19 pos = path.rfind('/')
20 20 while pos != -1:
21 21 yield path[:pos]
22 22 pos = path.rfind('/', 0, pos)
23 23
24 24 def _incdirs(dirs, path):
25 25 for base in _finddirs(path):
26 26 if base in dirs:
27 27 dirs[base] += 1
28 28 return
29 29 dirs[base] = 1
30 30
31 31 def _decdirs(dirs, path):
32 32 for base in _finddirs(path):
33 33 if dirs[base] > 1:
34 34 dirs[base] -= 1
35 35 return
36 36 del dirs[base]
37 37
38 38 class dirstate(object):
39 39
40 40 def __init__(self, opener, ui, root):
41 41 self._opener = opener
42 42 self._root = root
43 43 self._rootdir = os.path.join(root, '')
44 44 self._dirty = False
45 45 self._dirtypl = False
46 46 self._ui = ui
47 47
48 48 @propertycache
49 49 def _map(self):
50 50 self._read()
51 51 return self._map
52 52
53 53 @propertycache
54 54 def _copymap(self):
55 55 self._read()
56 56 return self._copymap
57 57
58 58 @propertycache
59 59 def _foldmap(self):
60 60 f = {}
61 61 for name in self._map:
62 62 f[os.path.normcase(name)] = name
63 63 return f
64 64
65 65 @propertycache
66 66 def _branch(self):
67 67 try:
68 68 return self._opener("branch").read().strip() or "default"
69 69 except IOError:
70 70 return "default"
71 71
72 72 @propertycache
73 73 def _pl(self):
74 74 try:
75 75 st = self._opener("dirstate").read(40)
76 if len(st) == 40:
76 l = len(st)
77 if l == 40:
77 78 return st[:20], st[20:40]
78 if len(st) < 40:
79 elif l > 0 and l < 40:
79 80 raise util.Abort(_('working directory state appears damaged!'))
80 81 except IOError, err:
81 82 if err.errno != errno.ENOENT: raise
82 83 return [nullid, nullid]
83 84
84 85 @propertycache
85 86 def _dirs(self):
86 87 dirs = {}
87 88 for f,s in self._map.iteritems():
88 89 if s[0] != 'r':
89 90 _incdirs(dirs, f)
90 91 return dirs
91 92
92 93 @propertycache
93 94 def _ignore(self):
94 95 files = [self._join('.hgignore')]
95 96 for name, path in self._ui.configitems("ui"):
96 97 if name == 'ignore' or name.startswith('ignore.'):
97 98 files.append(os.path.expanduser(path))
98 99 return ignore.ignore(self._root, files, self._ui.warn)
99 100
100 101 @propertycache
101 102 def _slash(self):
102 103 return self._ui.configbool('ui', 'slash') and os.sep != '/'
103 104
104 105 @propertycache
105 106 def _checklink(self):
106 107 return util.checklink(self._root)
107 108
108 109 @propertycache
109 110 def _checkexec(self):
110 111 return util.checkexec(self._root)
111 112
112 113 @propertycache
113 114 def _checkcase(self):
114 115 return not util.checkcase(self._join('.hg'))
115 116
116 117 @propertycache
117 118 def normalize(self):
118 119 if self._checkcase:
119 120 return self._normalize
120 121 return lambda x, y=False: x
121 122
122 123 def _join(self, f):
123 124 # much faster than os.path.join()
124 125 # it's safe because f is always a relative path
125 126 return self._rootdir + f
126 127
127 128 def flagfunc(self, fallback):
128 129 if self._checklink:
129 130 if self._checkexec:
130 131 def f(x):
131 132 p = self._join(x)
132 133 if os.path.islink(p):
133 134 return 'l'
134 135 if util.is_exec(p):
135 136 return 'x'
136 137 return ''
137 138 return f
138 139 def f(x):
139 140 if os.path.islink(self._join(x)):
140 141 return 'l'
141 142 if 'x' in fallback(x):
142 143 return 'x'
143 144 return ''
144 145 return f
145 146 if self._checkexec:
146 147 def f(x):
147 148 if 'l' in fallback(x):
148 149 return 'l'
149 150 if util.is_exec(self._join(x)):
150 151 return 'x'
151 152 return ''
152 153 return f
153 154 return fallback
154 155
155 156 def getcwd(self):
156 157 cwd = os.getcwd()
157 158 if cwd == self._root: return ''
158 159 # self._root ends with a path separator if self._root is '/' or 'C:\'
159 160 rootsep = self._root
160 161 if not util.endswithsep(rootsep):
161 162 rootsep += os.sep
162 163 if cwd.startswith(rootsep):
163 164 return cwd[len(rootsep):]
164 165 else:
165 166 # we're outside the repo. return an absolute path.
166 167 return cwd
167 168
168 169 def pathto(self, f, cwd=None):
169 170 if cwd is None:
170 171 cwd = self.getcwd()
171 172 path = util.pathto(self._root, cwd, f)
172 173 if self._slash:
173 174 return util.normpath(path)
174 175 return path
175 176
176 177 def __getitem__(self, key):
177 178 ''' current states:
178 179 n normal
179 180 m needs merging
180 181 r marked for removal
181 182 a marked for addition
182 183 ? not tracked'''
183 184 return self._map.get(key, ("?",))[0]
184 185
185 186 def __contains__(self, key):
186 187 return key in self._map
187 188
188 189 def __iter__(self):
189 190 for x in sorted(self._map):
190 191 yield x
191 192
192 193 def parents(self):
193 194 return self._pl
194 195
195 196 def branch(self):
196 197 return self._branch
197 198
198 199 def setparents(self, p1, p2=nullid):
199 200 self._dirty = self._dirtypl = True
200 201 self._pl = p1, p2
201 202
202 203 def setbranch(self, branch):
203 204 self._branch = branch
204 205 self._opener("branch", "w").write(branch + '\n')
205 206
206 207 def _read(self):
207 208 self._map = {}
208 209 self._copymap = {}
209 210 try:
210 211 st = self._opener("dirstate").read()
211 212 except IOError, err:
212 213 if err.errno != errno.ENOENT: raise
213 214 return
214 215 if not st:
215 216 return
216 217
217 218 p = parsers.parse_dirstate(self._map, self._copymap, st)
218 219 if not self._dirtypl:
219 220 self._pl = p
220 221
221 222 def invalidate(self):
222 223 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
223 224 if a in self.__dict__:
224 225 delattr(self, a)
225 226 self._dirty = False
226 227
227 228 def copy(self, source, dest):
228 229 """Mark dest as a copy of source. Unmark dest if source is None.
229 230 """
230 231 if source == dest:
231 232 return
232 233 self._dirty = True
233 234 if source is not None:
234 235 self._copymap[dest] = source
235 236 elif dest in self._copymap:
236 237 del self._copymap[dest]
237 238
238 239 def copied(self, file):
239 240 return self._copymap.get(file, None)
240 241
241 242 def copies(self):
242 243 return self._copymap
243 244
244 245 def _droppath(self, f):
245 246 if self[f] not in "?r" and "_dirs" in self.__dict__:
246 247 _decdirs(self._dirs, f)
247 248
248 249 def _addpath(self, f, check=False):
249 250 oldstate = self[f]
250 251 if check or oldstate == "r":
251 252 if '\r' in f or '\n' in f:
252 253 raise util.Abort(
253 254 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
254 255 if f in self._dirs:
255 256 raise util.Abort(_('directory %r already in dirstate') % f)
256 257 # shadows
257 258 for d in _finddirs(f):
258 259 if d in self._dirs:
259 260 break
260 261 if d in self._map and self[d] != 'r':
261 262 raise util.Abort(
262 263 _('file %r in dirstate clashes with %r') % (d, f))
263 264 if oldstate in "?r" and "_dirs" in self.__dict__:
264 265 _incdirs(self._dirs, f)
265 266
266 267 def normal(self, f):
267 268 'mark a file normal and clean'
268 269 self._dirty = True
269 270 self._addpath(f)
270 271 s = os.lstat(self._join(f))
271 272 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
272 273 if f in self._copymap:
273 274 del self._copymap[f]
274 275
275 276 def normallookup(self, f):
276 277 'mark a file normal, but possibly dirty'
277 278 if self._pl[1] != nullid and f in self._map:
278 279 # if there is a merge going on and the file was either
279 280 # in state 'm' or dirty before being removed, restore that state.
280 281 entry = self._map[f]
281 282 if entry[0] == 'r' and entry[2] in (-1, -2):
282 283 source = self._copymap.get(f)
283 284 if entry[2] == -1:
284 285 self.merge(f)
285 286 elif entry[2] == -2:
286 287 self.normaldirty(f)
287 288 if source:
288 289 self.copy(source, f)
289 290 return
290 291 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
291 292 return
292 293 self._dirty = True
293 294 self._addpath(f)
294 295 self._map[f] = ('n', 0, -1, -1)
295 296 if f in self._copymap:
296 297 del self._copymap[f]
297 298
298 299 def normaldirty(self, f):
299 300 'mark a file normal, but dirty'
300 301 self._dirty = True
301 302 self._addpath(f)
302 303 self._map[f] = ('n', 0, -2, -1)
303 304 if f in self._copymap:
304 305 del self._copymap[f]
305 306
306 307 def add(self, f):
307 308 'mark a file added'
308 309 self._dirty = True
309 310 self._addpath(f, True)
310 311 self._map[f] = ('a', 0, -1, -1)
311 312 if f in self._copymap:
312 313 del self._copymap[f]
313 314
314 315 def remove(self, f):
315 316 'mark a file removed'
316 317 self._dirty = True
317 318 self._droppath(f)
318 319 size = 0
319 320 if self._pl[1] != nullid and f in self._map:
320 321 entry = self._map[f]
321 322 if entry[0] == 'm':
322 323 size = -1
323 324 elif entry[0] == 'n' and entry[2] == -2:
324 325 size = -2
325 326 self._map[f] = ('r', 0, size, 0)
326 327 if size == 0 and f in self._copymap:
327 328 del self._copymap[f]
328 329
329 330 def merge(self, f):
330 331 'mark a file merged'
331 332 self._dirty = True
332 333 s = os.lstat(self._join(f))
333 334 self._addpath(f)
334 335 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
335 336 if f in self._copymap:
336 337 del self._copymap[f]
337 338
338 339 def forget(self, f):
339 340 'forget a file'
340 341 self._dirty = True
341 342 try:
342 343 self._droppath(f)
343 344 del self._map[f]
344 345 except KeyError:
345 346 self._ui.warn(_("not in dirstate: %s\n") % f)
346 347
347 348 def _normalize(self, path, knownpath=False):
348 349 norm_path = os.path.normcase(path)
349 350 fold_path = self._foldmap.get(norm_path, None)
350 351 if fold_path is None:
351 352 if knownpath or not os.path.exists(os.path.join(self._root, path)):
352 353 fold_path = path
353 354 else:
354 355 fold_path = self._foldmap.setdefault(norm_path,
355 356 util.fspath(path, self._root))
356 357 return fold_path
357 358
358 359 def clear(self):
359 360 self._map = {}
360 361 if "_dirs" in self.__dict__:
361 362 delattr(self, "_dirs");
362 363 self._copymap = {}
363 364 self._pl = [nullid, nullid]
364 365 self._dirty = True
365 366
366 367 def rebuild(self, parent, files):
367 368 self.clear()
368 369 for f in files:
369 370 if 'x' in files.flags(f):
370 371 self._map[f] = ('n', 0777, -1, 0)
371 372 else:
372 373 self._map[f] = ('n', 0666, -1, 0)
373 374 self._pl = (parent, nullid)
374 375 self._dirty = True
375 376
376 377 def write(self):
377 378 if not self._dirty:
378 379 return
379 380 st = self._opener("dirstate", "w", atomictemp=True)
380 381
381 382 try:
382 383 gran = int(self._ui.config('dirstate', 'granularity', 1))
383 384 except ValueError:
384 385 gran = 1
385 386 limit = sys.maxint
386 387 if gran > 0:
387 388 limit = util.fstat(st).st_mtime - gran
388 389
389 390 cs = cStringIO.StringIO()
390 391 copymap = self._copymap
391 392 pack = struct.pack
392 393 write = cs.write
393 394 write("".join(self._pl))
394 395 for f, e in self._map.iteritems():
395 396 if f in copymap:
396 397 f = "%s\0%s" % (f, copymap[f])
397 398 if e[3] > limit and e[0] == 'n':
398 399 e = (e[0], 0, -1, -1)
399 400 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
400 401 write(e)
401 402 write(f)
402 403 st.write(cs.getvalue())
403 404 st.rename()
404 405 self._dirty = self._dirtypl = False
405 406
406 407 def _dirignore(self, f):
407 408 if f == '.':
408 409 return False
409 410 if self._ignore(f):
410 411 return True
411 412 for p in _finddirs(f):
412 413 if self._ignore(p):
413 414 return True
414 415 return False
415 416
416 417 def walk(self, match, unknown, ignored):
417 418 '''
418 419 walk recursively through the directory tree, finding all files
419 420 matched by the match function
420 421
421 422 results are yielded in a tuple (filename, stat), where stat
422 423 and st is the stat result if the file was found in the directory.
423 424 '''
424 425
425 426 def fwarn(f, msg):
426 427 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
427 428 return False
428 429
429 430 def badtype(mode):
430 431 kind = _('unknown')
431 432 if stat.S_ISCHR(mode): kind = _('character device')
432 433 elif stat.S_ISBLK(mode): kind = _('block device')
433 434 elif stat.S_ISFIFO(mode): kind = _('fifo')
434 435 elif stat.S_ISSOCK(mode): kind = _('socket')
435 436 elif stat.S_ISDIR(mode): kind = _('directory')
436 437 return _('unsupported file type (type is %s)') % kind
437 438
438 439 ignore = self._ignore
439 440 dirignore = self._dirignore
440 441 if ignored:
441 442 ignore = util.never
442 443 dirignore = util.never
443 444 elif not unknown:
444 445 # if unknown and ignored are False, skip step 2
445 446 ignore = util.always
446 447 dirignore = util.always
447 448
448 449 matchfn = match.matchfn
449 450 badfn = match.bad
450 451 dmap = self._map
451 452 normpath = util.normpath
452 453 normalize = self.normalize
453 454 listdir = osutil.listdir
454 455 lstat = os.lstat
455 456 getkind = stat.S_IFMT
456 457 dirkind = stat.S_IFDIR
457 458 regkind = stat.S_IFREG
458 459 lnkkind = stat.S_IFLNK
459 460 join = self._join
460 461 work = []
461 462 wadd = work.append
462 463
463 464 exact = skipstep3 = False
464 465 if matchfn == match.exact: # match.exact
465 466 exact = True
466 467 dirignore = util.always # skip step 2
467 468 elif match.files() and not match.anypats(): # match.match, no patterns
468 469 skipstep3 = True
469 470
470 471 files = set(match.files())
471 472 if not files or '.' in files:
472 473 files = ['']
473 474 results = {'.hg': None}
474 475
475 476 # step 1: find all explicit files
476 477 for ff in sorted(files):
477 478 nf = normalize(normpath(ff))
478 479 if nf in results:
479 480 continue
480 481
481 482 try:
482 483 st = lstat(join(nf))
483 484 kind = getkind(st.st_mode)
484 485 if kind == dirkind:
485 486 skipstep3 = False
486 487 if nf in dmap:
487 488 #file deleted on disk but still in dirstate
488 489 results[nf] = None
489 490 match.dir(nf)
490 491 if not dirignore(nf):
491 492 wadd(nf)
492 493 elif kind == regkind or kind == lnkkind:
493 494 results[nf] = st
494 495 else:
495 496 badfn(ff, badtype(kind))
496 497 if nf in dmap:
497 498 results[nf] = None
498 499 except OSError, inst:
499 500 if nf in dmap: # does it exactly match a file?
500 501 results[nf] = None
501 502 else: # does it match a directory?
502 503 prefix = nf + "/"
503 504 for fn in dmap:
504 505 if fn.startswith(prefix):
505 506 match.dir(nf)
506 507 skipstep3 = False
507 508 break
508 509 else:
509 510 badfn(ff, inst.strerror)
510 511
511 512 # step 2: visit subdirectories
512 513 while work:
513 514 nd = work.pop()
514 515 skip = None
515 516 if nd == '.':
516 517 nd = ''
517 518 else:
518 519 skip = '.hg'
519 520 try:
520 521 entries = listdir(join(nd), stat=True, skip=skip)
521 522 except OSError, inst:
522 523 if inst.errno == errno.EACCES:
523 524 fwarn(nd, inst.strerror)
524 525 continue
525 526 raise
526 527 for f, kind, st in entries:
527 528 nf = normalize(nd and (nd + "/" + f) or f, True)
528 529 if nf not in results:
529 530 if kind == dirkind:
530 531 if not ignore(nf):
531 532 match.dir(nf)
532 533 wadd(nf)
533 534 if nf in dmap and matchfn(nf):
534 535 results[nf] = None
535 536 elif kind == regkind or kind == lnkkind:
536 537 if nf in dmap:
537 538 if matchfn(nf):
538 539 results[nf] = st
539 540 elif matchfn(nf) and not ignore(nf):
540 541 results[nf] = st
541 542 elif nf in dmap and matchfn(nf):
542 543 results[nf] = None
543 544
544 545 # step 3: report unseen items in the dmap hash
545 546 if not skipstep3 and not exact:
546 547 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
547 548 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
548 549 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
549 550 st = None
550 551 results[nf] = st
551 552
552 553 del results['.hg']
553 554 return results
554 555
555 556 def status(self, match, ignored, clean, unknown):
556 557 listignored, listclean, listunknown = ignored, clean, unknown
557 558 lookup, modified, added, unknown, ignored = [], [], [], [], []
558 559 removed, deleted, clean = [], [], []
559 560
560 561 dmap = self._map
561 562 ladd = lookup.append
562 563 madd = modified.append
563 564 aadd = added.append
564 565 uadd = unknown.append
565 566 iadd = ignored.append
566 567 radd = removed.append
567 568 dadd = deleted.append
568 569 cadd = clean.append
569 570
570 571 for fn, st in self.walk(match, listunknown, listignored).iteritems():
571 572 if fn not in dmap:
572 573 if (listignored or match.exact(fn)) and self._dirignore(fn):
573 574 if listignored:
574 575 iadd(fn)
575 576 elif listunknown:
576 577 uadd(fn)
577 578 continue
578 579
579 580 state, mode, size, time = dmap[fn]
580 581
581 582 if not st and state in "nma":
582 583 dadd(fn)
583 584 elif state == 'n':
584 585 if (size >= 0 and
585 586 (size != st.st_size
586 587 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
587 588 or size == -2
588 589 or fn in self._copymap):
589 590 madd(fn)
590 591 elif time != int(st.st_mtime):
591 592 ladd(fn)
592 593 elif listclean:
593 594 cadd(fn)
594 595 elif state == 'm':
595 596 madd(fn)
596 597 elif state == 'a':
597 598 aadd(fn)
598 599 elif state == 'r':
599 600 radd(fn)
600 601
601 602 return (lookup, modified, added, removed, deleted, unknown, ignored,
602 603 clean)
General Comments 0
You need to be logged in to leave comments. Login now