~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

Improved help for remerge and merge

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
43
42
from copy import deepcopy
44
43
from cStringIO import StringIO
45
44
import errno
46
45
import fnmatch
47
46
import os
48
 
import re
49
47
import stat
50
 
from time import time
 
48
 
51
49
 
52
50
from bzrlib.atomicfile import AtomicFile
53
51
from bzrlib.branch import (Branch,
82
80
                            pumpfile,
83
81
                            safe_unicode,
84
82
                            splitpath,
85
 
                            rand_chars,
 
83
                            rand_bytes,
86
84
                            normpath,
87
85
                            realpath,
88
86
                            relpath,
103
101
import bzrlib.xml5
104
102
 
105
103
 
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
 
 
133
104
def gen_file_id(name):
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()
 
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))
143
131
 
144
132
 
145
133
def gen_root_id():
320
308
    def is_control_filename(self, filename):
321
309
        """True if filename is the name of a control file in this tree.
322
310
        
323
 
        :param filename: A filename within the tree. This is a relative path
324
 
        from the root of this tree.
325
 
 
326
311
        This is true IF and ONLY IF the filename is part of the meta data
327
312
        that bzr controls in this tree. I.E. a random .bzr directory placed
328
313
        on disk will not be a control file for this tree.
329
314
        """
330
 
        return self.bzrdir.is_control_filename(filename)
 
315
        try:
 
316
            self.bzrdir.transport.relpath(self.abspath(filename))
 
317
            return True
 
318
        except errors.PathNotChild:
 
319
            return False
331
320
 
332
321
    @staticmethod
333
322
    def open(path=None, _unsupported=False):
434
423
        """
435
424
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
436
425
 
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)
 
426
    def relpath(self, abs):
 
427
        """Return the local path portion from a given absolute path."""
 
428
        return relpath(self.basedir, abs)
444
429
 
445
430
    def has_filename(self, filename):
446
431
        return bzrlib.osutils.lexists(self.abspath(filename))
603
588
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
604
589
 
605
590
            if file_id is None:
606
 
                inv.add_path(f, kind=kind)
607
 
            else:
608
 
                inv.add_path(f, kind=kind, file_id=file_id)
 
591
                file_id = gen_file_id(f)
 
592
            inv.add_path(f, kind=kind, file_id=file_id)
609
593
 
 
594
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
610
595
        self._write_inventory(inv)
611
596
 
612
597
    @needs_write_lock
981
966
                subp = appendpath(path, subf)
982
967
                yield subp
983
968
 
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
1038
969
 
1039
970
    def ignored_files(self):
1040
971
        """Yield list of PATH, IGNORE_PATTERN"""
1043
974
            if pat != None:
1044
975
                yield subp, pat
1045
976
 
 
977
 
1046
978
    def get_ignore_list(self):
1047
979
        """Return list of ignore patterns.
1048
980
 
1056
988
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1057
989
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
1058
990
        self._ignorelist = l
1059
 
        self._ignore_regex = self._combine_ignore_rules(l)
1060
991
        return l
1061
992
 
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
1071
993
 
1072
994
    def is_ignored(self, filename):
1073
995
        r"""Check whether the filename matches an ignore pattern.
1087
1009
        # treat dotfiles correctly and allows * to match /.
1088
1010
        # Eventually it should be replaced with something more
1089
1011
        # accurate.
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]
 
1012
        
 
1013
        basename = splitpath(filename)[-1]
 
1014
        for pat in self.get_ignore_list():
 
1015
            if '/' in pat or '\\' in pat:
 
1016
                
 
1017
                # as a special case, you can put ./ at the start of a
 
1018
                # pattern; this is good to match in the top-level
 
1019
                # only;
 
1020
                if pat[:2] in ('./', '.\\'):
 
1021
                    newpat = pat[2:]
 
1022
                else:
 
1023
                    newpat = pat
 
1024
                if fnmatch.fnmatchcase(filename, newpat):
 
1025
                    return pat
 
1026
            else:
 
1027
                if fnmatch.fnmatchcase(basename, pat):
 
1028
                    return pat
1101
1029
        return None
1102
1030
 
1103
1031
    def kind(self, file_id):