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
43
42
from copy import deepcopy
44
43
from cStringIO import StringIO
52
50
from bzrlib.atomicfile import AtomicFile
53
51
from bzrlib.branch import (Branch,
103
101
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)
133
104
def gen_file_id(name):
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()
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))
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.
323
:param filename: A filename within the tree. This is a relative path
324
from the root of this tree.
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.
330
return self.bzrdir.is_control_filename(filename)
316
self.bzrdir.transport.relpath(self.abspath(filename))
318
except errors.PathNotChild:
333
322
def open(path=None, _unsupported=False):
435
424
return bzrdir.BzrDir.create_standalone_workingtree(directory)
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)
426
def relpath(self, abs):
427
"""Return the local path portion from a given absolute path."""
428
return relpath(self.basedir, abs)
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))
605
590
if file_id is None:
606
inv.add_path(f, kind=kind)
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)
594
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
610
595
self._write_inventory(inv)
612
597
@needs_write_lock
981
966
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))
1039
970
def ignored_files(self):
1040
971
"""Yield list of PATH, IGNORE_PATTERN"""
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)
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
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
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]
1013
basename = splitpath(filename)[-1]
1014
for pat in self.get_ignore_list():
1015
if '/' in pat or '\\' in pat:
1017
# as a special case, you can put ./ at the start of a
1018
# pattern; this is good to match in the top-level
1020
if pat[:2] in ('./', '.\\'):
1024
if fnmatch.fnmatchcase(filename, newpat):
1027
if fnmatch.fnmatchcase(basename, pat):
1103
1031
def kind(self, file_id):