~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Vincent Ladeuil
  • Date: 2012-03-13 17:25:29 UTC
  • mfrom: (6499 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6501.
  • Revision ID: v.ladeuil+lp@free.fr-20120313172529-i0suyjnepsor25i7
Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
WorkingTree.open(dir).
30
30
"""
31
31
 
 
32
from __future__ import absolute_import
32
33
 
33
34
from cStringIO import StringIO
34
35
import os
46
47
 
47
48
from bzrlib import (
48
49
    branch,
49
 
    bzrdir,
50
50
    conflicts as _mod_conflicts,
51
51
    controldir,
52
52
    errors,
54
54
    generate_ids,
55
55
    globbing,
56
56
    graph as _mod_graph,
57
 
    hashcache,
58
57
    ignores,
59
58
    inventory,
60
59
    merge,
70
69
    )
71
70
""")
72
71
 
73
 
from bzrlib import symbol_versioning
 
72
# Explicitly import bzrlib.bzrdir so that the BzrProber
 
73
# is guaranteed to be registered.
 
74
from bzrlib import (
 
75
    bzrdir,
 
76
    symbol_versioning,
 
77
    )
 
78
 
74
79
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
80
from bzrlib.i18n import gettext
75
81
from bzrlib.lock import LogicalLockResult
76
82
import bzrlib.mutabletree
77
83
from bzrlib.mutabletree import needs_tree_write_lock
84
90
    realpath,
85
91
    safe_unicode,
86
92
    splitpath,
87
 
    supports_executable,
88
93
    )
89
94
from bzrlib.trace import mutter, note
90
95
from bzrlib.revision import CURRENT_REVISION
172
177
 
173
178
    def __init__(self, basedir='.',
174
179
                 branch=DEPRECATED_PARAMETER,
175
 
                 _control_files=None,
176
180
                 _internal=False,
 
181
                 _transport=None,
177
182
                 _format=None,
178
183
                 _bzrdir=None):
179
184
        """Construct a WorkingTree instance. This is not a public API.
192
197
        else:
193
198
            self._branch = self.bzrdir.open_branch()
194
199
        self.basedir = realpath(basedir)
195
 
        self._control_files = _control_files
196
 
        self._transport = self._control_files._transport
197
 
        # update the whole cache up front and write to disk if anything changed;
198
 
        # in the future we might want to do this more selectively
199
 
        # two possible ways offer themselves : in self._unlock, write the cache
200
 
        # if needed, or, when the cache sees a change, append it to the hash
201
 
        # cache file, and have the parser take the most recent entry for a
202
 
        # given path only.
203
 
        wt_trans = self.bzrdir.get_workingtree_transport(None)
204
 
        cache_filename = wt_trans.local_abspath('stat-cache')
205
 
        self._hashcache = hashcache.HashCache(basedir, cache_filename,
206
 
            self.bzrdir._get_file_mode(),
207
 
            self._content_filter_stack_provider())
208
 
        hc = self._hashcache
209
 
        hc.read()
210
 
        # is this scan needed ? it makes things kinda slow.
211
 
        #hc.scan()
212
 
 
213
 
        if hc.needs_write:
214
 
            mutter("write hc")
215
 
            hc.write()
216
 
 
217
 
        self._detect_case_handling()
 
200
        self._transport = _transport
218
201
        self._rules_searcher = None
219
202
        self.views = self._make_views()
220
203
 
238
221
        """
239
222
        return self.bzrdir.is_control_filename(filename)
240
223
 
241
 
    def _detect_case_handling(self):
242
 
        wt_trans = self.bzrdir.get_workingtree_transport(None)
243
 
        try:
244
 
            wt_trans.stat(self._format.case_sensitive_filename)
245
 
        except errors.NoSuchFile:
246
 
            self.case_sensitive = True
247
 
        else:
248
 
            self.case_sensitive = False
249
 
 
250
 
        self._setup_directory_is_tree_reference()
251
 
 
252
224
    branch = property(
253
225
        fget=lambda self: self._branch,
254
226
        doc="""The branch this WorkingTree is connected to.
257
229
            the working tree has been constructed from.
258
230
            """)
259
231
 
 
232
    def has_versioned_directories(self):
 
233
        """See `Tree.has_versioned_directories`."""
 
234
        return self._format.supports_versioned_directories
 
235
 
 
236
    def _supports_executable(self):
 
237
        if sys.platform == 'win32':
 
238
            return False
 
239
        # FIXME: Ideally this should check the file system
 
240
        return True
 
241
 
260
242
    def break_lock(self):
261
243
        """Break a lock if one is present from another instance.
262
244
 
265
247
 
266
248
        This will probe the repository for its lock as well.
267
249
        """
268
 
        self._control_files.break_lock()
269
 
        self.branch.break_lock()
 
250
        raise NotImplementedError(self.break_lock)
270
251
 
271
252
    def requires_rich_root(self):
272
253
        return self._format.requires_rich_root
280
261
    def supports_views(self):
281
262
        return self.views.supports_views()
282
263
 
 
264
    def get_config_stack(self):
 
265
        """Retrieve the config stack for this tree.
 
266
 
 
267
        :return: A ``bzrlib.config.Stack``
 
268
        """
 
269
        # For the moment, just provide the branch config stack.
 
270
        return self.branch.get_config_stack()
 
271
 
283
272
    @staticmethod
284
273
    def open(path=None, _unsupported=False):
285
274
        """Open an existing working tree at path.
287
276
        """
288
277
        if path is None:
289
278
            path = osutils.getcwd()
290
 
        control = bzrdir.BzrDir.open(path, _unsupported)
291
 
        return control.open_workingtree(_unsupported)
 
279
        control = controldir.ControlDir.open(path, _unsupported=_unsupported)
 
280
        return control.open_workingtree(unsupported=_unsupported)
292
281
 
293
282
    @staticmethod
294
283
    def open_containing(path=None):
305
294
        """
306
295
        if path is None:
307
296
            path = osutils.getcwd()
308
 
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
297
        control, relpath = controldir.ControlDir.open_containing(path)
309
298
        return control.open_workingtree(), relpath
310
299
 
311
300
    @staticmethod
331
320
                if view_files:
332
321
                    file_list = view_files
333
322
                    view_str = views.view_display_str(view_files)
334
 
                    note("Ignoring files outside view. View is %s" % view_str)
 
323
                    note(gettext("Ignoring files outside view. View is %s") % view_str)
335
324
            return tree, file_list
336
325
        if default_directory == u'.':
337
326
            seed = file_list[0]
394
383
            else:
395
384
                return True, tree
396
385
        t = transport.get_transport(location)
397
 
        iterator = bzrdir.BzrDir.find_bzrdirs(t, evaluate=evaluate,
 
386
        iterator = controldir.ControlDir.find_bzrdirs(t, evaluate=evaluate,
398
387
                                              list_current=list_current)
399
388
        return [tr for tr in iterator if tr is not None]
400
389
 
401
 
    def all_file_ids(self):
402
 
        """See Tree.iter_all_file_ids"""
403
 
        raise NotImplementedError(self.all_file_ids)
404
 
 
405
390
    def __repr__(self):
406
391
        return "<%s of %s>" % (self.__class__.__name__,
407
392
                               getattr(self, 'basedir', None))
522
507
        raise NotImplementedError(self.get_root_id)
523
508
 
524
509
    @needs_read_lock
525
 
    def clone(self, to_bzrdir, revision_id=None):
 
510
    def clone(self, to_controldir, revision_id=None):
526
511
        """Duplicate this working tree into to_bzr, including all state.
527
512
 
528
513
        Specifically modified files are kept as modified, but
529
514
        ignored and unknown files are discarded.
530
515
 
531
 
        If you want to make a new line of development, see bzrdir.sprout()
 
516
        If you want to make a new line of development, see ControlDir.sprout()
532
517
 
533
518
        revision
534
519
            If not None, the cloned tree will have its last revision set to
536
521
            and this one merged in.
537
522
        """
538
523
        # assumes the target bzr dir format is compatible.
539
 
        result = to_bzrdir.create_workingtree()
 
524
        result = to_controldir.create_workingtree()
540
525
        self.copy_content_into(result, revision_id)
541
526
        return result
542
527
 
550
535
            # TODO now merge from tree.last_revision to revision (to preserve
551
536
            # user local changes)
552
537
            merge.transform_tree(tree, self)
553
 
            tree.set_parent_ids([revision_id])
 
538
            if revision_id == _mod_revision.NULL_REVISION:
 
539
                new_parents = []
 
540
            else:
 
541
                new_parents = [revision_id]
 
542
            tree.set_parent_ids(new_parents)
554
543
 
555
544
    def id2abspath(self, file_id):
556
545
        return self.abspath(self.id2path(file_id))
595
584
            else:
596
585
                return None
597
586
 
598
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
599
 
        # FIXME: Shouldn't this be in Tree?
600
 
        raise NotImplementedError(self.get_file_sha1)
601
 
 
602
587
    @needs_tree_write_lock
603
588
    def _gather_kinds(self, files, kinds):
604
589
        """See MutableTree._gather_kinds."""
769
754
 
770
755
    @needs_tree_write_lock
771
756
    def set_merge_modified(self, modified_hashes):
772
 
        def iter_stanzas():
773
 
            for file_id, hash in modified_hashes.iteritems():
774
 
                yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
775
 
                    hash=hash)
776
 
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
 
757
        """Set the merge modified hashes."""
 
758
        raise NotImplementedError(self.set_merge_modified)
777
759
 
778
760
    def _sha_from_stat(self, path, stat_result):
779
761
        """Get a sha digest from the tree's stat cache.
785
767
        """
786
768
        return None
787
769
 
788
 
    def _put_rio(self, filename, stanzas, header):
789
 
        self._must_be_locked()
790
 
        my_file = _mod_rio.rio_file(stanzas, header)
791
 
        self._transport.put_file(filename, my_file,
792
 
            mode=self.bzrdir._get_file_mode())
793
 
 
794
770
    @needs_write_lock # because merge pulls data into the branch.
795
771
    def merge_from_branch(self, branch, to_revision=None, from_revision=None,
796
772
                          merge_type=None, force=False):
1036
1012
                                show_base=show_base)
1037
1013
                    basis_root_id = basis_tree.get_root_id()
1038
1014
                    new_root_id = new_basis_tree.get_root_id()
1039
 
                    if basis_root_id != new_root_id:
 
1015
                    if new_root_id is not None and basis_root_id != new_root_id:
1040
1016
                        self.set_root_id(new_root_id)
1041
1017
                finally:
1042
1018
                    basis_tree.unlock()
1043
1019
                # TODO - dedup parents list with things merged by pull ?
1044
1020
                # reuse the revisiontree we merged against to set the new
1045
1021
                # tree data.
1046
 
                parent_trees = [(self.branch.last_revision(), new_basis_tree)]
 
1022
                parent_trees = []
 
1023
                if self.branch.last_revision() != _mod_revision.NULL_REVISION:
 
1024
                    parent_trees.append(
 
1025
                        (self.branch.last_revision(), new_basis_tree))
1047
1026
                # we have to pull the merge trees out again, because
1048
1027
                # merge_inner has set the ids. - this corner is not yet
1049
1028
                # layered well enough to prevent double handling.
1066
1045
            stream.write(bytes)
1067
1046
        finally:
1068
1047
            stream.close()
1069
 
        # TODO: update the hashcache here ?
1070
1048
 
1071
1049
    def extras(self):
1072
1050
        """Yield all unversioned files in this WorkingTree.
1149
1127
        else:
1150
1128
            mode = stat_value.st_mode
1151
1129
            kind = osutils.file_kind_from_stat_mode(mode)
1152
 
            if not supports_executable():
 
1130
            if not self._supports_executable():
1153
1131
                executable = entry is not None and entry.executable
1154
1132
            else:
1155
1133
                executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1174
1152
        return _mod_revision.ensure_null(self.branch.last_revision())
1175
1153
 
1176
1154
    def is_locked(self):
1177
 
        return self._control_files.is_locked()
1178
 
 
1179
 
    def _must_be_locked(self):
1180
 
        if not self.is_locked():
1181
 
            raise errors.ObjectNotLocked(self)
 
1155
        """Check if this tree is locked."""
 
1156
        raise NotImplementedError(self.is_locked)
1182
1157
 
1183
1158
    def lock_read(self):
1184
1159
        """Lock the tree for reading.
1187
1162
 
1188
1163
        :return: A bzrlib.lock.LogicalLockResult.
1189
1164
        """
1190
 
        if not self.is_locked():
1191
 
            self._reset_data()
1192
 
        self.branch.lock_read()
1193
 
        try:
1194
 
            self._control_files.lock_read()
1195
 
            return LogicalLockResult(self.unlock)
1196
 
        except:
1197
 
            self.branch.unlock()
1198
 
            raise
 
1165
        raise NotImplementedError(self.lock_read)
1199
1166
 
1200
1167
    def lock_tree_write(self):
1201
1168
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
1202
1169
 
1203
1170
        :return: A bzrlib.lock.LogicalLockResult.
1204
1171
        """
1205
 
        if not self.is_locked():
1206
 
            self._reset_data()
1207
 
        self.branch.lock_read()
1208
 
        try:
1209
 
            self._control_files.lock_write()
1210
 
            return LogicalLockResult(self.unlock)
1211
 
        except:
1212
 
            self.branch.unlock()
1213
 
            raise
 
1172
        raise NotImplementedError(self.lock_tree_write)
1214
1173
 
1215
1174
    def lock_write(self):
1216
1175
        """See MutableTree.lock_write, and WorkingTree.unlock.
1217
1176
 
1218
1177
        :return: A bzrlib.lock.LogicalLockResult.
1219
1178
        """
1220
 
        if not self.is_locked():
1221
 
            self._reset_data()
1222
 
        self.branch.lock_write()
1223
 
        try:
1224
 
            self._control_files.lock_write()
1225
 
            return LogicalLockResult(self.unlock)
1226
 
        except:
1227
 
            self.branch.unlock()
1228
 
            raise
 
1179
        raise NotImplementedError(self.lock_write)
1229
1180
 
1230
1181
    def get_physical_lock_status(self):
1231
 
        return self._control_files.get_physical_lock_status()
1232
 
 
1233
 
    def _reset_data(self):
1234
 
        """Reset transient data that cannot be revalidated."""
1235
 
        raise NotImplementedError(self._reset_data)
 
1182
        raise NotImplementedError(self.get_physical_lock_status)
1236
1183
 
1237
1184
    def set_last_revision(self, new_revision):
1238
1185
        """Change the last revision in the working tree."""
1532
1479
                                             show_base=show_base)
1533
1480
            if nb_conflicts:
1534
1481
                self.add_parent_tree((old_tip, other_tree))
1535
 
                note('Rerun update after fixing the conflicts.')
 
1482
                note(gettext('Rerun update after fixing the conflicts.'))
1536
1483
                return nb_conflicts
1537
1484
 
1538
1485
        if last_rev != _mod_revision.ensure_null(revision):
1580
1527
            last_rev = parent_trees[0][0]
1581
1528
        return nb_conflicts
1582
1529
 
1583
 
    def _write_hashcache_if_dirty(self):
1584
 
        """Write out the hashcache if it is dirty."""
1585
 
        if self._hashcache.needs_write:
1586
 
            try:
1587
 
                self._hashcache.write()
1588
 
            except OSError, e:
1589
 
                if e.errno not in (errno.EPERM, errno.EACCES):
1590
 
                    raise
1591
 
                # TODO: jam 20061219 Should this be a warning? A single line
1592
 
                #       warning might be sufficient to let the user know what
1593
 
                #       is going on.
1594
 
                mutter('Could not write hashcache for %s\nError: %s',
1595
 
                              self._hashcache.cache_file_name(), e)
1596
 
 
1597
1530
    def set_conflicts(self, arg):
1598
1531
        raise errors.UnsupportedOperation(self.set_conflicts, self)
1599
1532
 
1828
1761
        :param branch: A branch to override probing for the branch.
1829
1762
        """
1830
1763
        super(InventoryWorkingTree, self).__init__(basedir=basedir,
1831
 
            branch=branch, _control_files=_control_files, _internal=_internal,
1832
 
            _format=_format, _bzrdir=_bzrdir)
 
1764
            branch=branch, _transport=_control_files._transport,
 
1765
            _internal=_internal, _format=_format, _bzrdir=_bzrdir)
 
1766
 
 
1767
        self._control_files = _control_files
 
1768
        self._detect_case_handling()
1833
1769
 
1834
1770
        if _inventory is None:
1835
1771
            # This will be acquired on lock_read() or lock_write()
1855
1791
        self._inventory = inv
1856
1792
        self._inventory_is_modified = dirty
1857
1793
 
 
1794
    def _detect_case_handling(self):
 
1795
        wt_trans = self.bzrdir.get_workingtree_transport(None)
 
1796
        try:
 
1797
            wt_trans.stat(self._format.case_sensitive_filename)
 
1798
        except errors.NoSuchFile:
 
1799
            self.case_sensitive = True
 
1800
        else:
 
1801
            self.case_sensitive = False
 
1802
 
 
1803
        self._setup_directory_is_tree_reference()
 
1804
 
1858
1805
    def _serialize(self, inventory, out_file):
1859
1806
        xml5.serializer_v5.write_inventory(self._inventory, out_file,
1860
1807
            working=True)
1862
1809
    def _deserialize(selt, in_file):
1863
1810
        return xml5.serializer_v5.read_inventory(in_file)
1864
1811
 
 
1812
    def break_lock(self):
 
1813
        """Break a lock if one is present from another instance.
 
1814
 
 
1815
        Uses the ui factory to ask for confirmation if the lock may be from
 
1816
        an active process.
 
1817
 
 
1818
        This will probe the repository for its lock as well.
 
1819
        """
 
1820
        self._control_files.break_lock()
 
1821
        self.branch.break_lock()
 
1822
 
 
1823
    def is_locked(self):
 
1824
        return self._control_files.is_locked()
 
1825
 
 
1826
    def _must_be_locked(self):
 
1827
        if not self.is_locked():
 
1828
            raise errors.ObjectNotLocked(self)
 
1829
 
 
1830
    def lock_read(self):
 
1831
        """Lock the tree for reading.
 
1832
 
 
1833
        This also locks the branch, and can be unlocked via self.unlock().
 
1834
 
 
1835
        :return: A bzrlib.lock.LogicalLockResult.
 
1836
        """
 
1837
        if not self.is_locked():
 
1838
            self._reset_data()
 
1839
        self.branch.lock_read()
 
1840
        try:
 
1841
            self._control_files.lock_read()
 
1842
            return LogicalLockResult(self.unlock)
 
1843
        except:
 
1844
            self.branch.unlock()
 
1845
            raise
 
1846
 
 
1847
    def lock_tree_write(self):
 
1848
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
1849
 
 
1850
        :return: A bzrlib.lock.LogicalLockResult.
 
1851
        """
 
1852
        if not self.is_locked():
 
1853
            self._reset_data()
 
1854
        self.branch.lock_read()
 
1855
        try:
 
1856
            self._control_files.lock_write()
 
1857
            return LogicalLockResult(self.unlock)
 
1858
        except:
 
1859
            self.branch.unlock()
 
1860
            raise
 
1861
 
 
1862
    def lock_write(self):
 
1863
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
1864
 
 
1865
        :return: A bzrlib.lock.LogicalLockResult.
 
1866
        """
 
1867
        if not self.is_locked():
 
1868
            self._reset_data()
 
1869
        self.branch.lock_write()
 
1870
        try:
 
1871
            self._control_files.lock_write()
 
1872
            return LogicalLockResult(self.unlock)
 
1873
        except:
 
1874
            self.branch.unlock()
 
1875
            raise
 
1876
 
 
1877
    def get_physical_lock_status(self):
 
1878
        return self._control_files.get_physical_lock_status()
 
1879
 
1865
1880
    @needs_tree_write_lock
1866
1881
    def _write_inventory(self, inv):
1867
1882
        """Write inventory as the current inventory."""
1935
1950
            if entry.parent_id == orig_root_id:
1936
1951
                entry.parent_id = inv.root.file_id
1937
1952
 
1938
 
    def all_file_ids(self):
1939
 
        """See Tree.iter_all_file_ids"""
1940
 
        return set(self.inventory)
1941
 
 
1942
1953
    @needs_tree_write_lock
1943
1954
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1944
1955
        """See MutableTree.set_parent_trees."""
1963
1974
                # parent tree from the repository.
1964
1975
                self._cache_basis_inventory(leftmost_parent_id)
1965
1976
            else:
1966
 
                inv = leftmost_parent_tree.inventory
 
1977
                inv = leftmost_parent_tree.root_inventory
1967
1978
                xml = self._create_basis_xml_from_inventory(
1968
1979
                                        leftmost_parent_id, inv)
1969
1980
                self._write_basis_inventory(xml)
2066
2077
 
2067
2078
    def has_id(self, file_id):
2068
2079
        # files that have been deleted are excluded
2069
 
        inv = self.inventory
2070
 
        if not inv.has_id(file_id):
 
2080
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2081
        if not inv.has_id(inv_file_id):
2071
2082
            return False
2072
 
        path = inv.id2path(file_id)
 
2083
        path = inv.id2path(inv_file_id)
2073
2084
        return osutils.lexists(self.abspath(path))
2074
2085
 
2075
2086
    def has_or_had_id(self, file_id):
2076
 
        if file_id == self.inventory.root.file_id:
 
2087
        if file_id == self.get_root_id():
2077
2088
            return True
2078
 
        return self.inventory.has_id(file_id)
 
2089
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2090
        return inv.has_id(inv_file_id)
2079
2091
 
2080
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2081
 
    def __iter__(self):
 
2092
    def all_file_ids(self):
2082
2093
        """Iterate through file_ids for this tree.
2083
2094
 
2084
2095
        file_ids are in a WorkingTree if they are in the working inventory
2085
2096
        and the working file exists.
2086
2097
        """
2087
 
        inv = self._inventory
2088
 
        for path, ie in inv.iter_entries():
2089
 
            if osutils.lexists(self.abspath(path)):
2090
 
                yield ie.file_id
 
2098
        ret = set()
 
2099
        for path, ie in self.iter_entries_by_dir():
 
2100
            ret.add(ie.file_id)
 
2101
        return ret
2091
2102
 
2092
2103
    @needs_tree_write_lock
2093
2104
    def set_last_revision(self, new_revision):
2149
2160
                _mod_revision.NULL_REVISION)
2150
2161
        else:
2151
2162
            rt = self.branch.repository.revision_tree(revision_ids[0])
2152
 
        self._write_inventory(rt.inventory)
 
2163
        self._write_inventory(rt.root_inventory)
2153
2164
        self.set_parent_ids(revision_ids)
2154
2165
 
2155
2166
    def flush(self):
2164
2175
            mode=self.bzrdir._get_file_mode())
2165
2176
        self._inventory_is_modified = False
2166
2177
 
2167
 
    @needs_read_lock
2168
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
2169
 
        if not path:
2170
 
            path = self._inventory.id2path(file_id)
2171
 
        return self._hashcache.get_sha1(path, stat_value)
2172
 
 
2173
2178
    def get_file_mtime(self, file_id, path=None):
2174
2179
        """See Tree.get_file_mtime."""
2175
2180
        if not path:
2176
 
            path = self.inventory.id2path(file_id)
2177
 
        return os.lstat(self.abspath(path)).st_mtime
 
2181
            path = self.id2path(file_id)
 
2182
        try:
 
2183
            return os.lstat(self.abspath(path)).st_mtime
 
2184
        except OSError, e:
 
2185
            if e.errno == errno.ENOENT:
 
2186
                raise errors.FileTimestampUnavailable(path)
 
2187
            raise
2178
2188
 
2179
2189
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
2180
 
        file_id = self.path2id(path)
 
2190
        inv, file_id = self._path2inv_file_id(path)
2181
2191
        if file_id is None:
2182
2192
            # For unversioned files on win32, we just assume they are not
2183
2193
            # executable
2184
2194
            return False
2185
 
        return self._inventory[file_id].executable
 
2195
        return inv[file_id].executable
2186
2196
 
2187
2197
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
2188
2198
        mode = stat_result.st_mode
2189
2199
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2190
2200
 
2191
 
    if not supports_executable():
2192
 
        def is_executable(self, file_id, path=None):
2193
 
            return self._inventory[file_id].executable
2194
 
 
2195
 
        _is_executable_from_path_and_stat = \
2196
 
            _is_executable_from_path_and_stat_from_basis
2197
 
    else:
2198
 
        def is_executable(self, file_id, path=None):
 
2201
    def is_executable(self, file_id, path=None):
 
2202
        if not self._supports_executable():
 
2203
            inv, inv_file_id = self._unpack_file_id(file_id)
 
2204
            return inv[inv_file_id].executable
 
2205
        else:
2199
2206
            if not path:
2200
2207
                path = self.id2path(file_id)
2201
2208
            mode = os.lstat(self.abspath(path)).st_mode
2202
2209
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2203
2210
 
2204
 
        _is_executable_from_path_and_stat = \
2205
 
            _is_executable_from_path_and_stat_from_stat
 
2211
    def _is_executable_from_path_and_stat(self, path, stat_result):
 
2212
        if not self._supports_executable():
 
2213
            return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
 
2214
        else:
 
2215
            return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
2206
2216
 
2207
2217
    @needs_tree_write_lock
2208
2218
    def _add(self, files, ids, kinds):
2211
2221
        # should probably put it back with the previous ID.
2212
2222
        # the read and write working inventory should not occur in this
2213
2223
        # function - they should be part of lock_write and unlock.
2214
 
        inv = self.inventory
 
2224
        # FIXME: nested trees
 
2225
        inv = self.root_inventory
2215
2226
        for f, file_id, kind in zip(files, ids, kinds):
2216
2227
            if file_id is None:
2217
2228
                inv.add_path(f, kind=kind)
2258
2269
                parent_tree = self.branch.repository.revision_tree(parent_id)
2259
2270
            parent_tree.lock_read()
2260
2271
            try:
2261
 
                if not parent_tree.has_id(file_id):
 
2272
                try:
 
2273
                    kind = parent_tree.kind(file_id)
 
2274
                except errors.NoSuchId:
2262
2275
                    continue
2263
 
                ie = parent_tree.inventory[file_id]
2264
 
                if ie.kind != 'file':
 
2276
                if kind != 'file':
2265
2277
                    # Note: this is slightly unnecessary, because symlinks and
2266
2278
                    # directories have a "text" which is the empty text, and we
2267
2279
                    # know that won't mess up annotations. But it seems cleaner
2268
2280
                    continue
2269
 
                parent_text_key = (file_id, ie.revision)
 
2281
                parent_text_key = (
 
2282
                    file_id, parent_tree.get_file_revision(file_id))
2270
2283
                if parent_text_key not in maybe_file_parent_keys:
2271
2284
                    maybe_file_parent_keys.append(parent_text_key)
2272
2285
            finally:
2287
2300
                       for key, line in annotator.annotate_flat(this_key)]
2288
2301
        return annotations
2289
2302
 
 
2303
    def _put_rio(self, filename, stanzas, header):
 
2304
        self._must_be_locked()
 
2305
        my_file = _mod_rio.rio_file(stanzas, header)
 
2306
        self._transport.put_file(filename, my_file,
 
2307
            mode=self.bzrdir._get_file_mode())
 
2308
 
 
2309
    @needs_tree_write_lock
 
2310
    def set_merge_modified(self, modified_hashes):
 
2311
        def iter_stanzas():
 
2312
            for file_id, hash in modified_hashes.iteritems():
 
2313
                yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
 
2314
                    hash=hash)
 
2315
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
 
2316
 
2290
2317
    @needs_read_lock
2291
2318
    def merge_modified(self):
2292
2319
        """Return a dictionary of files modified by a merge.
2312
2339
            for s in _mod_rio.RioReader(hashfile):
2313
2340
                # RioReader reads in Unicode, so convert file_ids back to utf8
2314
2341
                file_id = osutils.safe_file_id(s.get("file_id"), warn=False)
2315
 
                if not self.inventory.has_id(file_id):
 
2342
                if not self.has_id(file_id):
2316
2343
                    continue
2317
2344
                text_hash = s.get("hash")
2318
2345
                if text_hash == self.get_file_sha1(file_id):
2348
2375
        other_tree.lock_tree_write()
2349
2376
        try:
2350
2377
            new_parents = other_tree.get_parent_ids()
2351
 
            other_root = other_tree.inventory.root
 
2378
            other_root = other_tree.root_inventory.root
2352
2379
            other_root.parent_id = new_root_parent
2353
2380
            other_root.name = osutils.basename(other_tree_path)
2354
 
            self.inventory.add(other_root)
2355
 
            add_children(self.inventory, other_root)
2356
 
            self._write_inventory(self.inventory)
 
2381
            self.root_inventory.add(other_root)
 
2382
            add_children(self.root_inventory, other_root)
 
2383
            self._write_inventory(self.root_inventory)
2357
2384
            # normally we don't want to fetch whole repositories, but i think
2358
2385
            # here we really do want to consolidate the whole thing.
2359
2386
            for parent_id in other_tree.get_parent_ids():
2397
2424
        tree_transport = self.bzrdir.root_transport.clone(sub_path)
2398
2425
        if tree_transport.base != branch_transport.base:
2399
2426
            tree_bzrdir = format.initialize_on_transport(tree_transport)
2400
 
            branch.BranchReferenceFormat().initialize(tree_bzrdir,
2401
 
                target_branch=new_branch)
 
2427
            tree_bzrdir.set_branch_reference(new_branch)
2402
2428
        else:
2403
2429
            tree_bzrdir = branch_bzrdir
2404
2430
        wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
2405
2431
        wt.set_parent_ids(self.get_parent_ids())
2406
 
        my_inv = self.inventory
 
2432
        # FIXME: Support nested trees
 
2433
        my_inv = self.root_inventory
2407
2434
        child_inv = inventory.Inventory(root_id=None)
2408
2435
        new_root = my_inv[file_id]
2409
2436
        my_inv.remove_recursive_id(file_id)
2429
2456
        if not self.is_locked():
2430
2457
            raise errors.ObjectNotLocked(self)
2431
2458
 
2432
 
        inv = self.inventory
2433
2459
        if from_dir is None and include_root is True:
2434
 
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
 
2460
            yield ('', 'V', 'directory', self.get_root_id(), self.root_inventory.root)
2435
2461
        # Convert these into local objects to save lookup times
2436
2462
        pathjoin = osutils.pathjoin
2437
2463
        file_kind = self._kind
2444
2470
 
2445
2471
        # directory file_id, relative path, absolute path, reverse sorted children
2446
2472
        if from_dir is not None:
2447
 
            from_dir_id = inv.path2id(from_dir)
 
2473
            inv, from_dir_id = self._path2inv_file_id(from_dir)
2448
2474
            if from_dir_id is None:
2449
2475
                # Directory not versioned
2450
2476
                return
2451
2477
            from_dir_abspath = pathjoin(self.basedir, from_dir)
2452
2478
        else:
 
2479
            inv = self.root_inventory
2453
2480
            from_dir_id = inv.root.file_id
2454
2481
            from_dir_abspath = self.basedir
2455
2482
        children = os.listdir(from_dir_abspath)
2578
2605
        rename_entries = []
2579
2606
        rename_tuples = []
2580
2607
 
 
2608
        invs_to_write = set()
 
2609
 
2581
2610
        # check for deprecated use of signature
2582
2611
        if to_dir is None:
2583
2612
            raise TypeError('You must supply a target directory')
2584
2613
        # check destination directory
2585
2614
        if isinstance(from_paths, basestring):
2586
2615
            raise ValueError()
2587
 
        inv = self.inventory
2588
2616
        to_abs = self.abspath(to_dir)
2589
2617
        if not isdir(to_abs):
2590
2618
            raise errors.BzrMoveFailedError('',to_dir,
2592
2620
        if not self.has_filename(to_dir):
2593
2621
            raise errors.BzrMoveFailedError('',to_dir,
2594
2622
                errors.NotInWorkingDirectory(to_dir))
2595
 
        to_dir_id = inv.path2id(to_dir)
 
2623
        to_inv, to_dir_id = self._path2inv_file_id(to_dir)
2596
2624
        if to_dir_id is None:
2597
2625
            raise errors.BzrMoveFailedError('',to_dir,
2598
2626
                errors.NotVersionedError(path=to_dir))
2599
2627
 
2600
 
        to_dir_ie = inv[to_dir_id]
 
2628
        to_dir_ie = to_inv[to_dir_id]
2601
2629
        if to_dir_ie.kind != 'directory':
2602
2630
            raise errors.BzrMoveFailedError('',to_dir,
2603
2631
                errors.NotADirectory(to_abs))
2605
2633
        # create rename entries and tuples
2606
2634
        for from_rel in from_paths:
2607
2635
            from_tail = splitpath(from_rel)[-1]
2608
 
            from_id = inv.path2id(from_rel)
 
2636
            from_inv, from_id = self._path2inv_file_id(from_rel)
2609
2637
            if from_id is None:
2610
2638
                raise errors.BzrMoveFailedError(from_rel,to_dir,
2611
2639
                    errors.NotVersionedError(path=from_rel))
2612
2640
 
2613
 
            from_entry = inv[from_id]
 
2641
            from_entry = from_inv[from_id]
2614
2642
            from_parent_id = from_entry.parent_id
2615
2643
            to_rel = pathjoin(to_dir, from_tail)
2616
2644
            rename_entry = InventoryWorkingTree._RenameEntry(
2635
2663
            # restore the inventory on error
2636
2664
            self._inventory_is_modified = original_modified
2637
2665
            raise
2638
 
        self._write_inventory(inv)
 
2666
        #FIXME: Should potentially also write the from_invs
 
2667
        self._write_inventory(to_inv)
2639
2668
        return rename_tuples
2640
2669
 
2641
2670
    @needs_tree_write_lock
2661
2690
 
2662
2691
        Everything else results in an error.
2663
2692
        """
2664
 
        inv = self.inventory
2665
2693
        rename_entries = []
2666
2694
 
2667
2695
        # create rename entries and tuples
2668
2696
        from_tail = splitpath(from_rel)[-1]
2669
 
        from_id = inv.path2id(from_rel)
 
2697
        from_inv, from_id = self._path2inv_file_id(from_rel)
2670
2698
        if from_id is None:
2671
2699
            # if file is missing in the inventory maybe it's in the basis_tree
2672
2700
            basis_tree = self.branch.basis_tree()
2675
2703
                raise errors.BzrRenameFailedError(from_rel,to_rel,
2676
2704
                    errors.NotVersionedError(path=from_rel))
2677
2705
            # put entry back in the inventory so we can rename it
2678
 
            from_entry = basis_tree.inventory[from_id].copy()
2679
 
            inv.add(from_entry)
 
2706
            from_entry = basis_tree.root_inventory[from_id].copy()
 
2707
            from_inv.add(from_entry)
2680
2708
        else:
2681
 
            from_entry = inv[from_id]
 
2709
            from_inv, from_inv_id = self._unpack_file_id(from_id)
 
2710
            from_entry = from_inv[from_inv_id]
2682
2711
        from_parent_id = from_entry.parent_id
2683
2712
        to_dir, to_tail = os.path.split(to_rel)
2684
 
        to_dir_id = inv.path2id(to_dir)
 
2713
        to_inv, to_dir_id = self._path2inv_file_id(to_dir)
2685
2714
        rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
2686
2715
                                     from_id=from_id,
2687
2716
                                     from_tail=from_tail,
2709
2738
               from_id, from_rel, to_rel, to_dir, to_dir_id)
2710
2739
 
2711
2740
        self._move(rename_entries)
2712
 
        self._write_inventory(inv)
 
2741
        self._write_inventory(to_inv)
2713
2742
 
2714
2743
    class _RenameEntry(object):
2715
2744
        def __init__(self, from_rel, from_id, from_tail, from_parent_id,
2731
2760
 
2732
2761
        Also does basic plausability tests.
2733
2762
        """
2734
 
        inv = self.inventory
 
2763
        # FIXME: Handling of nested trees
 
2764
        inv = self.root_inventory
2735
2765
 
2736
2766
        for rename_entry in rename_entries:
2737
2767
            # store to local variables for easier reference
2782
2812
                # something is wrong, so lets determine what exactly
2783
2813
                if not self.has_filename(from_rel) and \
2784
2814
                   not self.has_filename(to_rel):
2785
 
                    raise errors.BzrRenameFailedError(from_rel,to_rel,
2786
 
                        errors.PathsDoNotExist(paths=(str(from_rel),
2787
 
                        str(to_rel))))
 
2815
                    raise errors.BzrRenameFailedError(from_rel, to_rel,
 
2816
                        errors.PathsDoNotExist(paths=(from_rel, to_rel)))
2788
2817
                else:
2789
2818
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
2790
2819
            rename_entry.only_change_inv = only_change_inv
2796
2825
        Depending on the value of the flag 'only_change_inv', the
2797
2826
        file will be moved on the file system or not.
2798
2827
        """
2799
 
        inv = self.inventory
2800
2828
        moved = []
2801
2829
 
2802
2830
        for entry in rename_entries:
2809
2837
 
2810
2838
    def _rollback_move(self, moved):
2811
2839
        """Try to rollback a previous move in case of an filesystem error."""
2812
 
        inv = self.inventory
2813
2840
        for entry in moved:
2814
2841
            try:
2815
2842
                self._move_entry(WorkingTree._RenameEntry(
2824
2851
                        " Error message is: %s" % e)
2825
2852
 
2826
2853
    def _move_entry(self, entry):
2827
 
        inv = self.inventory
 
2854
        inv = self.root_inventory
2828
2855
        from_rel_abs = self.abspath(entry.from_rel)
2829
2856
        to_rel_abs = self.abspath(entry.to_rel)
2830
2857
        if from_rel_abs == to_rel_abs:
2871
2898
 
2872
2899
    def stored_kind(self, file_id):
2873
2900
        """See Tree.stored_kind"""
2874
 
        return self.inventory[file_id].kind
 
2901
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2902
        return inv[inv_file_id].kind
2875
2903
 
2876
2904
    def extras(self):
2877
2905
        """Yield all unversioned files in this WorkingTree.
2884
2912
        This is the same order used by 'osutils.walkdirs'.
2885
2913
        """
2886
2914
        ## TODO: Work from given directory downwards
2887
 
        for path, dir_entry in self.inventory.directories():
 
2915
        for path, dir_entry in self.iter_entries_by_dir():
 
2916
            if dir_entry.kind != 'directory':
 
2917
                continue
2888
2918
            # mutter("search for unknowns in %r", path)
2889
2919
            dirabs = self.abspath(path)
2890
2920
            if not isdir(dirabs):
2927
2957
        """
2928
2958
        _directory = 'directory'
2929
2959
        # get the root in the inventory
2930
 
        inv = self.inventory
2931
 
        top_id = inv.path2id(prefix)
 
2960
        inv, top_id = self._path2inv_file_id(prefix)
2932
2961
        if top_id is None:
2933
2962
            pending = []
2934
2963
        else:
2955
2984
                if dir[2] == _directory:
2956
2985
                    pending.append(dir)
2957
2986
 
 
2987
    @needs_write_lock
 
2988
    def update_feature_flags(self, updated_flags):
 
2989
        """Update the feature flags for this branch.
 
2990
 
 
2991
        :param updated_flags: Dictionary mapping feature names to necessities
 
2992
            A necessity can be None to indicate the feature should be removed
 
2993
        """
 
2994
        self._format._update_feature_flags(updated_flags)
 
2995
        self.control_transport.put_bytes('format', self._format.as_string())
 
2996
 
2958
2997
 
2959
2998
class WorkingTreeFormatRegistry(controldir.ControlComponentFormatRegistry):
2960
2999
    """Registry for working tree formats."""
3016
3055
 
3017
3056
    supports_versioned_directories = None
3018
3057
 
3019
 
    @classmethod
3020
 
    def find_format_string(klass, a_bzrdir):
3021
 
        """Return format name for the working tree object in a_bzrdir."""
3022
 
        try:
3023
 
            transport = a_bzrdir.get_workingtree_transport(None)
3024
 
            return transport.get_bytes("format")
3025
 
        except errors.NoSuchFile:
3026
 
            raise errors.NoWorkingTree(base=transport.base)
3027
 
 
3028
 
    @classmethod
3029
 
    def find_format(klass, a_bzrdir):
3030
 
        """Return the format for the working tree object in a_bzrdir."""
3031
 
        try:
3032
 
            format_string = klass.find_format_string(a_bzrdir)
3033
 
            return format_registry.get(format_string)
3034
 
        except KeyError:
3035
 
            raise errors.UnknownFormatError(format=format_string,
3036
 
                                            kind="working tree")
3037
 
 
3038
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
 
3058
    def initialize(self, controldir, revision_id=None, from_branch=None,
3039
3059
                   accelerator_tree=None, hardlink=False):
3040
 
        """Initialize a new working tree in a_bzrdir.
 
3060
        """Initialize a new working tree in controldir.
3041
3061
 
3042
 
        :param a_bzrdir: BzrDir to initialize the working tree in.
 
3062
        :param controldir: ControlDir to initialize the working tree in.
3043
3063
        :param revision_id: allows creating a working tree at a different
3044
3064
            revision than the branch is at.
3045
3065
        :param from_branch: Branch to checkout
3065
3085
        """Return the current default format."""
3066
3086
        return format_registry.get_default()
3067
3087
 
3068
 
    def get_format_string(self):
3069
 
        """Return the ASCII format string that identifies this format."""
3070
 
        raise NotImplementedError(self.get_format_string)
3071
 
 
3072
3088
    def get_format_description(self):
3073
3089
        """Return the short description for this format."""
3074
3090
        raise NotImplementedError(self.get_format_description)
3126
3142
    def unregister_format(klass, format):
3127
3143
        format_registry.remove(format)
3128
3144
 
 
3145
    def get_controldir_for_branch(self):
 
3146
        """Get the control directory format for creating branches.
 
3147
 
 
3148
        This is to support testing of working tree formats that can not exist
 
3149
        in the same control directory as a branch.
 
3150
        """
 
3151
        return self._matchingbzrdir
 
3152
 
 
3153
 
 
3154
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
 
3155
    """Base class for working trees that live in bzr meta directories."""
 
3156
 
 
3157
    def __init__(self):
 
3158
        WorkingTreeFormat.__init__(self)
 
3159
        bzrdir.BzrFormat.__init__(self)
 
3160
 
 
3161
    @classmethod
 
3162
    def find_format_string(klass, controldir):
 
3163
        """Return format name for the working tree object in controldir."""
 
3164
        try:
 
3165
            transport = controldir.get_workingtree_transport(None)
 
3166
            return transport.get_bytes("format")
 
3167
        except errors.NoSuchFile:
 
3168
            raise errors.NoWorkingTree(base=transport.base)
 
3169
 
 
3170
    @classmethod
 
3171
    def find_format(klass, controldir):
 
3172
        """Return the format for the working tree object in controldir."""
 
3173
        format_string = klass.find_format_string(controldir)
 
3174
        return klass._find_format(format_registry, 'working tree',
 
3175
                format_string)
 
3176
 
 
3177
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
3178
            basedir=None):
 
3179
        WorkingTreeFormat.check_support_status(self,
 
3180
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
3181
            basedir=basedir)
 
3182
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
3183
            recommend_upgrade=recommend_upgrade, basedir=basedir)
 
3184
 
3129
3185
 
3130
3186
format_registry.register_lazy("Bazaar Working Tree Format 4 (bzr 0.15)\n",
3131
3187
    "bzrlib.workingtree_4", "WorkingTreeFormat4")