~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-05-19 06:10:20 UTC
  • mfrom: (1714.1.2 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060519061020-91373a33de6fa675
Optimisation of WorkingTree.is_ignored to reduce the threshold where N^2 performance creeps in. (Jan Hudec, Robert Collins).

Show diffs side-by-side

added added

removed removed

Lines of Context:
978
978
                subp = appendpath(path, subf)
979
979
                yield subp
980
980
 
 
981
    def _translate_ignore_rule(self, rule):
 
982
        """Translate a single ignore rule to a regex.
 
983
 
 
984
        There are two types of ignore rules.  Those that do not contain a / are
 
985
        matched against the tail of the filename (that is, they do not care
 
986
        what directory the file is in.)  Rules which do contain a slash must
 
987
        match the entire path.  As a special case, './' at the start of the
 
988
        string counts as a slash in the string but is removed before matching
 
989
        (e.g. ./foo.c, ./src/foo.c)
 
990
 
 
991
        :return: The translated regex.
 
992
        """
 
993
        if rule[:2] in ('./', '.\\'):
 
994
            # rootdir rule
 
995
            result = fnmatch.translate(rule[2:])
 
996
        elif '/' in rule or '\\' in rule:
 
997
            # path prefix 
 
998
            result = fnmatch.translate(rule)
 
999
        else:
 
1000
            # default rule style.
 
1001
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
 
1002
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
 
1003
        return "(" + result + ")"
 
1004
 
 
1005
    def _combine_ignore_rules(self, rules):
 
1006
        """Combine a list of ignore rules into a single regex object.
 
1007
 
 
1008
        Each individual rule is combined with | to form a big regex, which then
 
1009
        has $ added to it to form something like ()|()|()$. The group index for
 
1010
        each subregex's outermost group is placed in a dictionary mapping back 
 
1011
        to the rule. This allows quick identification of the matching rule that
 
1012
        triggered a match.
 
1013
        :return: a list of the compiled regex and the matching-group index 
 
1014
        dictionaries. We return a list because python complains if you try to 
 
1015
        combine more than 100 regexes.
 
1016
        """
 
1017
        result = []
 
1018
        groups = {}
 
1019
        next_group = 0
 
1020
        translated_rules = []
 
1021
        for rule in rules:
 
1022
            translated_rule = self._translate_ignore_rule(rule)
 
1023
            compiled_rule = re.compile(translated_rule)
 
1024
            groups[next_group] = rule
 
1025
            next_group += compiled_rule.groups
 
1026
            translated_rules.append(translated_rule)
 
1027
            if next_group == 99:
 
1028
                result.append((re.compile("|".join(translated_rules)), groups))
 
1029
                groups = {}
 
1030
                next_group = 0
 
1031
                translated_rules = []
 
1032
        if len(translated_rules):
 
1033
            result.append((re.compile("|".join(translated_rules)), groups))
 
1034
        return result
981
1035
 
982
1036
    def ignored_files(self):
983
1037
        """Yield list of PATH, IGNORE_PATTERN"""
986
1040
            if pat != None:
987
1041
                yield subp, pat
988
1042
 
989
 
 
990
1043
    def get_ignore_list(self):
991
1044
        """Return list of ignore patterns.
992
1045
 
1000
1053
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1001
1054
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
1002
1055
        self._ignorelist = l
 
1056
        self._ignore_regex = self._combine_ignore_rules(l)
1003
1057
        return l
1004
1058
 
 
1059
    def _get_ignore_rules_as_regex(self):
 
1060
        """Return a regex of the ignore rules and a mapping dict.
 
1061
 
 
1062
        :return: (ignore rules compiled regex, dictionary mapping rule group 
 
1063
        indices to original rule.)
 
1064
        """
 
1065
        if getattr(self, '_ignorelist', None) is None:
 
1066
            self.get_ignore_list()
 
1067
        return self._ignore_regex
1005
1068
 
1006
1069
    def is_ignored(self, filename):
1007
1070
        r"""Check whether the filename matches an ignore pattern.
1021
1084
        # treat dotfiles correctly and allows * to match /.
1022
1085
        # Eventually it should be replaced with something more
1023
1086
        # accurate.
1024
 
        
1025
 
        basename = splitpath(filename)[-1]
1026
 
        for pat in self.get_ignore_list():
1027
 
            if '/' in pat or '\\' in pat:
1028
 
                
1029
 
                # as a special case, you can put ./ at the start of a
1030
 
                # pattern; this is good to match in the top-level
1031
 
                # only;
1032
 
                if pat[:2] in ('./', '.\\'):
1033
 
                    newpat = pat[2:]
1034
 
                else:
1035
 
                    newpat = pat
1036
 
                if fnmatch.fnmatchcase(filename, newpat):
1037
 
                    return pat
1038
 
            else:
1039
 
                if fnmatch.fnmatchcase(basename, pat):
1040
 
                    return pat
 
1087
    
 
1088
        rules = self._get_ignore_rules_as_regex()
 
1089
        for regex, mapping in rules:
 
1090
            match = regex.match(filename)
 
1091
            if match is not None:
 
1092
                # one or more of the groups in mapping will have a non-None group 
 
1093
                # match.
 
1094
                groups = match.groups()
 
1095
                rules = [mapping[group] for group in 
 
1096
                    mapping if groups[group] is not None]
 
1097
                return rules[0]
1041
1098
        return None
1042
1099
 
1043
1100
    def kind(self, file_id):