##// END OF EJS Templates
dirstate: fix rebuild; add a test...
Alexis S. L. Carvalho -
r5065:b304c249 default
parent child Browse files
Show More
@@ -0,0 +1,24 b''
1 #!/bin/sh
2 # basic test for hg debugrebuildstate
3
4 hg init repo
5 cd repo
6
7 touch foo bar
8 hg ci -Am 'add foo bar'
9
10 touch baz
11 hg add baz
12 hg rm bar
13
14 echo '% state dump'
15 hg debugstate | cut -b 1-16,35- | sort
16 echo '% status'
17 hg st -A
18
19 hg debugrebuildstate
20 echo '% state dump'
21 hg debugstate | cut -b 1-16,35- | sort
22 echo '% status'
23 hg st -A
24
@@ -0,0 +1,17 b''
1 adding bar
2 adding foo
3 % state dump
4 a 644 0 baz
5 n 644 0 foo
6 r 0 0 bar
7 % status
8 A baz
9 R bar
10 C foo
11 % state dump
12 n 666 -1 bar
13 n 666 -1 foo
14 % status
15 ! bar
16 ? baz
17 C foo
@@ -1,496 +1,502 b''
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005-2007 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, ignore
13 13 import cStringIO
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 18 class dirstate(object):
19 19
20 20 def __init__(self, opener, ui, root):
21 21 self._opener = opener
22 22 self._root = root
23 23 self._dirty = 0
24 24 self._dirtypl = 0
25 25 self._ui = ui
26 26
27 27 def __getattr__(self, name):
28 28 if name == '_map':
29 29 self._read()
30 30 return self._map
31 31 elif name == '_copymap':
32 32 self._read()
33 33 return self._copymap
34 34 elif name == '_branch':
35 35 try:
36 36 self._branch = (self._opener("branch").read().strip()
37 37 or "default")
38 38 except IOError:
39 39 self._branch = "default"
40 40 return self._branch
41 41 elif name == '_pl':
42 42 self._pl = [nullid, nullid]
43 43 try:
44 44 st = self._opener("dirstate").read(40)
45 45 if len(st) == 40:
46 46 self._pl = st[:20], st[20:40]
47 47 except IOError, err:
48 48 if err.errno != errno.ENOENT: raise
49 49 return self._pl
50 50 elif name == '_dirs':
51 51 self._dirs = {}
52 52 for f in self._map:
53 53 self._incpath(f)
54 54 return self._dirs
55 55 elif name == '_ignore':
56 56 files = [self.wjoin('.hgignore')]
57 57 for name, path in self._ui.configitems("ui"):
58 58 if name == 'ignore' or name.startswith('ignore.'):
59 59 files.append(os.path.expanduser(path))
60 60 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
61 61 return self._ignore
62 62 elif name == '_slash':
63 63 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
64 64 return self._slash
65 65 else:
66 66 raise AttributeError, name
67 67
68 68 def wjoin(self, f):
69 69 return os.path.join(self._root, f)
70 70
71 71 def getcwd(self):
72 72 cwd = os.getcwd()
73 73 if cwd == self._root: return ''
74 74 # self._root ends with a path separator if self._root is '/' or 'C:\'
75 75 rootsep = self._root
76 76 if not rootsep.endswith(os.sep):
77 77 rootsep += os.sep
78 78 if cwd.startswith(rootsep):
79 79 return cwd[len(rootsep):]
80 80 else:
81 81 # we're outside the repo. return an absolute path.
82 82 return cwd
83 83
84 84 def pathto(self, f, cwd=None):
85 85 if cwd is None:
86 86 cwd = self.getcwd()
87 87 path = util.pathto(self._root, cwd, f)
88 88 if self._slash:
89 89 return path.replace(os.sep, '/')
90 90 return path
91 91
92 92 def __del__(self):
93 93 self.write()
94 94
95 95 def __getitem__(self, key):
96 96 return self._map[key]
97 97
98 98 def __contains__(self, key):
99 99 return key in self._map
100 100
101 101 def __iter__(self):
102 102 a = self._map.keys()
103 103 a.sort()
104 104 for x in a:
105 105 yield x
106 106
107 107 def parents(self):
108 108 return self._pl
109 109
110 110 def branch(self):
111 111 return self._branch
112 112
113 113 def markdirty(self):
114 114 self._dirty = 1
115 115
116 116 def setparents(self, p1, p2=nullid):
117 117 self.markdirty()
118 118 self._dirtypl = 1
119 119 self._pl = p1, p2
120 120
121 121 def setbranch(self, branch):
122 122 self._branch = branch
123 123 self._opener("branch", "w").write(branch + '\n')
124 124
125 125 def state(self, key):
126 126 return self._map.get(key, ("?",))[0]
127 127
128 128 def _read(self):
129 129 self._map = {}
130 130 self._copymap = {}
131 131 if not self._dirtypl:
132 132 self._pl = [nullid, nullid]
133 133 try:
134 134 st = self._opener("dirstate").read()
135 135 except IOError, err:
136 136 if err.errno != errno.ENOENT: raise
137 137 return
138 138 if not st:
139 139 return
140 140
141 141 if not self._dirtypl:
142 142 self._pl = [st[:20], st[20: 40]]
143 143
144 144 # deref fields so they will be local in loop
145 145 dmap = self._map
146 146 copymap = self._copymap
147 147 unpack = struct.unpack
148 148
149 149 pos = 40
150 150 e_size = struct.calcsize(_format)
151 151
152 152 while pos < len(st):
153 153 newpos = pos + e_size
154 154 e = unpack(_format, st[pos:newpos])
155 155 l = e[4]
156 156 pos = newpos
157 157 newpos = pos + l
158 158 f = st[pos:newpos]
159 159 if '\0' in f:
160 160 f, c = f.split('\0')
161 161 copymap[f] = c
162 162 dmap[f] = e[:4]
163 163 pos = newpos
164 164
165 165 def invalidate(self):
166 166 for a in "_map _copymap _branch _pl _dirs _ignore".split():
167 167 if a in self.__dict__:
168 168 delattr(self, a)
169 169 self._dirty = 0
170 170
171 171 def copy(self, source, dest):
172 172 self.markdirty()
173 173 self._copymap[dest] = source
174 174
175 175 def copied(self, file):
176 176 return self._copymap.get(file, None)
177 177
178 178 def copies(self):
179 179 return self._copymap
180 180
181 181 def _incpath(self, path):
182 182 for c in strutil.findall(path, '/'):
183 183 pc = path[:c]
184 184 self._dirs.setdefault(pc, 0)
185 185 self._dirs[pc] += 1
186 186
187 187 def _decpath(self, path):
188 188 for c in strutil.findall(path, '/'):
189 189 pc = path[:c]
190 190 self._dirs.setdefault(pc, 0)
191 191 self._dirs[pc] -= 1
192 192
193 193 def _incpathcheck(self, f):
194 194 if '\r' in f or '\n' in f:
195 195 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
196 196 # shadows
197 197 if f in self._dirs:
198 198 raise util.Abort(_('directory named %r already in dirstate') % f)
199 199 for c in strutil.rfindall(f, '/'):
200 200 d = f[:c]
201 201 if d in self._dirs:
202 202 break
203 203 if d in self._map:
204 204 raise util.Abort(_('file named %r already in dirstate') % d)
205 205 self._incpath(f)
206 206
207 207 def update(self, files, state, **kw):
208 208 ''' current states:
209 209 n normal
210 210 m needs merging
211 211 r marked for removal
212 212 a marked for addition'''
213 213
214 214 if not files: return
215 215 self.markdirty()
216 216 for f in files:
217 217 if self._copymap.has_key(f):
218 218 del self._copymap[f]
219 219
220 220 if state == "r":
221 221 self._map[f] = ('r', 0, 0, 0)
222 222 self._decpath(f)
223 223 continue
224 224 else:
225 225 if state == "a":
226 226 self._incpathcheck(f)
227 227 s = os.lstat(self.wjoin(f))
228 228 st_size = kw.get('st_size', s.st_size)
229 229 st_mtime = kw.get('st_mtime', s.st_mtime)
230 230 self._map[f] = (state, s.st_mode, st_size, st_mtime)
231 231
232 232 def forget(self, files):
233 233 if not files: return
234 234 self.markdirty()
235 235 for f in files:
236 236 try:
237 237 del self._map[f]
238 238 self._decpath(f)
239 239 except KeyError:
240 240 self._ui.warn(_("not in dirstate: %s!\n") % f)
241 241 pass
242 242
243 def clear(self):
244 self._map = {}
245 self._copymap = {}
246 self._pl = [nullid, nullid]
247 self.markdirty()
248
243 249 def rebuild(self, parent, files):
244 self.invalidate()
250 self.clear()
245 251 for f in files:
246 252 if files.execf(f):
247 253 self._map[f] = ('n', 0777, -1, 0)
248 254 else:
249 255 self._map[f] = ('n', 0666, -1, 0)
250 256 self._pl = (parent, nullid)
251 257 self.markdirty()
252 258
253 259 def write(self):
254 260 if not self._dirty:
255 261 return
256 262 cs = cStringIO.StringIO()
257 263 cs.write("".join(self._pl))
258 264 for f, e in self._map.iteritems():
259 265 c = self.copied(f)
260 266 if c:
261 267 f = f + "\0" + c
262 268 e = struct.pack(_format, e[0], e[1], e[2], e[3], len(f))
263 269 cs.write(e)
264 270 cs.write(f)
265 271 st = self._opener("dirstate", "w", atomictemp=True)
266 272 st.write(cs.getvalue())
267 273 st.rename()
268 274 self._dirty = 0
269 275 self._dirtypl = 0
270 276
271 277 def filterfiles(self, files):
272 278 ret = {}
273 279 unknown = []
274 280
275 281 for x in files:
276 282 if x == '.':
277 283 return self._map.copy()
278 284 if x not in self._map:
279 285 unknown.append(x)
280 286 else:
281 287 ret[x] = self._map[x]
282 288
283 289 if not unknown:
284 290 return ret
285 291
286 292 b = self._map.keys()
287 293 b.sort()
288 294 blen = len(b)
289 295
290 296 for x in unknown:
291 297 bs = bisect.bisect(b, "%s%s" % (x, '/'))
292 298 while bs < blen:
293 299 s = b[bs]
294 300 if len(s) > len(x) and s.startswith(x):
295 301 ret[s] = self._map[s]
296 302 else:
297 303 break
298 304 bs += 1
299 305 return ret
300 306
301 307 def _supported(self, f, st, verbose=False):
302 308 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
303 309 return True
304 310 if verbose:
305 311 kind = 'unknown'
306 312 if stat.S_ISCHR(st.st_mode): kind = _('character device')
307 313 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
308 314 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
309 315 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
310 316 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
311 317 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
312 318 % (self.pathto(f), kind))
313 319 return False
314 320
315 321 def walk(self, files=None, match=util.always, badmatch=None):
316 322 # filter out the stat
317 323 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
318 324 yield src, f
319 325
320 326 def statwalk(self, files=None, match=util.always, ignored=False,
321 327 badmatch=None, directories=False):
322 328 '''
323 329 walk recursively through the directory tree, finding all files
324 330 matched by the match function
325 331
326 332 results are yielded in a tuple (src, filename, st), where src
327 333 is one of:
328 334 'f' the file was found in the directory tree
329 335 'd' the file is a directory of the tree
330 336 'm' the file was only in the dirstate and not in the tree
331 337 'b' file was not found and matched badmatch
332 338
333 339 and st is the stat result if the file was found in the directory.
334 340 '''
335 341
336 342 # walk all files by default
337 343 if not files:
338 344 files = ['.']
339 345 dc = self._map.copy()
340 346 else:
341 347 files = util.unique(files)
342 348 dc = self.filterfiles(files)
343 349
344 350 def imatch(file_):
345 351 if file_ not in dc and self._ignore(file_):
346 352 return False
347 353 return match(file_)
348 354
349 355 ignore = self._ignore
350 356 if ignored:
351 357 imatch = match
352 358 ignore = util.never
353 359
354 360 # self._root may end with a path separator when self._root == '/'
355 361 common_prefix_len = len(self._root)
356 362 if not self._root.endswith(os.sep):
357 363 common_prefix_len += 1
358 364 # recursion free walker, faster than os.walk.
359 365 def findfiles(s):
360 366 work = [s]
361 367 if directories:
362 368 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
363 369 while work:
364 370 top = work.pop()
365 371 names = os.listdir(top)
366 372 names.sort()
367 373 # nd is the top of the repository dir tree
368 374 nd = util.normpath(top[common_prefix_len:])
369 375 if nd == '.':
370 376 nd = ''
371 377 else:
372 378 # do not recurse into a repo contained in this
373 379 # one. use bisect to find .hg directory so speed
374 380 # is good on big directory.
375 381 hg = bisect.bisect_left(names, '.hg')
376 382 if hg < len(names) and names[hg] == '.hg':
377 383 if os.path.isdir(os.path.join(top, '.hg')):
378 384 continue
379 385 for f in names:
380 386 np = util.pconvert(os.path.join(nd, f))
381 387 if seen(np):
382 388 continue
383 389 p = os.path.join(top, f)
384 390 # don't trip over symlinks
385 391 st = os.lstat(p)
386 392 if stat.S_ISDIR(st.st_mode):
387 393 if not ignore(np):
388 394 work.append(p)
389 395 if directories:
390 396 yield 'd', np, st
391 397 if imatch(np) and np in dc:
392 398 yield 'm', np, st
393 399 elif imatch(np):
394 400 if self._supported(np, st):
395 401 yield 'f', np, st
396 402 elif np in dc:
397 403 yield 'm', np, st
398 404
399 405 known = {'.hg': 1}
400 406 def seen(fn):
401 407 if fn in known: return True
402 408 known[fn] = 1
403 409
404 410 # step one, find all files that match our criteria
405 411 files.sort()
406 412 for ff in files:
407 413 nf = util.normpath(ff)
408 414 f = self.wjoin(ff)
409 415 try:
410 416 st = os.lstat(f)
411 417 except OSError, inst:
412 418 found = False
413 419 for fn in dc:
414 420 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
415 421 found = True
416 422 break
417 423 if not found:
418 424 if inst.errno != errno.ENOENT or not badmatch:
419 425 self._ui.warn('%s: %s\n' %
420 426 (self.pathto(ff), inst.strerror))
421 427 elif badmatch and badmatch(ff) and imatch(nf):
422 428 yield 'b', ff, None
423 429 continue
424 430 if stat.S_ISDIR(st.st_mode):
425 431 cmp1 = (lambda x, y: cmp(x[1], y[1]))
426 432 sorted_ = [ x for x in findfiles(f) ]
427 433 sorted_.sort(cmp1)
428 434 for e in sorted_:
429 435 yield e
430 436 else:
431 437 if not seen(nf) and match(nf):
432 438 if self._supported(ff, st, verbose=True):
433 439 yield 'f', nf, st
434 440 elif ff in dc:
435 441 yield 'm', nf, st
436 442
437 443 # step two run through anything left in the dc hash and yield
438 444 # if we haven't already seen it
439 445 ks = dc.keys()
440 446 ks.sort()
441 447 for k in ks:
442 448 if not seen(k) and imatch(k):
443 449 yield 'm', k, None
444 450
445 451 def status(self, files=None, match=util.always, list_ignored=False,
446 452 list_clean=False):
447 453 lookup, modified, added, unknown, ignored = [], [], [], [], []
448 454 removed, deleted, clean = [], [], []
449 455
450 456 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
451 457 try:
452 458 type_, mode, size, time = self[fn]
453 459 except KeyError:
454 460 if list_ignored and self._ignore(fn):
455 461 ignored.append(fn)
456 462 else:
457 463 unknown.append(fn)
458 464 continue
459 465 if src == 'm':
460 466 nonexistent = True
461 467 if not st:
462 468 try:
463 469 st = os.lstat(self.wjoin(fn))
464 470 except OSError, inst:
465 471 if inst.errno != errno.ENOENT:
466 472 raise
467 473 st = None
468 474 # We need to re-check that it is a valid file
469 475 if st and self._supported(fn, st):
470 476 nonexistent = False
471 477 # XXX: what to do with file no longer present in the fs
472 478 # who are not removed in the dirstate ?
473 479 if nonexistent and type_ in "nm":
474 480 deleted.append(fn)
475 481 continue
476 482 # check the common case first
477 483 if type_ == 'n':
478 484 if not st:
479 485 st = os.lstat(self.wjoin(fn))
480 486 if (size >= 0 and (size != st.st_size
481 487 or (mode ^ st.st_mode) & 0100)
482 488 or fn in self._copymap):
483 489 modified.append(fn)
484 490 elif time != int(st.st_mtime):
485 491 lookup.append(fn)
486 492 elif list_clean:
487 493 clean.append(fn)
488 494 elif type_ == 'm':
489 495 modified.append(fn)
490 496 elif type_ == 'a':
491 497 added.append(fn)
492 498 elif type_ == 'r':
493 499 removed.append(fn)
494 500
495 501 return (lookup, modified, added, removed, deleted, unknown, ignored,
496 502 clean)
General Comments 0
You need to be logged in to leave comments. Login now