103
102
from bzrlib.transport.local import LocalTransport
104
103
import bzrlib.tree
105
104
from bzrlib.progress import DummyProgress, ProgressPhase
106
from bzrlib.revision import NULL_REVISION
105
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
107
106
import bzrlib.revisiontree
108
107
from bzrlib.rio import RioReader, rio_file, Stanza
109
108
from bzrlib.symbol_versioning import (deprecated_passed,
469
468
def get_file_byname(self, filename):
470
469
return file(self.abspath(filename), 'rb')
471
def annotate_iter(self, file_id):
472
"""See Tree.annotate_iter
474
This implementation will use the basis tree implementation if possible.
475
Lines not in the basis are attributed to CURRENT_REVISION
477
If there are pending merges, lines added by those merges will be
478
incorrectly attributed to CURRENT_REVISION (but after committing, the
479
attribution will be correct).
481
basis = self.basis_tree()
482
changes = self._iter_changes(basis, True, [file_id]).next()
483
changed_content, kind = changes[2], changes[6]
484
if not changed_content:
485
return basis.annotate_iter(file_id)
489
if kind[0] != 'file':
492
old_lines = list(basis.annotate_iter(file_id))
494
for tree in self.branch.repository.revision_trees(
495
self.get_parent_ids()[1:]):
496
if file_id not in tree:
498
old.append(list(tree.annotate_iter(file_id)))
499
return annotate.reannotate(old, self.get_file(file_id).readlines(),
472
502
def get_parent_ids(self):
473
503
"""See Tree.get_parent_ids.
1223
1253
subp = pathjoin(path, subf)
1226
def _translate_ignore_rule(self, rule):
1227
"""Translate a single ignore rule to a regex.
1229
There are two types of ignore rules. Those that do not contain a / are
1230
matched against the tail of the filename (that is, they do not care
1231
what directory the file is in.) Rules which do contain a slash must
1232
match the entire path. As a special case, './' at the start of the
1233
string counts as a slash in the string but is removed before matching
1234
(e.g. ./foo.c, ./src/foo.c)
1236
:return: The translated regex.
1238
if rule[:2] in ('./', '.\\'):
1240
result = fnmatch.translate(rule[2:])
1241
elif '/' in rule or '\\' in rule:
1243
result = fnmatch.translate(rule)
1245
# default rule style.
1246
result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1247
assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1248
return "(" + result + ")"
1250
def _combine_ignore_rules(self, rules):
1251
"""Combine a list of ignore rules into a single regex object.
1253
Each individual rule is combined with | to form a big regex, which then
1254
has $ added to it to form something like ()|()|()$. The group index for
1255
each subregex's outermost group is placed in a dictionary mapping back
1256
to the rule. This allows quick identification of the matching rule that
1258
:return: a list of the compiled regex and the matching-group index
1259
dictionaries. We return a list because python complains if you try to
1260
combine more than 100 regexes.
1265
translated_rules = []
1267
translated_rule = self._translate_ignore_rule(rule)
1268
compiled_rule = re.compile(translated_rule)
1269
groups[next_group] = rule
1270
next_group += compiled_rule.groups
1271
translated_rules.append(translated_rule)
1272
if next_group == 99:
1273
result.append((re.compile("|".join(translated_rules)), groups))
1276
translated_rules = []
1277
if len(translated_rules):
1278
result.append((re.compile("|".join(translated_rules)), groups))
1281
1257
def ignored_files(self):
1282
1258
"""Yield list of PATH, IGNORE_PATTERN"""
1297
1273
ignore_globs = set(bzrlib.DEFAULT_IGNORE)
1298
1274
ignore_globs.update(ignores.get_runtime_ignores())
1300
1275
ignore_globs.update(ignores.get_user_ignores())
1302
1276
if self.has_filename(bzrlib.IGNORE_FILENAME):
1303
1277
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1305
1279
ignore_globs.update(ignores.parse_ignore_file(f))
1309
1282
self._ignoreset = ignore_globs
1310
self._ignore_regex = self._combine_ignore_rules(ignore_globs)
1311
1283
return ignore_globs
1313
def _get_ignore_rules_as_regex(self):
1314
"""Return a regex of the ignore rules and a mapping dict.
1316
:return: (ignore rules compiled regex, dictionary mapping rule group
1317
indices to original rule.)
1319
if getattr(self, '_ignoreset', None) is None:
1320
self.get_ignore_list()
1321
return self._ignore_regex
1285
def _flush_ignore_list_cache(self):
1286
"""Resets the cached ignore list to force a cache rebuild."""
1287
self._ignoreset = None
1288
self._ignoreglobster = None
1323
1290
def is_ignored(self, filename):
1324
1291
r"""Check whether the filename matches an ignore pattern.
1329
1296
If the file is ignored, returns the pattern which caused it to
1330
1297
be ignored, otherwise None. So this can simply be used as a
1331
1298
boolean if desired."""
1333
# TODO: Use '**' to match directories, and other extended
1334
# globbing stuff from cvs/rsync.
1336
# XXX: fnmatch is actually not quite what we want: it's only
1337
# approximately the same as real Unix fnmatch, and doesn't
1338
# treat dotfiles correctly and allows * to match /.
1339
# Eventually it should be replaced with something more
1342
rules = self._get_ignore_rules_as_regex()
1343
for regex, mapping in rules:
1344
match = regex.match(filename)
1345
if match is not None:
1346
# one or more of the groups in mapping will have a non-None
1348
groups = match.groups()
1349
rules = [mapping[group] for group in
1350
mapping if groups[group] is not None]
1299
if getattr(self, '_ignoreglobster', None) is None:
1300
self._ignoreglobster = globbing.Globster(self.get_ignore_list())
1301
return self._ignoreglobster.match(filename)
1354
1303
def kind(self, file_id):
1355
1304
return file_kind(self.id2abspath(file_id))