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