##// END OF EJS Templates
Fix walkhelper on windows....
Christian Boos -
r1562:2f97af0b default
parent child Browse files
Show More
@@ -1,411 +1,411
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(object):
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.lazyread()
105 105 return self[key]
106 106
107 107 def __contains__(self, key):
108 108 self.lazyread()
109 109 return key in self.map
110 110
111 111 def parents(self):
112 112 self.lazyread()
113 113 return self.pl
114 114
115 115 def markdirty(self):
116 116 if not self.dirty:
117 117 self.dirty = 1
118 118
119 119 def setparents(self, p1, p2=nullid):
120 120 self.lazyread()
121 121 self.markdirty()
122 122 self.pl = p1, p2
123 123
124 124 def state(self, key):
125 125 try:
126 126 return self[key][0]
127 127 except KeyError:
128 128 return "?"
129 129
130 130 def lazyread(self):
131 131 if self.map is None:
132 132 self.read()
133 133
134 134 def read(self):
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.lazyread()
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.lazyread()
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(self.wjoin(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.lazyread()
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", atomic=True)
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 == '.':
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=False):
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.lazyread()
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 work = [s]
300 300 while work:
301 301 top = work.pop()
302 302 names = os.listdir(top)
303 303 names.sort()
304 304 # nd is the top of the repository dir tree
305 305 nd = util.normpath(top[len(self.root) + 1:])
306 306 if nd == '.': nd = ''
307 307 for f in names:
308 np = os.path.join(nd, f)
308 np = util.pconvert(os.path.join(nd, f))
309 309 if seen(np):
310 310 continue
311 311 p = os.path.join(top, f)
312 312 # don't trip over symlinks
313 313 st = os.lstat(p)
314 314 if stat.S_ISDIR(st.st_mode):
315 315 ds = os.path.join(nd, f +'/')
316 316 if statmatch(ds, st):
317 317 work.append(p)
318 318 if statmatch(np, st) and np in dc:
319 yield 'm', util.pconvert(np), st
319 yield 'm', np, st
320 320 elif statmatch(np, st):
321 321 if self.supported_type(np, st):
322 yield 'f', util.pconvert(np), st
322 yield 'f', np, st
323 323 elif np in dc:
324 yield 'm', util.pconvert(np), st
324 yield 'm', np, st
325 325
326 326 known = {'.hg': 1}
327 327 def seen(fn):
328 328 if fn in known: return True
329 329 known[fn] = 1
330 330
331 331 # step one, find all files that match our criteria
332 332 files.sort()
333 333 for ff in util.unique(files):
334 334 f = self.wjoin(ff)
335 335 try:
336 336 st = os.lstat(f)
337 337 except OSError, inst:
338 338 if ff not in dc: self.ui.warn('%s: %s\n' % (
339 339 util.pathto(self.getcwd(), ff),
340 340 inst.strerror))
341 341 continue
342 342 if stat.S_ISDIR(st.st_mode):
343 343 cmp1 = (lambda x, y: cmp(x[1], y[1]))
344 344 sorted = [ x for x in findfiles(f) ]
345 345 sorted.sort(cmp1)
346 346 for e in sorted:
347 347 yield e
348 348 else:
349 349 ff = util.normpath(ff)
350 350 if seen(ff):
351 351 continue
352 352 self.blockignore = True
353 353 if statmatch(ff, st):
354 354 if self.supported_type(ff, st, verbose=True):
355 355 yield 'f', ff, st
356 356 elif ff in dc:
357 357 yield 'm', ff, st
358 358 self.blockignore = False
359 359
360 360 # step two run through anything left in the dc hash and yield
361 361 # if we haven't already seen it
362 362 ks = dc.keys()
363 363 ks.sort()
364 364 for k in ks:
365 365 if not seen(k) and (statmatch(k, None)):
366 366 yield 'm', k, None
367 367
368 368 def changes(self, files=None, match=util.always):
369 369 lookup, modified, added, unknown = [], [], [], []
370 370 removed, deleted = [], []
371 371
372 372 for src, fn, st in self.statwalk(files, match):
373 373 try:
374 374 type, mode, size, time = self[fn]
375 375 except KeyError:
376 376 unknown.append(fn)
377 377 continue
378 378 if src == 'm':
379 379 nonexistent = True
380 380 if not st:
381 381 try:
382 382 f = self.wjoin(fn)
383 383 st = os.lstat(f)
384 384 except OSError, inst:
385 385 if inst.errno != errno.ENOENT:
386 386 raise
387 387 st = None
388 388 # We need to re-check that it is a valid file
389 389 if st and self.supported_type(fn, st):
390 390 nonexistent = False
391 391 # XXX: what to do with file no longer present in the fs
392 392 # who are not removed in the dirstate ?
393 393 if nonexistent and type in "nm":
394 394 deleted.append(fn)
395 395 continue
396 396 # check the common case first
397 397 if type == 'n':
398 398 if not st:
399 399 st = os.stat(fn)
400 400 if size != st.st_size or (mode ^ st.st_mode) & 0100:
401 401 modified.append(fn)
402 402 elif time != st.st_mtime:
403 403 lookup.append(fn)
404 404 elif type == 'm':
405 405 modified.append(fn)
406 406 elif type == 'a':
407 407 added.append(fn)
408 408 elif type == 'r':
409 409 removed.append(fn)
410 410
411 411 return (lookup, modified, added, removed + deleted, unknown)
General Comments 0
You need to be logged in to leave comments. Login now