39
36
# At the moment they may alias the inventory and have old copies of it in
40
37
# memory. (Now done? -- mbp 20060309)
42
from binascii import hexlify
39
from cStringIO import StringIO
42
from bzrlib.lazy_import import lazy_import
43
lazy_import(globals(), """
44
45
from copy import deepcopy
45
from cStringIO import StringIO
51
48
from time import time
55
52
from bzrlib import (
54
conflicts as _mod_conflicts,
63
from bzrlib.atomicfile import AtomicFile
64
69
import bzrlib.branch
65
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
70
from bzrlib.transport import get_transport
74
from bzrlib import symbol_versioning
66
75
from bzrlib.decorators import needs_read_lock, needs_write_lock
67
76
from bzrlib.errors import (BzrCheckError,
77
86
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
78
87
from bzrlib.lockable_files import LockableFiles, TransportLock
79
88
from bzrlib.lockdir import LockDir
80
from bzrlib.merge import merge_inner, transform_tree
81
89
import bzrlib.mutabletree
82
90
from bzrlib.mutabletree import needs_tree_write_lock
83
91
from bzrlib.osutils import (
103
from bzrlib.trace import mutter, note
104
from bzrlib.transport.local import LocalTransport
100
105
from bzrlib.progress import DummyProgress, ProgressPhase
101
from bzrlib.revision import NULL_REVISION
102
import bzrlib.revisiontree
106
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
103
107
from bzrlib.rio import RioReader, rio_file, Stanza
104
108
from bzrlib.symbol_versioning import (deprecated_passed,
105
109
deprecated_method,
107
111
DEPRECATED_PARAMETER,
111
from bzrlib.trace import mutter, note
112
from bzrlib.transform import build_tree
113
from bzrlib.transport import get_transport
114
from bzrlib.transport.local import LocalTransport
115
from bzrlib.textui import show_status
120
# the regex removes any weird characters; we don't escape them
121
# but rather just pull them out
122
_gen_file_id_re = re.compile(r'[^\w.]')
123
_gen_id_suffix = None
127
def _next_id_suffix():
128
"""Create a new file id suffix that is reasonably unique.
130
On the first call we combine the current time with 64 bits of randomness
131
to give a highly probably globally unique number. Then each call in the same
132
process adds 1 to a serial number we append to that unique value.
134
# XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather
135
# than having to move the id randomness out of the inner loop like this.
136
# XXX TODO: for the global randomness this uses we should add the thread-id
137
# before the serial #.
138
global _gen_id_suffix, _gen_id_serial
139
if _gen_id_suffix is None:
140
_gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
142
return _gen_id_suffix + str(_gen_id_serial)
118
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
119
CONFLICT_HEADER_1 = "BZR conflict list format 1"
122
@deprecated_function(zero_thirteen)
145
123
def gen_file_id(name):
146
124
"""Return new file id for the basename 'name'.
148
The uniqueness is supplied from _next_id_suffix.
126
Use bzrlib.generate_ids.gen_file_id() instead
150
# The real randomness is in the _next_id_suffix, the
151
# rest of the identifier is just to be nice.
153
# 1) Remove non-ascii word characters to keep the ids portable
154
# 2) squash to lowercase, so the file id doesn't have to
155
# be escaped (case insensitive filesystems would bork for ids
156
# that only differred in case without escaping).
157
# 3) truncate the filename to 20 chars. Long filenames also bork on some
159
# 4) Removing starting '.' characters to prevent the file ids from
160
# being considered hidden.
161
ascii_word_only = _gen_file_id_re.sub('', name.lower())
162
short_no_dots = ascii_word_only.lstrip('.')[:20]
163
return short_no_dots + _next_id_suffix()
128
return generate_ids.gen_file_id(name)
131
@deprecated_function(zero_thirteen)
166
132
def gen_root_id():
167
"""Return a new tree-root file id."""
168
return gen_file_id('TREE_ROOT')
133
"""Return a new tree-root file id.
135
This has been deprecated in favor of bzrlib.generate_ids.gen_root_id()
137
return generate_ids.gen_root_id()
171
140
class TreeEntry(object):
263
232
self._set_inventory(wt._inventory, dirty=False)
264
233
self._format = wt._format
265
234
self.bzrdir = wt.bzrdir
266
from bzrlib.hashcache import HashCache
267
from bzrlib.trace import note, mutter
268
235
assert isinstance(basedir, basestring), \
269
236
"base directory %r is not a string" % basedir
270
237
basedir = safe_unicode(basedir)
298
265
# cache file, and have the parser take the most recent entry for a
299
266
# given path only.
300
267
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
301
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
268
self._hashcache = hashcache.HashCache(basedir, cache_filename,
269
self._control_files._file_mode)
303
272
# is this scan needed ? it makes things kinda slow.
411
380
If the left most parent is a ghost then the returned tree will be an
412
381
empty tree - one obtained by calling repository.revision_tree(None).
414
revision_id = self.last_revision()
415
if revision_id is None:
384
revision_id = self.get_parent_ids()[0]
416
386
# no parents, return an empty revision tree.
417
387
# in the future this should return the tree for
418
388
# 'empty:' - the implicit root empty tree.
479
449
The path may be absolute or relative. If its a relative path it is
480
450
interpreted relative to the python current working directory.
482
return relpath(self.basedir, path)
452
return osutils.relpath(self.basedir, path)
484
454
def has_filename(self, filename):
485
455
return osutils.lexists(self.abspath(filename))
493
463
def get_file_byname(self, filename):
494
464
return file(self.abspath(filename), 'rb')
466
def annotate_iter(self, file_id):
467
"""See Tree.annotate_iter
469
This implementation will use the basis tree implementation if possible.
470
Lines not in the basis are attributed to CURRENT_REVISION
472
If there are pending merges, lines added by those merges will be
473
incorrectly attributed to CURRENT_REVISION (but after committing, the
474
attribution will be correct).
476
basis = self.basis_tree()
477
changes = self._iter_changes(basis, True, [file_id]).next()
478
changed_content, kind = changes[2], changes[6]
479
if not changed_content:
480
return basis.annotate_iter(file_id)
484
if kind[0] != 'file':
487
old_lines = list(basis.annotate_iter(file_id))
489
for tree in self.branch.repository.revision_trees(
490
self.get_parent_ids()[1:]):
491
if file_id not in tree:
493
old.append(list(tree.annotate_iter(file_id)))
494
return annotate.reannotate(old, self.get_file(file_id).readlines(),
496
497
def get_parent_ids(self):
497
498
"""See Tree.get_parent_ids.
549
550
def copy_content_into(self, tree, revision_id=None):
550
551
"""Copy the current content and user files of this tree into tree."""
552
tree.set_root_id(self.get_root_id())
551
553
if revision_id is None:
552
transform_tree(tree, self)
554
merge.transform_tree(tree, self)
554
556
# TODO now merge from tree.last_revision to revision (to preserve
555
557
# user local changes)
556
transform_tree(tree, self)
558
merge.transform_tree(tree, self)
557
559
tree.set_parent_ids([revision_id])
559
561
def id2abspath(self, file_id):
578
580
return os.path.getsize(self.id2abspath(file_id))
581
def get_file_sha1(self, file_id, path=None):
583
def get_file_sha1(self, file_id, path=None, stat_value=None):
583
585
path = self._inventory.id2path(file_id)
584
return self._hashcache.get_sha1(path)
586
return self._hashcache.get_sha1(path, stat_value)
586
588
def get_file_mtime(self, file_id, path=None):
843
845
def mkdir(self, path, file_id=None):
844
846
"""See MutableTree.mkdir()."""
845
847
if file_id is None:
846
file_id = gen_file_id(os.path.basename(path))
848
file_id = generate_ids.gen_file_id(os.path.basename(path))
847
849
os.mkdir(self.abspath(path))
848
850
self.add(path, file_id, 'directory')
865
867
if self._control_files._lock_mode != 'w':
866
868
raise errors.NotWriteLocked(self)
868
bzrlib.xml5.serializer_v5.write_inventory(self._inventory, sio)
870
xml5.serializer_v5.write_inventory(self._inventory, sio)
870
872
self._control_files.put('inventory', sio)
871
873
self._inventory_is_modified = False
1041
1043
result.append((f, dest_path))
1042
1044
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1044
rename(self.abspath(f), self.abspath(dest_path))
1046
osutils.rename(self.abspath(f), self.abspath(dest_path))
1045
1047
except OSError, e:
1046
1048
raise BzrError("failed to rename %r to %r: %s" %
1047
(f, dest_path, e[1]),
1048
["rename rolled back"])
1049
(f, dest_path, e[1]))
1050
1051
# restore the inventory on error
1051
1052
self._set_inventory(orig_inv, dirty=original_modified)
1093
1094
from_abs = self.abspath(from_rel)
1094
1095
to_abs = self.abspath(to_rel)
1096
rename(from_abs, to_abs)
1097
osutils.rename(from_abs, to_abs)
1097
1098
except OSError, e:
1098
1099
inv.rename(file_id, from_parent, from_name)
1099
1100
raise BzrError("failed to rename %r to %r: %s"
1100
% (from_abs, to_abs, e[1]),
1101
["rename rolled back"])
1101
% (from_abs, to_abs, e[1]))
1102
1102
self._write_inventory(inv)
1104
1104
@needs_read_lock
1244
1248
subp = pathjoin(path, subf)
1247
def _translate_ignore_rule(self, rule):
1248
"""Translate a single ignore rule to a regex.
1250
There are two types of ignore rules. Those that do not contain a / are
1251
matched against the tail of the filename (that is, they do not care
1252
what directory the file is in.) Rules which do contain a slash must
1253
match the entire path. As a special case, './' at the start of the
1254
string counts as a slash in the string but is removed before matching
1255
(e.g. ./foo.c, ./src/foo.c)
1257
:return: The translated regex.
1259
if rule[:2] in ('./', '.\\'):
1261
result = fnmatch.translate(rule[2:])
1262
elif '/' in rule or '\\' in rule:
1264
result = fnmatch.translate(rule)
1266
# default rule style.
1267
result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1268
assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1269
return "(" + result + ")"
1271
def _combine_ignore_rules(self, rules):
1272
"""Combine a list of ignore rules into a single regex object.
1274
Each individual rule is combined with | to form a big regex, which then
1275
has $ added to it to form something like ()|()|()$. The group index for
1276
each subregex's outermost group is placed in a dictionary mapping back
1277
to the rule. This allows quick identification of the matching rule that
1279
:return: a list of the compiled regex and the matching-group index
1280
dictionaries. We return a list because python complains if you try to
1281
combine more than 100 regexes.
1286
translated_rules = []
1288
translated_rule = self._translate_ignore_rule(rule)
1289
compiled_rule = re.compile(translated_rule)
1290
groups[next_group] = rule
1291
next_group += compiled_rule.groups
1292
translated_rules.append(translated_rule)
1293
if next_group == 99:
1294
result.append((re.compile("|".join(translated_rules)), groups))
1297
translated_rules = []
1298
if len(translated_rules):
1299
result.append((re.compile("|".join(translated_rules)), groups))
1302
1252
def ignored_files(self):
1303
1253
"""Yield list of PATH, IGNORE_PATTERN"""
1318
1268
ignore_globs = set(bzrlib.DEFAULT_IGNORE)
1319
1269
ignore_globs.update(ignores.get_runtime_ignores())
1321
1270
ignore_globs.update(ignores.get_user_ignores())
1323
1271
if self.has_filename(bzrlib.IGNORE_FILENAME):
1324
1272
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1326
1274
ignore_globs.update(ignores.parse_ignore_file(f))
1330
1277
self._ignoreset = ignore_globs
1331
self._ignore_regex = self._combine_ignore_rules(ignore_globs)
1332
1278
return ignore_globs
1334
def _get_ignore_rules_as_regex(self):
1335
"""Return a regex of the ignore rules and a mapping dict.
1337
:return: (ignore rules compiled regex, dictionary mapping rule group
1338
indices to original rule.)
1340
if getattr(self, '_ignoreset', None) is None:
1341
self.get_ignore_list()
1342
return self._ignore_regex
1280
def _flush_ignore_list_cache(self):
1281
"""Resets the cached ignore list to force a cache rebuild."""
1282
self._ignoreset = None
1283
self._ignoreglobster = None
1344
1285
def is_ignored(self, filename):
1345
1286
r"""Check whether the filename matches an ignore pattern.
1350
1291
If the file is ignored, returns the pattern which caused it to
1351
1292
be ignored, otherwise None. So this can simply be used as a
1352
1293
boolean if desired."""
1354
# TODO: Use '**' to match directories, and other extended
1355
# globbing stuff from cvs/rsync.
1357
# XXX: fnmatch is actually not quite what we want: it's only
1358
# approximately the same as real Unix fnmatch, and doesn't
1359
# treat dotfiles correctly and allows * to match /.
1360
# Eventually it should be replaced with something more
1363
rules = self._get_ignore_rules_as_regex()
1364
for regex, mapping in rules:
1365
match = regex.match(filename)
1366
if match is not None:
1367
# one or more of the groups in mapping will have a non-None
1369
groups = match.groups()
1370
rules = [mapping[group] for group in
1371
mapping if groups[group] is not None]
1294
if getattr(self, '_ignoreglobster', None) is None:
1295
self._ignoreglobster = globbing.Globster(self.get_ignore_list())
1296
return self._ignoreglobster.match(filename)
1375
1298
def kind(self, file_id):
1376
1299
return file_kind(self.id2abspath(file_id))
1301
def _comparison_data(self, entry, path):
1302
abspath = self.abspath(path)
1304
stat_value = os.lstat(abspath)
1306
if getattr(e, 'errno', None) == errno.ENOENT:
1313
mode = stat_value.st_mode
1314
kind = osutils.file_kind_from_stat_mode(mode)
1315
if not supports_executable():
1316
executable = entry.executable
1318
executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1319
return kind, executable, stat_value
1321
def _file_size(self, entry, stat_value):
1322
return stat_value.st_size
1378
1324
def last_revision(self):
1379
1325
"""Return the last revision of the branch for this tree.
1458
1404
def _create_basis_xml_from_inventory(self, revision_id, inventory):
1459
1405
"""Create the text that will be saved in basis-inventory"""
1460
1406
inventory.revision_id = revision_id
1461
return bzrlib.xml6.serializer_v6.write_inventory_to_string(inventory)
1407
return xml6.serializer_v6.write_inventory_to_string(inventory)
1463
1409
def _cache_basis_inventory(self, new_revision):
1464
1410
"""Cache new_revision as the basis inventory."""
1496
1442
def read_working_inventory(self):
1497
1443
"""Read the working inventory.
1499
:raises errors.InventoryModified: When the current in memory
1500
inventory has been modified, read_working_inventory will
1445
:raises errors.InventoryModified: read_working_inventory will fail
1446
when the current in memory inventory has been modified.
1503
1448
# conceptually this should be an implementation detail of the tree.
1504
1449
# XXX: Deprecate this.
1507
1452
if self._inventory_is_modified:
1508
1453
raise errors.InventoryModified(self)
1509
result = bzrlib.xml5.serializer_v5.read_inventory(
1454
result = xml5.serializer_v5.read_inventory(
1510
1455
self._control_files.get('inventory'))
1511
1456
self._set_inventory(result, dirty=False)
1554
1500
@needs_tree_write_lock
1555
1501
def revert(self, filenames, old_tree=None, backups=True,
1556
1502
pb=DummyProgress()):
1557
from transform import revert
1558
from conflicts import resolve
1503
from bzrlib.conflicts import resolve
1559
1504
if old_tree is None:
1560
1505
old_tree = self.basis_tree()
1561
conflicts = revert(self, old_tree, filenames, backups, pb)
1506
conflicts = transform.revert(self, old_tree, filenames, backups, pb)
1562
1507
if not len(filenames):
1563
1508
self.set_parent_ids(self.get_parent_ids()[:1])
1583
1528
# dont use the repository revision_tree api because we want
1584
1529
# to supply the inventory.
1585
1530
if inv.revision_id == revision_id:
1586
return bzrlib.tree.RevisionTree(self.branch.repository,
1531
return revisiontree.RevisionTree(self.branch.repository,
1587
1532
inv, revision_id)
1588
1533
except errors.BadInventoryFormat:
1656
1601
raise NotImplementedError(self.unlock)
1659
1603
def update(self):
1660
1604
"""Update a working tree along its branch.
1662
This will update the branch if its bound too, which means we have multiple trees involved:
1663
The new basis tree of the master.
1664
The old basis tree of the branch.
1665
The old basis tree of the working tree.
1666
The current working tree state.
1667
pathologically all three may be different, and non ancestors of each other.
1668
Conceptually we want to:
1669
Preserve the wt.basis->wt.state changes
1670
Transform the wt.basis to the new master basis.
1671
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1672
Restore the wt.basis->wt.state changes.
1606
This will update the branch if its bound too, which means we have
1607
multiple trees involved:
1609
- The new basis tree of the master.
1610
- The old basis tree of the branch.
1611
- The old basis tree of the working tree.
1612
- The current working tree state.
1614
Pathologically, all three may be different, and non-ancestors of each
1615
other. Conceptually we want to:
1617
- Preserve the wt.basis->wt.state changes
1618
- Transform the wt.basis to the new master basis.
1619
- Apply a merge of the old branch basis to get any 'local' changes from
1621
- Restore the wt.basis->wt.state changes.
1674
1623
There isn't a single operation at the moment to do that, so we:
1675
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1676
Do a 'normal' merge of the old branch basis if it is relevant.
1678
old_tip = self.branch.update()
1624
- Merge current state -> basis tree of the master w.r.t. the old tree
1626
- Do a 'normal' merge of the old branch basis if it is relevant.
1628
if self.branch.get_master_branch() is not None:
1630
update_branch = True
1632
self.lock_tree_write()
1633
update_branch = False
1636
old_tip = self.branch.update()
1639
return self._update_tree(old_tip)
1643
@needs_tree_write_lock
1644
def _update_tree(self, old_tip=None):
1645
"""Update a tree to the master branch.
1647
:param old_tip: if supplied, the previous tip revision the branch,
1648
before it was changed to the master branch's tip.
1679
1650
# here if old_tip is not None, it is the old tip of the branch before
1680
1651
# it was updated from the master branch. This should become a pending
1681
1652
# merge in the working tree to preserve the user existing work. we
1695
1666
# merge tree state up to new branch tip.
1696
1667
basis = self.basis_tree()
1697
1668
to_tree = self.branch.basis_tree()
1698
result += merge_inner(self.branch,
1669
if basis.inventory.root is None:
1670
self.set_root_id(to_tree.inventory.root.file_id)
1671
result += merge.merge_inner(
1701
1675
this_tree=self)
1736
1710
base_rev_id = None
1737
1711
base_tree = self.branch.repository.revision_tree(base_rev_id)
1738
1712
other_tree = self.branch.repository.revision_tree(old_tip)
1739
result += merge_inner(self.branch,
1713
result += merge.merge_inner(
1742
1717
this_tree=self)
1720
def _write_hashcache_if_dirty(self):
1721
"""Write out the hashcache if it is dirty."""
1722
if self._hashcache.needs_write:
1724
self._hashcache.write()
1726
if e.errno not in (errno.EPERM, errno.EACCES):
1728
# TODO: jam 20061219 Should this be a warning? A single line
1729
# warning might be sufficient to let the user know what
1731
mutter('Could not write hashcache for %s\nError: %s',
1732
self._hashcache.cache_file_name(), e)
1745
1734
@needs_tree_write_lock
1746
1735
def _write_inventory(self, inv):
1747
1736
"""Write inventory as the current inventory."""
1855
1845
def add_conflicts(self, new_conflicts):
1856
1846
conflict_set = set(self.conflicts())
1857
1847
conflict_set.update(set(list(new_conflicts)))
1858
self.set_conflicts(ConflictList(sorted(conflict_set,
1859
key=Conflict.sort_key)))
1848
self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
1849
key=_mod_conflicts.Conflict.sort_key)))
1861
1851
@needs_read_lock
1862
1852
def conflicts(self):
1864
1854
confile = self._control_files.get('conflicts')
1865
1855
except NoSuchFile:
1866
return ConflictList()
1856
return _mod_conflicts.ConflictList()
1868
1858
if confile.next() != CONFLICT_HEADER_1 + '\n':
1869
1859
raise ConflictFormatError()
1870
1860
except StopIteration:
1871
1861
raise ConflictFormatError()
1872
return ConflictList.from_stanzas(RioReader(confile))
1862
return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
1874
1864
def unlock(self):
1875
1865
if self._control_files._lock_count == 1:
1876
1866
# _inventory_is_modified is always False during a read lock.
1877
1867
if self._inventory_is_modified:
1879
if self._hashcache.needs_write:
1880
self._hashcache.write()
1869
self._write_hashcache_if_dirty()
1881
1870
# reverse order of locking.
1883
1872
return self._control_files.unlock()
2029
2018
_internal=True,
2031
2020
_bzrdir=a_bzrdir)
2032
wt.set_root_id(inv.root.file_id)
2033
2021
basis_tree = branch.repository.revision_tree(revision)
2022
if basis_tree.inventory.root is not None:
2023
wt.set_root_id(basis_tree.inventory.root.file_id)
2024
# set the parent list and cache the basis tree.
2034
2025
wt.set_parent_trees([(revision, basis_tree)])
2035
build_tree(basis_tree, wt)
2026
transform.build_tree(basis_tree, wt)
2038
2029
def __init__(self):
2100
2091
branch = a_bzrdir.open_branch()
2101
2092
if revision_id is None:
2102
2093
revision_id = branch.last_revision()
2094
# WorkingTree3 can handle an inventory which has a unique root id.
2095
# as of bzr 0.12. However, bzr 0.11 and earlier fail to handle
2096
# those trees. And because there isn't a format bump inbetween, we
2097
# are maintaining compatibility with older clients.
2098
# inv = Inventory(root_id=gen_root_id())
2103
2099
inv = Inventory()
2104
2100
wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2110
2106
_control_files=control_files)
2111
2107
wt.lock_tree_write()
2113
wt.set_root_id(inv.root.file_id)
2114
2109
basis_tree = branch.repository.revision_tree(revision_id)
2115
if revision_id == bzrlib.revision.NULL_REVISION:
2110
# only set an explicit root id if there is one to set.
2111
if basis_tree.inventory.root is not None:
2112
wt.set_root_id(basis_tree.inventory.root.file_id)
2113
if revision_id == NULL_REVISION:
2116
2114
wt.set_parent_trees([])
2118
2116
wt.set_parent_trees([(revision_id, basis_tree)])
2119
build_tree(basis_tree, wt)
2117
transform.build_tree(basis_tree, wt)
2121
# unlock in this order so that the unlock-triggers-flush in
2119
# Unlock in this order so that the unlock-triggers-flush in
2122
2120
# WorkingTree is given a chance to fire.
2123
2121
control_files.unlock()