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
43
from bzrlib.lazy_import import lazy_import
44
lazy_import(globals(), """
44
46
from copy import deepcopy
45
from cStringIO import StringIO
51
50
from time import time
55
from bzrlib import bzrdir, errors, ignores, osutils, urlutils
56
from bzrlib.atomicfile import AtomicFile
56
conflicts as _mod_conflicts,
57
67
import bzrlib.branch
58
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
68
from bzrlib.transport import get_transport
72
from bzrlib import symbol_versioning
59
73
from bzrlib.decorators import needs_read_lock, needs_write_lock
60
74
from bzrlib.errors import (BzrCheckError,
67
81
MergeModifiedFormatError,
68
82
UnsupportedOperation,
70
from bzrlib.inventory import InventoryEntry, Inventory
84
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID
71
85
from bzrlib.lockable_files import LockableFiles, TransportLock
72
86
from bzrlib.lockdir import LockDir
73
from bzrlib.merge import merge_inner, transform_tree
74
87
import bzrlib.mutabletree
75
88
from bzrlib.mutabletree import needs_tree_write_lock
76
89
from bzrlib.osutils import (
101
from bzrlib.trace import mutter, note
102
from bzrlib.transport.local import LocalTransport
93
104
from bzrlib.progress import DummyProgress, ProgressPhase
94
105
from bzrlib.revision import NULL_REVISION
95
106
import bzrlib.revisiontree
104
from bzrlib.trace import mutter, note
105
from bzrlib.transform import build_tree
106
from bzrlib.transport import get_transport
107
from bzrlib.transport.local import LocalTransport
108
from bzrlib.textui import show_status
117
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
118
CONFLICT_HEADER_1 = "BZR conflict list format 1"
113
120
# the regex removes any weird characters; we don't escape them
114
121
# but rather just pull them out
253
260
self.basedir = wt.basedir
254
261
self._control_files = wt._control_files
255
262
self._hashcache = wt._hashcache
256
self._set_inventory(wt._inventory)
263
self._set_inventory(wt._inventory, dirty=False)
257
264
self._format = wt._format
258
265
self.bzrdir = wt.bzrdir
259
266
from bzrlib.hashcache import HashCache
303
310
if _inventory is None:
304
self._set_inventory(self.read_working_inventory())
311
self._inventory_is_modified = False
312
self.read_working_inventory()
306
self._set_inventory(_inventory)
314
# the caller of __init__ has provided an inventory,
315
# we assume they know what they are doing - as its only
316
# the Format factory and creation methods that are
317
# permitted to do this.
318
self._set_inventory(_inventory, dirty=False)
308
320
branch = property(
309
321
fget=lambda self: self._branch,
324
336
self._control_files.break_lock()
325
337
self.branch.break_lock()
327
def _set_inventory(self, inv):
339
def _set_inventory(self, inv, dirty):
340
"""Set the internal cached inventory.
342
:param inv: The inventory to set.
343
:param dirty: A boolean indicating whether the inventory is the same
344
logical inventory as whats on disk. If True the inventory is not
345
the same and should be written to disk or data will be lost, if
346
False then the inventory is the same as that on disk and any
347
serialisation would be unneeded overhead.
328
349
assert inv.root is not None
329
350
self._inventory = inv
351
self._inventory_is_modified = dirty
332
354
def open(path=None, _unsupported=False):
401
423
xml = self.read_basis_inventory()
402
inv = bzrlib.xml6.serializer_v6.read_inventory_from_string(xml)
424
inv = xml6.serializer_v6.read_inventory_from_string(xml)
403
425
if inv is not None and inv.revision_id == revision_id:
404
return bzrlib.tree.RevisionTree(self.branch.repository,
426
return bzrlib.revisiontree.RevisionTree(
427
self.branch.repository, inv, revision_id)
406
428
except (NoSuchFile, errors.BadInventoryFormat):
408
430
# No cached copy available, retrieve from the repository.
463
485
The path may be absolute or relative. If its a relative path it is
464
486
interpreted relative to the python current working directory.
466
return relpath(self.basedir, path)
488
return osutils.relpath(self.basedir, path)
468
490
def has_filename(self, filename):
469
491
return osutils.lexists(self.abspath(filename))
497
519
parents.append(l.rstrip('\n'))
500
523
def get_root_id(self):
501
524
"""Return the id of this trees root"""
502
inv = self.read_working_inventory()
503
return inv.root.file_id
525
return self._inventory.root.file_id
505
527
def _get_store_filename(self, file_id):
506
528
## XXX: badly named; this is not in the store at all
533
555
def copy_content_into(self, tree, revision_id=None):
534
556
"""Copy the current content and user files of this tree into tree."""
557
tree.set_root_id(self.get_root_id())
535
558
if revision_id is None:
536
transform_tree(tree, self)
559
merge.transform_tree(tree, self)
538
561
# TODO now merge from tree.last_revision to revision (to preserve
539
562
# user local changes)
540
transform_tree(tree, self)
563
merge.transform_tree(tree, self)
541
564
tree.set_parent_ids([revision_id])
543
566
def id2abspath(self, file_id):
870
"""Write the in memory inventory to disk."""
871
# TODO: Maybe this should only write on dirty ?
872
if self._control_files._lock_mode != 'w':
873
raise errors.NotWriteLocked(self)
875
xml5.serializer_v5.write_inventory(self._inventory, sio)
877
self._control_files.put('inventory', sio)
878
self._inventory_is_modified = False
846
880
def list_files(self, include_root=False):
847
881
"""Recursively list all files as (path, class, kind, id, entry).
1004
1038
# create a file in this interval and then the rename might be
1005
1039
# left half-done. But we should have caught most problems.
1006
1040
orig_inv = deepcopy(self.inventory)
1041
original_modified = self._inventory_is_modified
1044
self._inventory_is_modified = True
1008
1045
for f in from_paths:
1009
1046
name_tail = splitpath(f)[-1]
1010
1047
dest_path = pathjoin(to_name, name_tail)
1011
1048
result.append((f, dest_path))
1012
1049
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1014
rename(self.abspath(f), self.abspath(dest_path))
1051
osutils.rename(self.abspath(f), self.abspath(dest_path))
1015
1052
except OSError, e:
1016
1053
raise BzrError("failed to rename %r to %r: %s" %
1017
1054
(f, dest_path, e[1]))
1019
1056
# restore the inventory on error
1020
self._set_inventory(orig_inv)
1057
self._set_inventory(orig_inv, dirty=original_modified)
1022
1059
self._write_inventory(inv)
1062
1099
from_abs = self.abspath(from_rel)
1063
1100
to_abs = self.abspath(to_rel)
1065
rename(from_abs, to_abs)
1102
osutils.rename(from_abs, to_abs)
1066
1103
except OSError, e:
1067
1104
inv.rename(file_id, from_parent, from_name)
1068
1105
raise BzrError("failed to rename %r to %r: %s"
1144
1181
pb = bzrlib.ui.ui_factory.nested_progress_bar()
1146
1183
new_basis_tree = self.branch.basis_tree()
1147
merge_inner(self.branch,
1148
1186
new_basis_tree,
1150
1188
this_tree=self,
1190
if (basis_tree.inventory.root is None and
1191
new_basis_tree.inventory.root is not None):
1192
self.set_root_id(new_basis_tree.inventory.root.file_id)
1154
1195
# TODO - dedup parents list with things merged by pull ?
1426
1467
def _create_basis_xml_from_inventory(self, revision_id, inventory):
1427
1468
"""Create the text that will be saved in basis-inventory"""
1428
1469
inventory.revision_id = revision_id
1429
return bzrlib.xml6.serializer_v6.write_inventory_to_string(inventory)
1470
return xml6.serializer_v6.write_inventory_to_string(inventory)
1431
1472
def _cache_basis_inventory(self, new_revision):
1432
1473
"""Cache new_revision as the basis inventory."""
1463
1504
@needs_read_lock
1464
1505
def read_working_inventory(self):
1465
"""Read the working inventory."""
1506
"""Read the working inventory.
1508
:raises errors.InventoryModified: read_working_inventory will fail
1509
when the current in memory inventory has been modified.
1511
# conceptually this should be an implementation detail of the tree.
1512
# XXX: Deprecate this.
1466
1513
# ElementTree does its own conversion from UTF-8, so open in
1468
result = bzrlib.xml5.serializer_v5.read_inventory(
1515
if self._inventory_is_modified:
1516
raise errors.InventoryModified(self)
1517
result = xml5.serializer_v5.read_inventory(
1469
1518
self._control_files.get('inventory'))
1470
self._set_inventory(result)
1519
self._set_inventory(result, dirty=False)
1473
1522
@needs_tree_write_lock
1513
1563
@needs_tree_write_lock
1514
1564
def revert(self, filenames, old_tree=None, backups=True,
1515
1565
pb=DummyProgress()):
1516
from transform import revert
1517
from conflicts import resolve
1566
from bzrlib.conflicts import resolve
1518
1567
if old_tree is None:
1519
1568
old_tree = self.basis_tree()
1520
conflicts = revert(self, old_tree, filenames, backups, pb)
1569
conflicts = transform.revert(self, old_tree, filenames, backups, pb)
1521
1570
if not len(filenames):
1522
1571
self.set_parent_ids(self.get_parent_ids()[:1])
1553
1602
@needs_tree_write_lock
1554
1603
def set_root_id(self, file_id):
1555
1604
"""Set the root id for this tree."""
1556
inv = self.read_working_inventory()
1607
symbol_versioning.warn(symbol_versioning.zero_twelve
1608
% 'WorkingTree.set_root_id with fileid=None',
1612
inv = self._inventory
1557
1613
orig_root_id = inv.root.file_id
1614
# TODO: it might be nice to exit early if there was nothing
1615
# to do, saving us from trigger a sync on unlock.
1616
self._inventory_is_modified = True
1617
# we preserve the root inventory entry object, but
1618
# unlinkit from the byid index
1558
1619
del inv._byid[inv.root.file_id]
1559
1620
inv.root.file_id = file_id
1621
# and link it into the index with the new changed id.
1560
1622
inv._byid[inv.root.file_id] = inv.root
1623
# and finally update all children to reference the new id.
1624
# XXX: this should be safe to just look at the root.children
1625
# list, not the WHOLE INVENTORY.
1561
1626
for fid in inv:
1562
1627
entry = inv[fid]
1563
1628
if entry.parent_id == orig_root_id:
1564
1629
entry.parent_id = inv.root.file_id
1565
self._write_inventory(inv)
1567
1631
def unlock(self):
1568
1632
"""See Branch.unlock.
1576
1640
raise NotImplementedError(self.unlock)
1579
1642
def update(self):
1580
1643
"""Update a working tree along its branch.
1582
This will update the branch if its bound too, which means we have multiple trees involved:
1583
The new basis tree of the master.
1584
The old basis tree of the branch.
1585
The old basis tree of the working tree.
1586
The current working tree state.
1587
pathologically all three may be different, and non ancestors of each other.
1588
Conceptually we want to:
1589
Preserve the wt.basis->wt.state changes
1590
Transform the wt.basis to the new master basis.
1591
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1592
Restore the wt.basis->wt.state changes.
1645
This will update the branch if its bound too, which means we have
1646
multiple trees involved:
1648
- The new basis tree of the master.
1649
- The old basis tree of the branch.
1650
- The old basis tree of the working tree.
1651
- The current working tree state.
1653
Pathologically, all three may be different, and non-ancestors of each
1654
other. Conceptually we want to:
1656
- Preserve the wt.basis->wt.state changes
1657
- Transform the wt.basis to the new master basis.
1658
- Apply a merge of the old branch basis to get any 'local' changes from
1660
- Restore the wt.basis->wt.state changes.
1594
1662
There isn't a single operation at the moment to do that, so we:
1595
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1596
Do a 'normal' merge of the old branch basis if it is relevant.
1598
old_tip = self.branch.update()
1663
- Merge current state -> basis tree of the master w.r.t. the old tree
1665
- Do a 'normal' merge of the old branch basis if it is relevant.
1667
if self.branch.get_master_branch() is not None:
1669
update_branch = True
1671
self.lock_tree_write()
1672
update_branch = False
1675
old_tip = self.branch.update()
1678
return self._update_tree(old_tip)
1682
@needs_tree_write_lock
1683
def _update_tree(self, old_tip=None):
1684
"""Update a tree to the master branch.
1686
:param old_tip: if supplied, the previous tip revision the branch,
1687
before it was changed to the master branch's tip.
1599
1689
# here if old_tip is not None, it is the old tip of the branch before
1600
1690
# it was updated from the master branch. This should become a pending
1601
1691
# merge in the working tree to preserve the user existing work. we
1615
1705
# merge tree state up to new branch tip.
1616
1706
basis = self.basis_tree()
1617
1707
to_tree = self.branch.basis_tree()
1618
result += merge_inner(self.branch,
1708
if basis.inventory.root is None:
1709
self.set_root_id(to_tree.inventory.root.file_id)
1710
result += merge.merge_inner(
1621
1714
this_tree=self)
1665
1759
@needs_tree_write_lock
1666
1760
def _write_inventory(self, inv):
1667
1761
"""Write inventory as the current inventory."""
1669
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1671
self._control_files.put('inventory', sio)
1672
self._set_inventory(inv)
1673
mutter('wrote working inventory')
1762
self._set_inventory(inv, dirty=True)
1675
1765
def set_conflicts(self, arg):
1676
1766
raise UnsupportedOperation(self.set_conflicts, self)
1699
1789
if text == False:
1701
1791
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1702
conflicts.append(Conflict.factory(ctype, path=conflicted,
1792
conflicts.append(_mod_conflicts.Conflict.factory(ctype,
1703
1794
file_id=self.path2id(conflicted)))
1704
1795
return conflicts
1728
1819
def unlock(self):
1729
1820
# we share control files:
1730
if self._hashcache.needs_write and self._control_files._lock_count==3:
1731
self._hashcache.write()
1821
if self._control_files._lock_count == 3:
1822
# _inventory_is_modified is always False during a read lock.
1823
if self._inventory_is_modified:
1825
if self._hashcache.needs_write:
1826
self._hashcache.write()
1732
1827
# reverse order of locking.
1734
1829
return self._control_files.unlock()
1775
1870
def add_conflicts(self, new_conflicts):
1776
1871
conflict_set = set(self.conflicts())
1777
1872
conflict_set.update(set(list(new_conflicts)))
1778
self.set_conflicts(ConflictList(sorted(conflict_set,
1779
key=Conflict.sort_key)))
1873
self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
1874
key=_mod_conflicts.Conflict.sort_key)))
1781
1876
@needs_read_lock
1782
1877
def conflicts(self):
1784
1879
confile = self._control_files.get('conflicts')
1785
1880
except NoSuchFile:
1786
return ConflictList()
1881
return _mod_conflicts.ConflictList()
1788
1883
if confile.next() != CONFLICT_HEADER_1 + '\n':
1789
1884
raise ConflictFormatError()
1790
1885
except StopIteration:
1791
1886
raise ConflictFormatError()
1792
return ConflictList.from_stanzas(RioReader(confile))
1887
return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
1794
1889
def unlock(self):
1795
if self._hashcache.needs_write and self._control_files._lock_count==1:
1796
self._hashcache.write()
1890
if self._control_files._lock_count == 1:
1891
# _inventory_is_modified is always False during a read lock.
1892
if self._inventory_is_modified:
1894
if self._hashcache.needs_write:
1895
self._hashcache.write()
1797
1896
# reverse order of locking.
1799
1898
return self._control_files.unlock()
1945
2044
_internal=True,
1947
2046
_bzrdir=a_bzrdir)
1948
wt._write_inventory(inv)
1949
wt.set_root_id(inv.root.file_id)
1950
2047
basis_tree = branch.repository.revision_tree(revision)
2048
if basis_tree.inventory.root is not None:
2049
wt.set_root_id(basis_tree.inventory.root.file_id)
2050
# set the parent list and cache the basis tree.
1951
2051
wt.set_parent_trees([(revision, basis_tree)])
1952
build_tree(basis_tree, wt)
2052
transform.build_tree(basis_tree, wt)
1955
2055
def __init__(self):
2017
2117
branch = a_bzrdir.open_branch()
2018
2118
if revision_id is None:
2019
2119
revision_id = branch.last_revision()
2120
# WorkingTree3 can handle an inventory which has a unique root id.
2121
# as of bzr 0.12. However, bzr 0.11 and earlier fail to handle
2122
# those trees. And because there isn't a format bump inbetween, we
2123
# are maintaining compatibility with older clients.
2124
# inv = Inventory(root_id=gen_root_id())
2021
2126
wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
2027
2132
_control_files=control_files)
2028
2133
wt.lock_tree_write()
2030
wt._write_inventory(inv)
2031
wt.set_root_id(inv.root.file_id)
2032
2135
basis_tree = branch.repository.revision_tree(revision_id)
2033
if revision_id == bzrlib.revision.NULL_REVISION:
2136
# only set an explicit root id if there is one to set.
2137
if basis_tree.inventory.root is not None:
2138
wt.set_root_id(basis_tree.inventory.root.file_id)
2139
if revision_id == NULL_REVISION:
2034
2140
wt.set_parent_trees([])
2036
2142
wt.set_parent_trees([(revision_id, basis_tree)])
2037
build_tree(basis_tree, wt)
2143
transform.build_tree(basis_tree, wt)
2145
# Unlock in this order so that the unlock-triggers-flush in
2146
# WorkingTree is given a chance to fire.
2147
control_files.unlock()
2040
control_files.unlock()
2043
2151
def __init__(self):