212 if not dc: |
215 if not dc: |
213 dc = self.map.copy() |
216 dc = self.map.copy() |
214 elif not dc: |
217 elif not dc: |
215 dc = self.filterfiles(files) |
218 dc = self.filterfiles(files) |
216 |
219 |
|
220 def statmatch(file, stat): |
|
221 if file not in dc and self.ignore(file): |
|
222 return False |
|
223 return match(file) |
|
224 return self.walkhelper(files=files, statmatch=statmatch, dc=dc) |
|
225 |
|
226 # walk recursively through the directory tree, finding all files |
|
227 # matched by the statmatch function |
|
228 # |
|
229 # results are yielded in a tuple (src, filename), where src is one of: |
|
230 # 'f' the file was found in the directory tree |
|
231 # 'm' the file was only in the dirstate and not in the tree |
|
232 # |
|
233 # dc is an optional arg for the current dirstate. dc is not modified |
|
234 # directly by this function, but might be modified by your statmatch call. |
|
235 # |
|
236 def walkhelper(self, files, statmatch, dc): |
|
237 # recursion free walker, faster than os.walk. |
|
238 def findfiles(s): |
|
239 retfiles = [] |
|
240 work = [s] |
|
241 while work: |
|
242 top = work.pop() |
|
243 names = os.listdir(top) |
|
244 names.sort() |
|
245 # nd is the top of the repository dir tree |
|
246 nd = util.normpath(top[len(self.root) + 1:]) |
|
247 if nd == '.': nd = '' |
|
248 for f in names: |
|
249 np = os.path.join(nd, f) |
|
250 if seen(np): |
|
251 continue |
|
252 p = os.path.join(top, f) |
|
253 st = os.stat(p) |
|
254 if stat.S_ISDIR(st.st_mode): |
|
255 ds = os.path.join(nd, f +'/') |
|
256 if statmatch(ds, st): |
|
257 work.append(p) |
|
258 else: |
|
259 if statmatch(np, st): |
|
260 yield np |
|
261 |
217 known = {'.hg': 1} |
262 known = {'.hg': 1} |
218 def seen(fn): |
263 def seen(fn): |
219 if fn in known: return True |
264 if fn in known: return True |
220 known[fn] = 1 |
265 known[fn] = 1 |
221 def traverse(): |
266 |
222 for ff in util.unique(files): |
267 # step one, find all files that match our criteria |
223 f = os.path.join(self.root, ff) |
268 files.sort() |
224 try: |
269 for ff in util.unique(files): |
225 st = os.stat(f) |
270 f = os.path.join(self.root, ff) |
226 except OSError, inst: |
271 try: |
227 if ff not in dc: self.ui.warn('%s: %s\n' % ( |
272 st = os.stat(f) |
228 util.pathto(self.getcwd(), ff), |
273 except OSError, inst: |
229 inst.strerror)) |
274 if ff not in dc: self.ui.warn('%s: %s\n' % ( |
|
275 util.pathto(self.getcwd(), ff), |
|
276 inst.strerror)) |
|
277 continue |
|
278 if stat.S_ISDIR(st.st_mode): |
|
279 sorted = [ x for x in findfiles(f) ] |
|
280 sorted.sort() |
|
281 for fl in sorted: |
|
282 yield 'f', fl |
|
283 elif stat.S_ISREG(st.st_mode): |
|
284 ff = util.normpath(ff) |
|
285 if seen(ff): |
230 continue |
286 continue |
231 if stat.S_ISDIR(st.st_mode): |
287 found = False |
232 for dir, subdirs, fl in os.walk(f): |
288 self.blockignore = True |
233 d = dir[len(self.root) + 1:] |
289 if statmatch(ff, st): |
234 nd = util.normpath(d) |
290 found = True |
235 if nd == '.': nd = '' |
291 self.blockignore = False |
236 if seen(nd): |
292 if found: |
237 subdirs[:] = [] |
|
238 continue |
|
239 for sd in subdirs: |
|
240 ds = os.path.join(nd, sd +'/') |
|
241 if self.ignore(ds) or not match(ds): |
|
242 subdirs.remove(sd) |
|
243 subdirs.sort() |
|
244 fl.sort() |
|
245 for fn in fl: |
|
246 fn = util.pconvert(os.path.join(d, fn)) |
|
247 yield 'f', fn |
|
248 elif stat.S_ISREG(st.st_mode): |
|
249 yield 'f', ff |
293 yield 'f', ff |
250 else: |
294 else: |
251 kind = 'unknown' |
295 kind = 'unknown' |
252 if stat.S_ISCHR(st.st_mode): kind = 'character device' |
296 if stat.S_ISCHR(st.st_mode): kind = 'character device' |
253 elif stat.S_ISBLK(st.st_mode): kind = 'block device' |
297 elif stat.S_ISBLK(st.st_mode): kind = 'block device' |
254 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo' |
298 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo' |
255 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link' |
299 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link' |
256 elif stat.S_ISSOCK(st.st_mode): kind = 'socket' |
300 elif stat.S_ISSOCK(st.st_mode): kind = 'socket' |
257 self.ui.warn('%s: unsupported file type (type is %s)\n' % ( |
301 self.ui.warn('%s: unsupported file type (type is %s)\n' % ( |
258 util.pathto(self.getcwd(), ff), |
302 util.pathto(self.getcwd(), ff), |
259 kind)) |
303 kind)) |
260 |
304 |
261 ks = dc.keys() |
305 # step two run through anything left in the dc hash and yield |
262 ks.sort() |
306 # if we haven't already seen it |
263 for k in ks: |
307 ks = dc.keys() |
|
308 ks.sort() |
|
309 for k in ks: |
|
310 if not seen(k) and (statmatch(k, None)): |
264 yield 'm', k |
311 yield 'm', k |
265 |
312 |
266 # yield only files that match: all in dirstate, others only if |
|
267 # not in .hgignore |
|
268 |
|
269 for src, fn in util.unique(traverse()): |
|
270 fn = util.normpath(fn) |
|
271 if seen(fn): continue |
|
272 if fn not in dc and self.ignore(fn): |
|
273 continue |
|
274 if match(fn): |
|
275 yield src, fn |
|
276 |
|
277 def changes(self, files=None, match=util.always): |
313 def changes(self, files=None, match=util.always): |
278 self.read() |
314 self.read() |
279 if not files: |
315 if not files: |
|
316 files = [self.root] |
280 dc = self.map.copy() |
317 dc = self.map.copy() |
281 else: |
318 else: |
282 dc = self.filterfiles(files) |
319 dc = self.filterfiles(files) |
283 lookup, modified, added, unknown = [], [], [], [] |
320 lookup, modified, added, unknown = [], [], [], [] |
284 removed, deleted = [], [] |
321 removed, deleted = [], [] |
285 |
322 |
286 for src, fn in self.walk(files, match, dc=dc): |
323 # statmatch function to eliminate entries from the dirstate copy |
287 try: |
324 # and put files into the appropriate array. This gets passed |
288 s = os.stat(os.path.join(self.root, fn)) |
325 # to the walking code |
289 except OSError: |
326 def statmatch(fn, s): |
290 continue |
327 def checkappend(l, fn): |
|
328 if match is util.always or match(fn): |
|
329 l.append(fn) |
|
330 |
|
331 if not s or stat.S_ISDIR(s.st_mode): |
|
332 return self.ignore(fn) and False or match(fn) |
|
333 |
291 if not stat.S_ISREG(s.st_mode): |
334 if not stat.S_ISREG(s.st_mode): |
292 continue |
335 return False |
293 c = dc.get(fn) |
336 c = dc.pop(fn, None) |
294 if c: |
337 if c: |
295 del dc[fn] |
338 type, mode, size, time = c |
296 if c[0] == 'm': |
339 # check the common case first |
297 modified.append(fn) |
340 if type == 'n': |
298 elif c[0] == 'a': |
341 if size != s.st_size or (mode ^ s.st_mode) & 0100: |
299 added.append(fn) |
342 checkappend(modified, fn) |
300 elif c[0] == 'r': |
343 elif time != s.st_mtime: |
|
344 checkappend(lookup, fn) |
|
345 elif type == 'm': |
|
346 checkappend(modified, fn) |
|
347 elif type == 'a': |
|
348 checkappend(added, fn) |
|
349 elif type == 'r': |
|
350 checkappend(unknown, fn) |
|
351 else: |
|
352 if not self.ignore(fn) and match(fn): |
301 unknown.append(fn) |
353 unknown.append(fn) |
302 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100: |
354 # return false because we've already handled all cases above. |
303 modified.append(fn) |
355 # there's no need for the walking code to process the file |
304 elif c[3] != s.st_mtime: |
356 # any further. |
305 lookup.append(fn) |
357 return False |
306 else: |
358 |
307 unknown.append(fn) |
359 # because our statmatch always returns false, self.walk will only |
308 |
360 # return files in the dirstate map that are not present in the FS. |
|
361 # But, we still need to iterate through the results to force the |
|
362 # walk to complete |
|
363 for src, fn in self.walkhelper(files, statmatch, dc): |
|
364 pass |
|
365 |
|
366 # anything left in dc didn't exist in the filesystem |
309 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]: |
367 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]: |
310 if c[0] == 'r': |
368 if c[0] == 'r': |
311 removed.append(fn) |
369 removed.append(fn) |
312 else: |
370 else: |
313 deleted.append(fn) |
371 deleted.append(fn) |