39
39
# At the moment they may alias the inventory and have old copies of it in
40
40
# memory. (Now done? -- mbp 20060309)
42
from binascii import hexlify
42
43
from copy import deepcopy
43
44
from cStringIO import StringIO
50
52
from bzrlib.atomicfile import AtomicFile
51
53
from bzrlib.branch import (Branch,
101
103
import bzrlib.xml5
106
# the regex here does the following:
107
# 1) remove any weird characters; we don't escape them but rather
109
# 2) match leading '.'s to make it not hidden
110
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
111
_gen_id_suffix = None
115
def _next_id_suffix():
116
"""Create a new file id suffix that is reasonably unique.
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.
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))
130
return _gen_id_suffix + str(_gen_id_serial)
104
133
def gen_file_id(name):
105
"""Return new file id.
107
This should probably generate proper UUIDs, but for the moment we
108
cope with just randomness because running uuidgen every time is
111
from binascii import hexlify
112
from time import time
115
idx = name.rfind('/')
117
name = name[idx+1 : ]
118
idx = name.rfind('\\')
120
name = name[idx+1 : ]
122
# make it not a hidden file
123
name = name.lstrip('.')
125
# remove any wierd characters; we don't escape them but rather
127
name = re.sub(r'[^\w.]', '', name)
129
s = hexlify(rand_bytes(8))
130
return '-'.join((name, compact_date(time()), s))
134
"""Return new file id for the basename 'name'.
136
The uniqueness is supplied from _next_id_suffix.
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()
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.
323
:param filename: A filename within the tree. This is a relative path
324
from the root of this tree.
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.
316
self.bzrdir.transport.relpath(self.abspath(filename))
318
except errors.PathNotChild:
330
return self.bzrdir.is_control_filename(filename)
322
333
def open(path=None, _unsupported=False):
424
435
return bzrdir.BzrDir.create_standalone_workingtree(directory)
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.
440
The path may be absolute or relative. If its a relative path it is
441
interpreted relative to the python current working directory.
443
return relpath(self.basedir, path)
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))
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)
608
inv.add_path(f, kind=kind, file_id=file_id)
594
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
595
610
self._write_inventory(inv)
597
612
@needs_write_lock
966
981
subp = appendpath(path, subf)
984
def _translate_ignore_rule(self, rule):
985
"""Translate a single ignore rule to a regex.
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)
994
:return: The translated regex.
996
if rule[:2] in ('./', '.\\'):
998
result = fnmatch.translate(rule[2:])
999
elif '/' in rule or '\\' in rule:
1001
result = fnmatch.translate(rule)
1003
# default rule style.
1004
result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1005
assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1006
return "(" + result + ")"
1008
def _combine_ignore_rules(self, rules):
1009
"""Combine a list of ignore rules into a single regex object.
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
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.
1023
translated_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))
1034
translated_rules = []
1035
if len(translated_rules):
1036
result.append((re.compile("|".join(translated_rules)), groups))
970
1039
def ignored_files(self):
971
1040
"""Yield list of PATH, IGNORE_PATTERN"""
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)
1062
def _get_ignore_rules_as_regex(self):
1063
"""Return a regex of the ignore rules and a mapping dict.
1065
:return: (ignore rules compiled regex, dictionary mapping rule group
1066
indices to original rule.)
1068
if getattr(self, '_ignorelist', None) is None:
1069
self.get_ignore_list()
1070
return self._ignore_regex
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
1013
for pat in self.get_ignore_list():
1014
if '/' in pat or '\\' in pat:
1016
# as a special case, you can put ./ at the start of a
1017
# pattern; this is good to match in the top-level
1020
if (pat[:2] == './') or (pat[:2] == '.\\'):
1024
if fnmatch.fnmatchcase(filename, newpat):
1027
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
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
1097
groups = match.groups()
1098
rules = [mapping[group] for group in
1099
mapping if groups[group] is not None]
1032
1103
def kind(self, file_id):
1033
1104
return file_kind(self.id2abspath(file_id))