changeset 1186:508c7d1b3e1c

Merge with myself.
author bos@serpentine.internal.keyresearch.com
date Thu, 01 Sep 2005 07:43:53 -0700
parents 2ae9c319e6fe (current diff) 9462df772bc8 (diff)
children 120aa5fc7ced
files
diffstat 3 files changed, 145 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/dirstate.py	Thu Sep 01 07:41:32 2005 -0700
+++ b/mercurial/dirstate.py	Thu Sep 01 07:43:53 2005 -0700
@@ -22,6 +22,7 @@
         self.pl = None
         self.copies = {}
         self.ignorefunc = None
+        self.blockignore = False
 
     def wjoin(self, f):
         return os.path.join(self.root, f)
@@ -32,6 +33,8 @@
         return cwd[len(self.root) + 1:]
 
     def ignore(self, f):
+        if self.blockignore:
+            return False
         if not self.ignorefunc:
             bigpat = []
             try:
@@ -214,98 +217,153 @@
         elif not dc:
             dc = self.filterfiles(files)
 
+        def statmatch(file, stat):
+            if file not in dc and self.ignore(file):
+                return False
+            return match(file)
+        return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
+
+    # walk recursively through the directory tree, finding all files
+    # matched by the statmatch function
+    # 
+    # results are yielded in a tuple (src, filename), where src is one of:
+    # 'f' the file was found in the directory tree
+    # 'm' the file was only in the dirstate and not in the tree
+    #
+    # dc is an optional arg for the current dirstate.  dc is not modified
+    # directly by this function, but might be modified by your statmatch call.
+    #
+    def walkhelper(self, files, statmatch, dc):
+        # recursion free walker, faster than os.walk.
+        def findfiles(s):
+            retfiles = []
+            work = [s]
+            while work:
+                top = work.pop()
+                names = os.listdir(top)
+                names.sort()
+                # nd is the top of the repository dir tree
+                nd = util.normpath(top[len(self.root) + 1:])
+                if nd == '.': nd = ''
+                for f in names:
+                    np = os.path.join(nd, f)
+                    if seen(np):
+                        continue
+                    p = os.path.join(top, f)
+                    st = os.stat(p)
+                    if stat.S_ISDIR(st.st_mode):
+                        ds = os.path.join(nd, f +'/')
+                        if statmatch(ds, st):
+                            work.append(p)
+                    else:
+                        if statmatch(np, st):
+                            yield np
+
         known = {'.hg': 1}
         def seen(fn):
             if fn in known: return True
             known[fn] = 1
-        def traverse():
-            for ff in util.unique(files):
-                f = os.path.join(self.root, ff)
-                try:
-                    st = os.stat(f)
-                except OSError, inst:
-                    if ff not in dc: self.ui.warn('%s: %s\n' % (
-                        util.pathto(self.getcwd(), ff),
-                        inst.strerror))
+
+        # step one, find all files that match our criteria
+        files.sort()
+        for ff in util.unique(files):
+            f = os.path.join(self.root, ff)
+            try:
+                st = os.stat(f)
+            except OSError, inst:
+                if ff not in dc: self.ui.warn('%s: %s\n' % (
+                    util.pathto(self.getcwd(), ff),
+                    inst.strerror))
+                continue
+            if stat.S_ISDIR(st.st_mode):
+                sorted = [ x for x in findfiles(f) ]
+                sorted.sort()
+                for fl in sorted:
+                    yield 'f', fl
+            elif stat.S_ISREG(st.st_mode):
+                ff = util.normpath(ff)
+                if seen(ff):
                     continue
-                if stat.S_ISDIR(st.st_mode):
-                    for dir, subdirs, fl in os.walk(f):
-                        d = dir[len(self.root) + 1:]
-                        nd = util.normpath(d)
-                        if nd == '.': nd = ''
-                        if seen(nd):
-                            subdirs[:] = []
-                            continue
-                        for sd in subdirs:
-                            ds = os.path.join(nd, sd +'/')
-                            if self.ignore(ds) or not match(ds):
-                                subdirs.remove(sd)
-                        subdirs.sort()
-                        fl.sort()
-                        for fn in fl:
-                            fn = util.pconvert(os.path.join(d, fn))
-                            yield 'f', fn
-                elif stat.S_ISREG(st.st_mode):
+                found = False
+                self.blockignore = True
+                if statmatch(ff, st):
+                    found = True
+                self.blockignore = False
+                if found:
                     yield 'f', ff
-                else:
-                    kind = 'unknown'
-                    if stat.S_ISCHR(st.st_mode): kind = 'character device'
-                    elif stat.S_ISBLK(st.st_mode): kind = 'block device'
-                    elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
-                    elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
-                    elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
-                    self.ui.warn('%s: unsupported file type (type is %s)\n' % (
-                        util.pathto(self.getcwd(), ff),
-                        kind))
+            else:
+                kind = 'unknown'
+                if stat.S_ISCHR(st.st_mode): kind = 'character device'
+                elif stat.S_ISBLK(st.st_mode): kind = 'block device'
+                elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
+                elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
+                elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
+                self.ui.warn('%s: unsupported file type (type is %s)\n' % (
+                    util.pathto(self.getcwd(), ff),
+                    kind))
 
-            ks = dc.keys()
-            ks.sort()
-            for k in ks:
+        # step two run through anything left in the dc hash and yield
+        # if we haven't already seen it
+        ks = dc.keys()
+        ks.sort()
+        for k in ks:
+            if not seen(k) and (statmatch(k, None)):
                 yield 'm', k
 
-        # yield only files that match: all in dirstate, others only if
-        # not in .hgignore
-
-        for src, fn in util.unique(traverse()):
-            fn = util.normpath(fn)
-            if seen(fn): continue
-            if fn not in dc and self.ignore(fn):
-                continue
-            if match(fn):
-                yield src, fn
-
     def changes(self, files=None, match=util.always):
         self.read()
         if not files:
+            files = [self.root]
             dc = self.map.copy()
         else:
             dc = self.filterfiles(files)
         lookup, modified, added, unknown = [], [], [], []
         removed, deleted = [], []
 
-        for src, fn in self.walk(files, match, dc=dc):
-            try:
-                s = os.stat(os.path.join(self.root, fn))
-            except OSError:
-                continue
+        # statmatch function to eliminate entries from the dirstate copy
+        # and put files into the appropriate array.  This gets passed
+        # to the walking code
+        def statmatch(fn, s):
+            def checkappend(l, fn):
+                if match is util.always or match(fn):
+                    l.append(fn)
+                   
+            if not s or stat.S_ISDIR(s.st_mode):
+                return self.ignore(fn) and False or match(fn)
+
             if not stat.S_ISREG(s.st_mode):
-                continue
-            c = dc.get(fn)
+                return False
+            c = dc.pop(fn, None)
             if c:
-                del dc[fn]
-                if c[0] == 'm':
-                    modified.append(fn)
-                elif c[0] == 'a':
-                    added.append(fn)
-                elif c[0] == 'r':
+                type, mode, size, time = c
+                # check the common case first
+                if type == 'n':
+                    if size != s.st_size or (mode ^ s.st_mode) & 0100:
+                        checkappend(modified, fn)
+                    elif time != s.st_mtime:
+                        checkappend(lookup, fn)
+                elif type == 'm':
+                    checkappend(modified, fn)
+                elif type == 'a':
+                    checkappend(added, fn)
+                elif type == 'r':
+                    checkappend(unknown, fn)
+            else:
+                if not self.ignore(fn) and match(fn):
                     unknown.append(fn)
-                elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
-                    modified.append(fn)
-                elif c[3] != s.st_mtime:
-                    lookup.append(fn)
-            else:
-                unknown.append(fn)
+            # return false because we've already handled all cases above.
+            # there's no need for the walking code to process the file
+            # any further.
+            return False
 
+        # because our statmatch always returns false, self.walk will only
+        # return files in the dirstate map that are not present in the FS.
+        # But, we still need to iterate through the results to force the
+        # walk to complete
+        for src, fn in self.walkhelper(files, statmatch, dc):
+            pass
+
+        # anything left in dc didn't exist in the filesystem
         for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
             if c[0] == 'r':
                 removed.append(fn)
--- a/tests/test-notfound.out	Thu Sep 01 07:41:32 2005 -0700
+++ b/tests/test-notfound.out	Thu Sep 01 07:43:53 2005 -0700
@@ -1,6 +1,6 @@
 Is there an error message when trying to diff non-existing files?
-not: No such file or directory
 found: No such file or directory
-Is there an error message when trying to add non-existing files?
 not: No such file or directory
+Is there an error message when trying to add non-existing files?
 found: No such file or directory
+not: No such file or directory
--- a/tests/test-walk.out	Thu Sep 01 07:41:32 2005 -0700
+++ b/tests/test-walk.out	Thu Sep 01 07:43:53 2005 -0700
@@ -1,55 +1,55 @@
-adding fennel
-adding fenugreek
-adding fiddlehead
-adding glob:glob
 adding beans/black
 adding beans/borlotti
 adding beans/kidney
 adding beans/navy
 adding beans/pinto
 adding beans/turtle
-adding mammals/skunk
+adding fennel
+adding fenugreek
+adding fiddlehead
+adding glob:glob
 adding mammals/Procyonidae/cacomistle
 adding mammals/Procyonidae/coatimundi
 adding mammals/Procyonidae/raccoon
-f  fennel                          fennel                          
-f  fenugreek                       fenugreek                       
-f  fiddlehead                      fiddlehead                      
-f  glob:glob                       glob:glob                       
+adding mammals/skunk
 f  beans/black                     beans/black                     
 f  beans/borlotti                  beans/borlotti                  
 f  beans/kidney                    beans/kidney                    
 f  beans/navy                      beans/navy                      
 f  beans/pinto                     beans/pinto                     
 f  beans/turtle                    beans/turtle                    
-f  mammals/skunk                   mammals/skunk                   
+f  fennel                          fennel                          
+f  fenugreek                       fenugreek                       
+f  fiddlehead                      fiddlehead                      
+f  glob:glob                       glob:glob                       
 f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle  
 f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi  
 f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon     
-f  mammals/skunk                   skunk                   
+f  mammals/skunk                   mammals/skunk                   
 f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle  
 f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi  
 f  mammals/Procyonidae/raccoon     Procyonidae/raccoon     
+f  mammals/skunk                   skunk                   
 f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle  
 f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi  
 f  mammals/Procyonidae/raccoon     Procyonidae/raccoon     
 f  mammals/Procyonidae/cacomistle  cacomistle  
 f  mammals/Procyonidae/coatimundi  coatimundi  
 f  mammals/Procyonidae/raccoon     raccoon     
-f  mammals/skunk                   ../skunk    
 f  mammals/Procyonidae/cacomistle  cacomistle  
 f  mammals/Procyonidae/coatimundi  coatimundi  
 f  mammals/Procyonidae/raccoon     raccoon     
+f  mammals/skunk                   ../skunk    
 f  beans/black     ../beans/black     
 f  beans/borlotti  ../beans/borlotti  
 f  beans/kidney    ../beans/kidney    
 f  beans/navy      ../beans/navy      
 f  beans/pinto     ../beans/pinto     
 f  beans/turtle    ../beans/turtle    
-f  mammals/skunk                   skunk                   
 f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle  
 f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi  
 f  mammals/Procyonidae/raccoon     Procyonidae/raccoon     
+f  mammals/skunk                   skunk                   
 f  beans/black     beans/black     
 f  beans/borlotti  beans/borlotti  
 f  beans/kidney    beans/kidney    
@@ -59,19 +59,19 @@
 f  beans/black     beans/black     
 f  beans/borlotti  beans/borlotti  
 f  mammals/skunk  mammals/skunk  
-f  mammals/skunk                   mammals/skunk                   
 f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle  
 f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi  
 f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon     
+f  mammals/skunk                   mammals/skunk                   
 abort: .. not under root
 abort: beans/../.. not under root
 f  fennel      fennel      
 f  fenugreek   fenugreek   
 f  fiddlehead  fiddlehead  
 f  glob:glob   glob:glob   
+f  beans/black    beans/black    
 f  fenugreek      fenugreek      
 f  glob:glob      glob:glob      
-f  beans/black    beans/black    
 f  mammals/skunk  mammals/skunk  
 f  beans/black  beans/black  
 f  beans/black     beans/black