~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-22 14:53:12 UTC
  • mfrom: (1707.3.30 ftp-server)
  • Revision ID: pqm@pqm.ubuntu.com-20060522145312-94c3c934d7cbd9ff
(jam) use medusa as an ftp-server provider, fix FtpTransport

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
# At the moment they may alias the inventory and have old copies of it in
40
40
# memory.  (Now done? -- mbp 20060309)
41
41
 
 
42
from binascii import hexlify
42
43
from copy import deepcopy
43
44
from cStringIO import StringIO
44
45
import errno
45
46
import fnmatch
46
47
import os
 
48
import re
47
49
import stat
48
 
 
 
50
from time import time
49
51
 
50
52
from bzrlib.atomicfile import AtomicFile
51
53
from bzrlib.branch import (Branch,
80
82
                            pumpfile,
81
83
                            safe_unicode,
82
84
                            splitpath,
83
 
                            rand_bytes,
 
85
                            rand_chars,
84
86
                            normpath,
85
87
                            realpath,
86
88
                            relpath,
101
103
import bzrlib.xml5
102
104
 
103
105
 
 
106
# the regex here does the following:
 
107
# 1) remove any weird characters; we don't escape them but rather
 
108
# just pull them out
 
109
 # 2) match leading '.'s to make it not hidden
 
110
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
 
111
_gen_id_suffix = None
 
112
_gen_id_serial = 0
 
113
 
 
114
 
 
115
def _next_id_suffix():
 
116
    """Create a new file id suffix that is reasonably unique.
 
117
    
 
118
    On the first call we combine the current time with 64 bits of randomness
 
119
    to give a highly probably globally unique number. Then each call in the same
 
120
    process adds 1 to a serial number we append to that unique value.
 
121
    """
 
122
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
 
123
    # than having to move the id randomness out of the inner loop like this.
 
124
    # XXX TODO: for the global randomness this uses we should add the thread-id
 
125
    # before the serial #.
 
126
    global _gen_id_suffix, _gen_id_serial
 
127
    if _gen_id_suffix is None:
 
128
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
 
129
    _gen_id_serial += 1
 
130
    return _gen_id_suffix + str(_gen_id_serial)
 
131
 
 
132
 
104
133
def gen_file_id(name):
105
 
    """Return new file id.
106
 
 
107
 
    This should probably generate proper UUIDs, but for the moment we
108
 
    cope with just randomness because running uuidgen every time is
109
 
    slow."""
110
 
    import re
111
 
    from binascii import hexlify
112
 
    from time import time
113
 
 
114
 
    # get last component
115
 
    idx = name.rfind('/')
116
 
    if idx != -1:
117
 
        name = name[idx+1 : ]
118
 
    idx = name.rfind('\\')
119
 
    if idx != -1:
120
 
        name = name[idx+1 : ]
121
 
 
122
 
    # make it not a hidden file
123
 
    name = name.lstrip('.')
124
 
 
125
 
    # remove any wierd characters; we don't escape them but rather
126
 
    # just pull them out
127
 
    name = re.sub(r'[^\w.]', '', name)
128
 
 
129
 
    s = hexlify(rand_bytes(8))
130
 
    return '-'.join((name, compact_date(time()), s))
 
134
    """Return new file id for the basename 'name'.
 
135
 
 
136
    The uniqueness is supplied from _next_id_suffix.
 
137
    """
 
138
    # XXX TODO: squash the filename to lowercase.
 
139
    # XXX TODO: truncate the filename to something like 20 or 30 chars.
 
140
    # XXX TODO: consider what to do with ids that look like illegal filepaths
 
141
    # on platforms we support.
 
142
    return _gen_file_id_re.sub('', name) + _next_id_suffix()
131
143
 
132
144
 
133
145
def gen_root_id():
308
320
    def is_control_filename(self, filename):
309
321
        """True if filename is the name of a control file in this tree.
310
322
        
 
323
        :param filename: A filename within the tree. This is a relative path
 
324
        from the root of this tree.
 
325
 
311
326
        This is true IF and ONLY IF the filename is part of the meta data
312
327
        that bzr controls in this tree. I.E. a random .bzr directory placed
313
328
        on disk will not be a control file for this tree.
314
329
        """
315
 
        try:
316
 
            self.bzrdir.transport.relpath(self.abspath(filename))
317
 
            return True
318
 
        except errors.PathNotChild:
319
 
            return False
 
330
        return self.bzrdir.is_control_filename(filename)
320
331
 
321
332
    @staticmethod
322
333
    def open(path=None, _unsupported=False):
423
434
        """
424
435
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
425
436
 
426
 
    def relpath(self, abs):
427
 
        """Return the local path portion from a given absolute path."""
428
 
        return relpath(self.basedir, abs)
 
437
    def relpath(self, path):
 
438
        """Return the local path portion from a given path.
 
439
        
 
440
        The path may be absolute or relative. If its a relative path it is 
 
441
        interpreted relative to the python current working directory.
 
442
        """
 
443
        return relpath(self.basedir, path)
429
444
 
430
445
    def has_filename(self, filename):
431
446
        return bzrlib.osutils.lexists(self.abspath(filename))
588
603
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
589
604
 
590
605
            if file_id is None:
591
 
                file_id = gen_file_id(f)
592
 
            inv.add_path(f, kind=kind, file_id=file_id)
 
606
                inv.add_path(f, kind=kind)
 
607
            else:
 
608
                inv.add_path(f, kind=kind, file_id=file_id)
593
609
 
594
 
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
595
610
        self._write_inventory(inv)
596
611
 
597
612
    @needs_write_lock
966
981
                subp = appendpath(path, subf)
967
982
                yield subp
968
983
 
 
984
    def _translate_ignore_rule(self, rule):
 
985
        """Translate a single ignore rule to a regex.
 
986
 
 
987
        There are two types of ignore rules.  Those that do not contain a / are
 
988
        matched against the tail of the filename (that is, they do not care
 
989
        what directory the file is in.)  Rules which do contain a slash must
 
990
        match the entire path.  As a special case, './' at the start of the
 
991
        string counts as a slash in the string but is removed before matching
 
992
        (e.g. ./foo.c, ./src/foo.c)
 
993
 
 
994
        :return: The translated regex.
 
995
        """
 
996
        if rule[:2] in ('./', '.\\'):
 
997
            # rootdir rule
 
998
            result = fnmatch.translate(rule[2:])
 
999
        elif '/' in rule or '\\' in rule:
 
1000
            # path prefix 
 
1001
            result = fnmatch.translate(rule)
 
1002
        else:
 
1003
            # default rule style.
 
1004
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
 
1005
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
 
1006
        return "(" + result + ")"
 
1007
 
 
1008
    def _combine_ignore_rules(self, rules):
 
1009
        """Combine a list of ignore rules into a single regex object.
 
1010
 
 
1011
        Each individual rule is combined with | to form a big regex, which then
 
1012
        has $ added to it to form something like ()|()|()$. The group index for
 
1013
        each subregex's outermost group is placed in a dictionary mapping back 
 
1014
        to the rule. This allows quick identification of the matching rule that
 
1015
        triggered a match.
 
1016
        :return: a list of the compiled regex and the matching-group index 
 
1017
        dictionaries. We return a list because python complains if you try to 
 
1018
        combine more than 100 regexes.
 
1019
        """
 
1020
        result = []
 
1021
        groups = {}
 
1022
        next_group = 0
 
1023
        translated_rules = []
 
1024
        for rule in rules:
 
1025
            translated_rule = self._translate_ignore_rule(rule)
 
1026
            compiled_rule = re.compile(translated_rule)
 
1027
            groups[next_group] = rule
 
1028
            next_group += compiled_rule.groups
 
1029
            translated_rules.append(translated_rule)
 
1030
            if next_group == 99:
 
1031
                result.append((re.compile("|".join(translated_rules)), groups))
 
1032
                groups = {}
 
1033
                next_group = 0
 
1034
                translated_rules = []
 
1035
        if len(translated_rules):
 
1036
            result.append((re.compile("|".join(translated_rules)), groups))
 
1037
        return result
969
1038
 
970
1039
    def ignored_files(self):
971
1040
        """Yield list of PATH, IGNORE_PATTERN"""
974
1043
            if pat != None:
975
1044
                yield subp, pat
976
1045
 
977
 
 
978
1046
    def get_ignore_list(self):
979
1047
        """Return list of ignore patterns.
980
1048
 
988
1056
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
989
1057
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
990
1058
        self._ignorelist = l
 
1059
        self._ignore_regex = self._combine_ignore_rules(l)
991
1060
        return l
992
1061
 
 
1062
    def _get_ignore_rules_as_regex(self):
 
1063
        """Return a regex of the ignore rules and a mapping dict.
 
1064
 
 
1065
        :return: (ignore rules compiled regex, dictionary mapping rule group 
 
1066
        indices to original rule.)
 
1067
        """
 
1068
        if getattr(self, '_ignorelist', None) is None:
 
1069
            self.get_ignore_list()
 
1070
        return self._ignore_regex
993
1071
 
994
1072
    def is_ignored(self, filename):
995
1073
        r"""Check whether the filename matches an ignore pattern.
1009
1087
        # treat dotfiles correctly and allows * to match /.
1010
1088
        # Eventually it should be replaced with something more
1011
1089
        # accurate.
1012
 
        
1013
 
        for pat in self.get_ignore_list():
1014
 
            if '/' in pat or '\\' in pat:
1015
 
                
1016
 
                # as a special case, you can put ./ at the start of a
1017
 
                # pattern; this is good to match in the top-level
1018
 
                # only;
1019
 
                
1020
 
                if (pat[:2] == './') or (pat[:2] == '.\\'):
1021
 
                    newpat = pat[2:]
1022
 
                else:
1023
 
                    newpat = pat
1024
 
                if fnmatch.fnmatchcase(filename, newpat):
1025
 
                    return pat
1026
 
            else:
1027
 
                if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1028
 
                    return pat
1029
 
        else:
1030
 
            return None
 
1090
    
 
1091
        rules = self._get_ignore_rules_as_regex()
 
1092
        for regex, mapping in rules:
 
1093
            match = regex.match(filename)
 
1094
            if match is not None:
 
1095
                # one or more of the groups in mapping will have a non-None group 
 
1096
                # match.
 
1097
                groups = match.groups()
 
1098
                rules = [mapping[group] for group in 
 
1099
                    mapping if groups[group] is not None]
 
1100
                return rules[0]
 
1101
        return None
1031
1102
 
1032
1103
    def kind(self, file_id):
1033
1104
        return file_kind(self.id2abspath(file_id))