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