##// END OF EJS Templates
fix a bug in dirstate.changes when cwd != repo.root...
Benoit Boissinot -
r1491:91c0e8d7 default
parent child Browse files
Show More
@@ -1,411 +1,412 b''
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005 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 import struct, os
11 11 from node import *
12 12 from i18n import gettext as _
13 13 from demandload import *
14 14 demandload(globals(), "time bisect stat util re errno")
15 15
16 16 class dirstate:
17 17 def __init__(self, opener, ui, root):
18 18 self.opener = opener
19 19 self.root = root
20 20 self.dirty = 0
21 21 self.ui = ui
22 22 self.map = None
23 23 self.pl = None
24 24 self.copies = {}
25 25 self.ignorefunc = None
26 26 self.blockignore = False
27 27
28 28 def wjoin(self, f):
29 29 return os.path.join(self.root, f)
30 30
31 31 def getcwd(self):
32 32 cwd = os.getcwd()
33 33 if cwd == self.root: return ''
34 34 return cwd[len(self.root) + 1:]
35 35
36 36 def hgignore(self):
37 37 '''return the contents of .hgignore as a list of patterns.
38 38
39 39 trailing white space is dropped.
40 40 the escape character is backslash.
41 41 comments start with #.
42 42 empty lines are skipped.
43 43
44 44 lines can be of the following formats:
45 45
46 46 syntax: regexp # defaults following lines to non-rooted regexps
47 47 syntax: glob # defaults following lines to non-rooted globs
48 48 re:pattern # non-rooted regular expression
49 49 glob:pattern # non-rooted glob
50 50 pattern # pattern of the current default type'''
51 51 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
52 52 def parselines(fp):
53 53 for line in fp:
54 54 escape = False
55 55 for i in xrange(len(line)):
56 56 if escape: escape = False
57 57 elif line[i] == '\\': escape = True
58 58 elif line[i] == '#': break
59 59 line = line[:i].rstrip()
60 60 if line: yield line
61 61 pats = []
62 62 try:
63 63 fp = open(self.wjoin('.hgignore'))
64 64 syntax = 'relre:'
65 65 for line in parselines(fp):
66 66 if line.startswith('syntax:'):
67 67 s = line[7:].strip()
68 68 try:
69 69 syntax = syntaxes[s]
70 70 except KeyError:
71 71 self.ui.warn(_("ignoring invalid syntax '%s'\n") % s)
72 72 continue
73 73 pat = syntax + line
74 74 for s in syntaxes.values():
75 75 if line.startswith(s):
76 76 pat = line
77 77 break
78 78 pats.append(pat)
79 79 except IOError: pass
80 80 return pats
81 81
82 82 def ignore(self, fn):
83 83 '''default match function used by dirstate and localrepository.
84 84 this honours the .hgignore file, and nothing more.'''
85 85 if self.blockignore:
86 86 return False
87 87 if not self.ignorefunc:
88 88 ignore = self.hgignore()
89 89 if ignore:
90 90 files, self.ignorefunc, anypats = util.matcher(self.root,
91 91 inc=ignore)
92 92 else:
93 93 self.ignorefunc = util.never
94 94 return self.ignorefunc(fn)
95 95
96 96 def __del__(self):
97 97 if self.dirty:
98 98 self.write()
99 99
100 100 def __getitem__(self, key):
101 101 try:
102 102 return self.map[key]
103 103 except TypeError:
104 104 self.read()
105 105 return self[key]
106 106
107 107 def __contains__(self, key):
108 108 if not self.map: self.read()
109 109 return key in self.map
110 110
111 111 def parents(self):
112 112 if not self.pl:
113 113 self.read()
114 114 return self.pl
115 115
116 116 def markdirty(self):
117 117 if not self.dirty:
118 118 self.dirty = 1
119 119
120 120 def setparents(self, p1, p2=nullid):
121 121 if not self.pl:
122 122 self.read()
123 123 self.markdirty()
124 124 self.pl = p1, p2
125 125
126 126 def state(self, key):
127 127 try:
128 128 return self[key][0]
129 129 except KeyError:
130 130 return "?"
131 131
132 132 def read(self):
133 133 if self.map is not None: return self.map
134 134
135 135 self.map = {}
136 136 self.pl = [nullid, nullid]
137 137 try:
138 138 st = self.opener("dirstate").read()
139 139 if not st: return
140 140 except: return
141 141
142 142 self.pl = [st[:20], st[20: 40]]
143 143
144 144 pos = 40
145 145 while pos < len(st):
146 146 e = struct.unpack(">cllll", st[pos:pos+17])
147 147 l = e[4]
148 148 pos += 17
149 149 f = st[pos:pos + l]
150 150 if '\0' in f:
151 151 f, c = f.split('\0')
152 152 self.copies[f] = c
153 153 self.map[f] = e[:4]
154 154 pos += l
155 155
156 156 def copy(self, source, dest):
157 157 self.read()
158 158 self.markdirty()
159 159 self.copies[dest] = source
160 160
161 161 def copied(self, file):
162 162 return self.copies.get(file, None)
163 163
164 164 def update(self, files, state, **kw):
165 165 ''' current states:
166 166 n normal
167 167 m needs merging
168 168 r marked for removal
169 169 a marked for addition'''
170 170
171 171 if not files: return
172 172 self.read()
173 173 self.markdirty()
174 174 for f in files:
175 175 if state == "r":
176 176 self.map[f] = ('r', 0, 0, 0)
177 177 else:
178 178 s = os.lstat(os.path.join(self.root, f))
179 179 st_size = kw.get('st_size', s.st_size)
180 180 st_mtime = kw.get('st_mtime', s.st_mtime)
181 181 self.map[f] = (state, s.st_mode, st_size, st_mtime)
182 182 if self.copies.has_key(f):
183 183 del self.copies[f]
184 184
185 185 def forget(self, files):
186 186 if not files: return
187 187 self.read()
188 188 self.markdirty()
189 189 for f in files:
190 190 try:
191 191 del self.map[f]
192 192 except KeyError:
193 193 self.ui.warn(_("not in dirstate: %s!\n") % f)
194 194 pass
195 195
196 196 def clear(self):
197 197 self.map = {}
198 198 self.markdirty()
199 199
200 200 def write(self):
201 201 st = self.opener("dirstate", "w")
202 202 st.write("".join(self.pl))
203 203 for f, e in self.map.items():
204 204 c = self.copied(f)
205 205 if c:
206 206 f = f + "\0" + c
207 207 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
208 208 st.write(e + f)
209 209 self.dirty = 0
210 210
211 211 def filterfiles(self, files):
212 212 ret = {}
213 213 unknown = []
214 214
215 215 for x in files:
216 216 if x is '.':
217 217 return self.map.copy()
218 218 if x not in self.map:
219 219 unknown.append(x)
220 220 else:
221 221 ret[x] = self.map[x]
222 222
223 223 if not unknown:
224 224 return ret
225 225
226 226 b = self.map.keys()
227 227 b.sort()
228 228 blen = len(b)
229 229
230 230 for x in unknown:
231 231 bs = bisect.bisect(b, x)
232 232 if bs != 0 and b[bs-1] == x:
233 233 ret[x] = self.map[x]
234 234 continue
235 235 while bs < blen:
236 236 s = b[bs]
237 237 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
238 238 ret[s] = self.map[s]
239 239 else:
240 240 break
241 241 bs += 1
242 242 return ret
243 243
244 244 def supported_type(self, f, st, verbose=True):
245 245 if stat.S_ISREG(st.st_mode):
246 246 return True
247 247 if verbose:
248 248 kind = 'unknown'
249 249 if stat.S_ISCHR(st.st_mode): kind = _('character device')
250 250 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
251 251 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
252 252 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
253 253 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
254 254 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
255 255 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
256 256 util.pathto(self.getcwd(), f),
257 257 kind))
258 258 return False
259 259
260 260 def statwalk(self, files=None, match=util.always, dc=None):
261 261 self.read()
262 262
263 263 # walk all files by default
264 264 if not files:
265 265 files = [self.root]
266 266 if not dc:
267 267 dc = self.map.copy()
268 268 elif not dc:
269 269 dc = self.filterfiles(files)
270 270
271 271 def statmatch(file, stat):
272 272 file = util.pconvert(file)
273 273 if file not in dc and self.ignore(file):
274 274 return False
275 275 return match(file)
276 276
277 277 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
278 278
279 279 def walk(self, files=None, match=util.always, dc=None):
280 280 # filter out the stat
281 281 for src, f, st in self.statwalk(files, match, dc):
282 282 yield src, f
283 283
284 284 # walk recursively through the directory tree, finding all files
285 285 # matched by the statmatch function
286 286 #
287 287 # results are yielded in a tuple (src, filename, st), where src
288 288 # is one of:
289 289 # 'f' the file was found in the directory tree
290 290 # 'm' the file was only in the dirstate and not in the tree
291 291 # and st is the stat result if the file was found in the directory.
292 292 #
293 293 # dc is an optional arg for the current dirstate. dc is not modified
294 294 # directly by this function, but might be modified by your statmatch call.
295 295 #
296 296 def walkhelper(self, files, statmatch, dc):
297 297 # recursion free walker, faster than os.walk.
298 298 def findfiles(s):
299 299 retfiles = []
300 300 work = [s]
301 301 while work:
302 302 top = work.pop()
303 303 names = os.listdir(top)
304 304 names.sort()
305 305 # nd is the top of the repository dir tree
306 306 nd = util.normpath(top[len(self.root) + 1:])
307 307 if nd == '.': nd = ''
308 308 for f in names:
309 309 np = os.path.join(nd, f)
310 310 if seen(np):
311 311 continue
312 312 p = os.path.join(top, f)
313 313 # don't trip over symlinks
314 314 st = os.lstat(p)
315 315 if stat.S_ISDIR(st.st_mode):
316 316 ds = os.path.join(nd, f +'/')
317 317 if statmatch(ds, st):
318 318 work.append(p)
319 319 if statmatch(np, st) and np in dc:
320 320 yield 'm', util.pconvert(np), st
321 321 elif statmatch(np, st):
322 322 if self.supported_type(np, st):
323 323 yield 'f', util.pconvert(np), st
324 324 elif np in dc:
325 325 yield 'm', util.pconvert(np), st
326 326
327 327 known = {'.hg': 1}
328 328 def seen(fn):
329 329 if fn in known: return True
330 330 known[fn] = 1
331 331
332 332 # step one, find all files that match our criteria
333 333 files.sort()
334 334 for ff in util.unique(files):
335 335 f = os.path.join(self.root, ff)
336 336 try:
337 337 st = os.lstat(f)
338 338 except OSError, inst:
339 339 if ff not in dc: self.ui.warn('%s: %s\n' % (
340 340 util.pathto(self.getcwd(), ff),
341 341 inst.strerror))
342 342 continue
343 343 if stat.S_ISDIR(st.st_mode):
344 344 cmp1 = (lambda x, y: cmp(x[1], y[1]))
345 345 sorted = [ x for x in findfiles(f) ]
346 346 sorted.sort(cmp1)
347 347 for e in sorted:
348 348 yield e
349 349 else:
350 350 ff = util.normpath(ff)
351 351 if seen(ff):
352 352 continue
353 353 self.blockignore = True
354 354 if statmatch(ff, st):
355 355 if self.supported_type(ff, st):
356 356 yield 'f', ff, st
357 357 elif ff in dc:
358 358 yield 'm', ff, st
359 359 self.blockignore = False
360 360
361 361 # step two run through anything left in the dc hash and yield
362 362 # if we haven't already seen it
363 363 ks = dc.keys()
364 364 ks.sort()
365 365 for k in ks:
366 366 if not seen(k) and (statmatch(k, None)):
367 367 yield 'm', k, None
368 368
369 369 def changes(self, files=None, match=util.always):
370 370 lookup, modified, added, unknown = [], [], [], []
371 371 removed, deleted = [], []
372 372
373 373 for src, fn, st in self.statwalk(files, match):
374 374 try:
375 375 type, mode, size, time = self[fn]
376 376 except KeyError:
377 377 unknown.append(fn)
378 378 continue
379 379 if src == 'm':
380 380 nonexistent = True
381 381 if not st:
382 382 try:
383 st = os.lstat(fn)
383 f = os.path.join(self.root, fn)
384 st = os.lstat(f)
384 385 except OSError, inst:
385 386 if inst.errno != errno.ENOENT:
386 387 raise
387 388 st = None
388 389 # We need to re-check that it is a valid file
389 390 if st and self.supported_type(fn, st):
390 391 nonexistent = False
391 392 # XXX: what to do with file no longer present in the fs
392 393 # who are not removed in the dirstate ?
393 394 if nonexistent and type in "nm":
394 395 deleted.append(fn)
395 396 continue
396 397 # check the common case first
397 398 if type == 'n':
398 399 if not st:
399 400 st = os.stat(fn)
400 401 if size != st.st_size or (mode ^ st.st_mode) & 0100:
401 402 modified.append(fn)
402 403 elif time != st.st_mtime:
403 404 lookup.append(fn)
404 405 elif type == 'm':
405 406 modified.append(fn)
406 407 elif type == 'a':
407 408 added.append(fn)
408 409 elif type == 'r':
409 410 removed.append(fn)
410 411
411 412 return (lookup, modified, added, removed + deleted, unknown)
@@ -1,42 +1,45 b''
1 1 #!/bin/sh
2 2
3 3 hg init
4 4 touch a.o
5 5 touch a.c
6 6 touch syntax
7 7 mkdir dir
8 8 touch dir/a.o
9 9 touch dir/b.o
10 10 touch dir/c.o
11 11
12 12 hg add dir/a.o
13 13 hg commit -m 0
14 14 hg add dir/b.o
15 15
16 16 echo "--" ; hg status
17 17
18 18 echo "*.o" > .hgignore
19 19 echo "--" ; hg status
20 20
21 21 echo ".*\.o" > .hgignore
22 22 echo "--" ; hg status
23 23
24 24 # XXX: broken
25 25 #echo "glob:**.o" > .hgignore
26 26 #echo "--" ; hg status
27 27 #
28 28 #echo "glob:*.o" > .hgignore
29 29 #echo "--" ; hg status
30 30
31 31 echo "syntax: invalid" > .hgignore
32 32 echo "--" ; hg status
33 33
34 34 echo "syntax: glob" > .hgignore
35 35 echo "*.o" >> .hgignore
36 36 echo "--" ; hg status
37 37
38 38 echo "relglob:syntax*" > .hgignore
39 39 echo "--" ; hg status
40 40
41 41 echo "relglob:*" > .hgignore
42 42 echo "--" ; hg status
43
44 cd dir
45 echo "--" ; hg status
@@ -1,34 +1,36 b''
1 1 --
2 2 A dir/b.o
3 3 ? a.c
4 4 ? a.o
5 5 ? dir/c.o
6 6 ? syntax
7 7 --
8 8 abort: invalid pattern: relre:*.o
9 9 --
10 10 A dir/b.o
11 11 ? .hgignore
12 12 ? a.c
13 13 ? syntax
14 14 --
15 15 ignoring invalid syntax 'invalid'
16 16 A dir/b.o
17 17 ? .hgignore
18 18 ? a.c
19 19 ? a.o
20 20 ? dir/c.o
21 21 ? syntax
22 22 --
23 23 A dir/b.o
24 24 ? .hgignore
25 25 ? a.c
26 26 ? syntax
27 27 --
28 28 A dir/b.o
29 29 ? .hgignore
30 30 ? a.c
31 31 ? a.o
32 32 ? dir/c.o
33 33 --
34 34 A dir/b.o
35 --
36 A b.o
General Comments 0
You need to be logged in to leave comments. Login now