~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Jelmer Vernooij
  • Date: 2012-01-27 21:28:56 UTC
  • mto: This revision was merged to the branch mainline in revision 6460.
  • Revision ID: jelmer@samba.org-20120127212856-ewnjgn7fyblphcqw
Migrate mail_client to config stacks.

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
287
268
        """
288
269
        if path is None:
289
270
            path = osutils.getcwd()
290
 
        control = bzrdir.BzrDir.open(path, _unsupported)
291
 
        return control.open_workingtree(_unsupported)
 
271
        control = controldir.ControlDir.open(path, _unsupported=_unsupported)
 
272
        return control.open_workingtree(unsupported=_unsupported)
292
273
 
293
274
    @staticmethod
294
275
    def open_containing(path=None):
305
286
        """
306
287
        if path is None:
307
288
            path = osutils.getcwd()
308
 
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
289
        control, relpath = controldir.ControlDir.open_containing(path)
309
290
        return control.open_workingtree(), relpath
310
291
 
311
292
    @staticmethod
331
312
                if view_files:
332
313
                    file_list = view_files
333
314
                    view_str = views.view_display_str(view_files)
334
 
                    note("Ignoring files outside view. View is %s" % view_str)
 
315
                    note(gettext("Ignoring files outside view. View is %s") % view_str)
335
316
            return tree, file_list
336
317
        if default_directory == u'.':
337
318
            seed = file_list[0]
394
375
            else:
395
376
                return True, tree
396
377
        t = transport.get_transport(location)
397
 
        iterator = bzrdir.BzrDir.find_bzrdirs(t, evaluate=evaluate,
 
378
        iterator = controldir.ControlDir.find_bzrdirs(t, evaluate=evaluate,
398
379
                                              list_current=list_current)
399
380
        return [tr for tr in iterator if tr is not None]
400
381
 
401
 
    def all_file_ids(self):
402
 
        """See Tree.iter_all_file_ids"""
403
 
        raise NotImplementedError(self.all_file_ids)
404
 
 
405
382
    def __repr__(self):
406
383
        return "<%s of %s>" % (self.__class__.__name__,
407
384
                               getattr(self, 'basedir', None))
522
499
        raise NotImplementedError(self.get_root_id)
523
500
 
524
501
    @needs_read_lock
525
 
    def clone(self, to_bzrdir, revision_id=None):
 
502
    def clone(self, to_controldir, revision_id=None):
526
503
        """Duplicate this working tree into to_bzr, including all state.
527
504
 
528
505
        Specifically modified files are kept as modified, but
529
506
        ignored and unknown files are discarded.
530
507
 
531
 
        If you want to make a new line of development, see bzrdir.sprout()
 
508
        If you want to make a new line of development, see ControlDir.sprout()
532
509
 
533
510
        revision
534
511
            If not None, the cloned tree will have its last revision set to
536
513
            and this one merged in.
537
514
        """
538
515
        # assumes the target bzr dir format is compatible.
539
 
        result = to_bzrdir.create_workingtree()
 
516
        result = to_controldir.create_workingtree()
540
517
        self.copy_content_into(result, revision_id)
541
518
        return result
542
519
 
550
527
            # TODO now merge from tree.last_revision to revision (to preserve
551
528
            # user local changes)
552
529
            merge.transform_tree(tree, self)
553
 
            tree.set_parent_ids([revision_id])
 
530
            if revision_id == _mod_revision.NULL_REVISION:
 
531
                new_parents = []
 
532
            else:
 
533
                new_parents = [revision_id]
 
534
            tree.set_parent_ids(new_parents)
554
535
 
555
536
    def id2abspath(self, file_id):
556
537
        return self.abspath(self.id2path(file_id))
595
576
            else:
596
577
                return None
597
578
 
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
579
    @needs_tree_write_lock
603
580
    def _gather_kinds(self, files, kinds):
604
581
        """See MutableTree._gather_kinds."""
769
746
 
770
747
    @needs_tree_write_lock
771
748
    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)
 
749
        """Set the merge modified hashes."""
 
750
        raise NotImplementedError(self.set_merge_modified)
777
751
 
778
752
    def _sha_from_stat(self, path, stat_result):
779
753
        """Get a sha digest from the tree's stat cache.
785
759
        """
786
760
        return None
787
761
 
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
762
    @needs_write_lock # because merge pulls data into the branch.
795
763
    def merge_from_branch(self, branch, to_revision=None, from_revision=None,
796
764
                          merge_type=None, force=False):
1036
1004
                                show_base=show_base)
1037
1005
                    basis_root_id = basis_tree.get_root_id()
1038
1006
                    new_root_id = new_basis_tree.get_root_id()
1039
 
                    if basis_root_id != new_root_id:
 
1007
                    if new_root_id is not None and basis_root_id != new_root_id:
1040
1008
                        self.set_root_id(new_root_id)
1041
1009
                finally:
1042
1010
                    basis_tree.unlock()
1043
1011
                # TODO - dedup parents list with things merged by pull ?
1044
1012
                # reuse the revisiontree we merged against to set the new
1045
1013
                # tree data.
1046
 
                parent_trees = [(self.branch.last_revision(), new_basis_tree)]
 
1014
                parent_trees = []
 
1015
                if self.branch.last_revision() != _mod_revision.NULL_REVISION:
 
1016
                    parent_trees.append(
 
1017
                        (self.branch.last_revision(), new_basis_tree))
1047
1018
                # we have to pull the merge trees out again, because
1048
1019
                # merge_inner has set the ids. - this corner is not yet
1049
1020
                # layered well enough to prevent double handling.
1066
1037
            stream.write(bytes)
1067
1038
        finally:
1068
1039
            stream.close()
1069
 
        # TODO: update the hashcache here ?
1070
1040
 
1071
1041
    def extras(self):
1072
1042
        """Yield all unversioned files in this WorkingTree.
1149
1119
        else:
1150
1120
            mode = stat_value.st_mode
1151
1121
            kind = osutils.file_kind_from_stat_mode(mode)
1152
 
            if not supports_executable():
 
1122
            if not self._supports_executable():
1153
1123
                executable = entry is not None and entry.executable
1154
1124
            else:
1155
1125
                executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1174
1144
        return _mod_revision.ensure_null(self.branch.last_revision())
1175
1145
 
1176
1146
    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)
 
1147
        """Check if this tree is locked."""
 
1148
        raise NotImplementedError(self.is_locked)
1182
1149
 
1183
1150
    def lock_read(self):
1184
1151
        """Lock the tree for reading.
1187
1154
 
1188
1155
        :return: A bzrlib.lock.LogicalLockResult.
1189
1156
        """
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
 
1157
        raise NotImplementedError(self.lock_read)
1199
1158
 
1200
1159
    def lock_tree_write(self):
1201
1160
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
1202
1161
 
1203
1162
        :return: A bzrlib.lock.LogicalLockResult.
1204
1163
        """
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
 
1164
        raise NotImplementedError(self.lock_tree_write)
1214
1165
 
1215
1166
    def lock_write(self):
1216
1167
        """See MutableTree.lock_write, and WorkingTree.unlock.
1217
1168
 
1218
1169
        :return: A bzrlib.lock.LogicalLockResult.
1219
1170
        """
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
 
1171
        raise NotImplementedError(self.lock_write)
1229
1172
 
1230
1173
    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)
 
1174
        raise NotImplementedError(self.get_physical_lock_status)
1236
1175
 
1237
1176
    def set_last_revision(self, new_revision):
1238
1177
        """Change the last revision in the working tree."""
1532
1471
                                             show_base=show_base)
1533
1472
            if nb_conflicts:
1534
1473
                self.add_parent_tree((old_tip, other_tree))
1535
 
                note('Rerun update after fixing the conflicts.')
 
1474
                note(gettext('Rerun update after fixing the conflicts.'))
1536
1475
                return nb_conflicts
1537
1476
 
1538
1477
        if last_rev != _mod_revision.ensure_null(revision):
1580
1519
            last_rev = parent_trees[0][0]
1581
1520
        return nb_conflicts
1582
1521
 
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
1522
    def set_conflicts(self, arg):
1598
1523
        raise errors.UnsupportedOperation(self.set_conflicts, self)
1599
1524
 
1828
1753
        :param branch: A branch to override probing for the branch.
1829
1754
        """
1830
1755
        super(InventoryWorkingTree, self).__init__(basedir=basedir,
1831
 
            branch=branch, _control_files=_control_files, _internal=_internal,
1832
 
            _format=_format, _bzrdir=_bzrdir)
 
1756
            branch=branch, _transport=_control_files._transport,
 
1757
            _internal=_internal, _format=_format, _bzrdir=_bzrdir)
 
1758
 
 
1759
        self._control_files = _control_files
 
1760
        self._detect_case_handling()
1833
1761
 
1834
1762
        if _inventory is None:
1835
1763
            # This will be acquired on lock_read() or lock_write()
1855
1783
        self._inventory = inv
1856
1784
        self._inventory_is_modified = dirty
1857
1785
 
 
1786
    def _detect_case_handling(self):
 
1787
        wt_trans = self.bzrdir.get_workingtree_transport(None)
 
1788
        try:
 
1789
            wt_trans.stat(self._format.case_sensitive_filename)
 
1790
        except errors.NoSuchFile:
 
1791
            self.case_sensitive = True
 
1792
        else:
 
1793
            self.case_sensitive = False
 
1794
 
 
1795
        self._setup_directory_is_tree_reference()
 
1796
 
1858
1797
    def _serialize(self, inventory, out_file):
1859
1798
        xml5.serializer_v5.write_inventory(self._inventory, out_file,
1860
1799
            working=True)
1862
1801
    def _deserialize(selt, in_file):
1863
1802
        return xml5.serializer_v5.read_inventory(in_file)
1864
1803
 
 
1804
    def break_lock(self):
 
1805
        """Break a lock if one is present from another instance.
 
1806
 
 
1807
        Uses the ui factory to ask for confirmation if the lock may be from
 
1808
        an active process.
 
1809
 
 
1810
        This will probe the repository for its lock as well.
 
1811
        """
 
1812
        self._control_files.break_lock()
 
1813
        self.branch.break_lock()
 
1814
 
 
1815
    def is_locked(self):
 
1816
        return self._control_files.is_locked()
 
1817
 
 
1818
    def _must_be_locked(self):
 
1819
        if not self.is_locked():
 
1820
            raise errors.ObjectNotLocked(self)
 
1821
 
 
1822
    def lock_read(self):
 
1823
        """Lock the tree for reading.
 
1824
 
 
1825
        This also locks the branch, and can be unlocked via self.unlock().
 
1826
 
 
1827
        :return: A bzrlib.lock.LogicalLockResult.
 
1828
        """
 
1829
        if not self.is_locked():
 
1830
            self._reset_data()
 
1831
        self.branch.lock_read()
 
1832
        try:
 
1833
            self._control_files.lock_read()
 
1834
            return LogicalLockResult(self.unlock)
 
1835
        except:
 
1836
            self.branch.unlock()
 
1837
            raise
 
1838
 
 
1839
    def lock_tree_write(self):
 
1840
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
1841
 
 
1842
        :return: A bzrlib.lock.LogicalLockResult.
 
1843
        """
 
1844
        if not self.is_locked():
 
1845
            self._reset_data()
 
1846
        self.branch.lock_read()
 
1847
        try:
 
1848
            self._control_files.lock_write()
 
1849
            return LogicalLockResult(self.unlock)
 
1850
        except:
 
1851
            self.branch.unlock()
 
1852
            raise
 
1853
 
 
1854
    def lock_write(self):
 
1855
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
1856
 
 
1857
        :return: A bzrlib.lock.LogicalLockResult.
 
1858
        """
 
1859
        if not self.is_locked():
 
1860
            self._reset_data()
 
1861
        self.branch.lock_write()
 
1862
        try:
 
1863
            self._control_files.lock_write()
 
1864
            return LogicalLockResult(self.unlock)
 
1865
        except:
 
1866
            self.branch.unlock()
 
1867
            raise
 
1868
 
 
1869
    def get_physical_lock_status(self):
 
1870
        return self._control_files.get_physical_lock_status()
 
1871
 
1865
1872
    @needs_tree_write_lock
1866
1873
    def _write_inventory(self, inv):
1867
1874
        """Write inventory as the current inventory."""
1935
1942
            if entry.parent_id == orig_root_id:
1936
1943
                entry.parent_id = inv.root.file_id
1937
1944
 
1938
 
    def all_file_ids(self):
1939
 
        """See Tree.iter_all_file_ids"""
1940
 
        return set(self.inventory)
1941
 
 
1942
1945
    @needs_tree_write_lock
1943
1946
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1944
1947
        """See MutableTree.set_parent_trees."""
2066
2069
 
2067
2070
    def has_id(self, file_id):
2068
2071
        # files that have been deleted are excluded
2069
 
        inv = self.inventory
2070
 
        if not inv.has_id(file_id):
 
2072
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2073
        if not inv.has_id(inv_file_id):
2071
2074
            return False
2072
 
        path = inv.id2path(file_id)
 
2075
        path = inv.id2path(inv_file_id)
2073
2076
        return osutils.lexists(self.abspath(path))
2074
2077
 
2075
2078
    def has_or_had_id(self, file_id):
2076
2079
        if file_id == self.inventory.root.file_id:
2077
2080
            return True
2078
 
        return self.inventory.has_id(file_id)
 
2081
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2082
        return inv.has_id(inv_file_id)
2079
2083
 
2080
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2081
 
    def __iter__(self):
 
2084
    def all_file_ids(self):
2082
2085
        """Iterate through file_ids for this tree.
2083
2086
 
2084
2087
        file_ids are in a WorkingTree if they are in the working inventory
2085
2088
        and the working file exists.
2086
2089
        """
2087
 
        inv = self._inventory
2088
 
        for path, ie in inv.iter_entries():
2089
 
            if osutils.lexists(self.abspath(path)):
2090
 
                yield ie.file_id
 
2090
        ret = set()
 
2091
        for path, ie in self.iter_entries_by_dir():
 
2092
            ret.add(ie.file_id)
 
2093
        return ret
2091
2094
 
2092
2095
    @needs_tree_write_lock
2093
2096
    def set_last_revision(self, new_revision):
2164
2167
            mode=self.bzrdir._get_file_mode())
2165
2168
        self._inventory_is_modified = False
2166
2169
 
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
2170
    def get_file_mtime(self, file_id, path=None):
2174
2171
        """See Tree.get_file_mtime."""
2175
2172
        if not path:
2176
 
            path = self.inventory.id2path(file_id)
2177
 
        return os.lstat(self.abspath(path)).st_mtime
 
2173
            path = self.id2path(file_id)
 
2174
        try:
 
2175
            return os.lstat(self.abspath(path)).st_mtime
 
2176
        except OSError, e:
 
2177
            if e.errno == errno.ENOENT:
 
2178
                raise errors.FileTimestampUnavailable(path)
 
2179
            raise
2178
2180
 
2179
2181
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
2180
 
        file_id = self.path2id(path)
 
2182
        inv, file_id = self._path2inv_file_id(path)
2181
2183
        if file_id is None:
2182
2184
            # For unversioned files on win32, we just assume they are not
2183
2185
            # executable
2184
2186
            return False
2185
 
        return self._inventory[file_id].executable
 
2187
        return inv[file_id].executable
2186
2188
 
2187
2189
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
2188
2190
        mode = stat_result.st_mode
2189
2191
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2190
2192
 
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):
 
2193
    def is_executable(self, file_id, path=None):
 
2194
        if not self._supports_executable():
 
2195
            inv, inv_file_id = self._unpack_file_id(file_id)
 
2196
            return inv[inv_file_id].executable
 
2197
        else:
2199
2198
            if not path:
2200
2199
                path = self.id2path(file_id)
2201
2200
            mode = os.lstat(self.abspath(path)).st_mode
2202
2201
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
2203
2202
 
2204
 
        _is_executable_from_path_and_stat = \
2205
 
            _is_executable_from_path_and_stat_from_stat
 
2203
    def _is_executable_from_path_and_stat(self, path, stat_result):
 
2204
        if not self._supports_executable():
 
2205
            return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
 
2206
        else:
 
2207
            return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
2206
2208
 
2207
2209
    @needs_tree_write_lock
2208
2210
    def _add(self, files, ids, kinds):
2258
2260
                parent_tree = self.branch.repository.revision_tree(parent_id)
2259
2261
            parent_tree.lock_read()
2260
2262
            try:
2261
 
                if not parent_tree.has_id(file_id):
 
2263
                try:
 
2264
                    kind = parent_tree.kind(file_id)
 
2265
                except errors.NoSuchId:
2262
2266
                    continue
2263
 
                ie = parent_tree.inventory[file_id]
2264
 
                if ie.kind != 'file':
 
2267
                if kind != 'file':
2265
2268
                    # Note: this is slightly unnecessary, because symlinks and
2266
2269
                    # directories have a "text" which is the empty text, and we
2267
2270
                    # know that won't mess up annotations. But it seems cleaner
2268
2271
                    continue
2269
 
                parent_text_key = (file_id, ie.revision)
 
2272
                parent_text_key = (
 
2273
                    file_id, parent_tree.get_file_revision(file_id))
2270
2274
                if parent_text_key not in maybe_file_parent_keys:
2271
2275
                    maybe_file_parent_keys.append(parent_text_key)
2272
2276
            finally:
2287
2291
                       for key, line in annotator.annotate_flat(this_key)]
2288
2292
        return annotations
2289
2293
 
 
2294
    def _put_rio(self, filename, stanzas, header):
 
2295
        self._must_be_locked()
 
2296
        my_file = _mod_rio.rio_file(stanzas, header)
 
2297
        self._transport.put_file(filename, my_file,
 
2298
            mode=self.bzrdir._get_file_mode())
 
2299
 
 
2300
    @needs_tree_write_lock
 
2301
    def set_merge_modified(self, modified_hashes):
 
2302
        def iter_stanzas():
 
2303
            for file_id, hash in modified_hashes.iteritems():
 
2304
                yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
 
2305
                    hash=hash)
 
2306
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
 
2307
 
2290
2308
    @needs_read_lock
2291
2309
    def merge_modified(self):
2292
2310
        """Return a dictionary of files modified by a merge.
2312
2330
            for s in _mod_rio.RioReader(hashfile):
2313
2331
                # RioReader reads in Unicode, so convert file_ids back to utf8
2314
2332
                file_id = osutils.safe_file_id(s.get("file_id"), warn=False)
2315
 
                if not self.inventory.has_id(file_id):
 
2333
                if not self.has_id(file_id):
2316
2334
                    continue
2317
2335
                text_hash = s.get("hash")
2318
2336
                if text_hash == self.get_file_sha1(file_id):
2397
2415
        tree_transport = self.bzrdir.root_transport.clone(sub_path)
2398
2416
        if tree_transport.base != branch_transport.base:
2399
2417
            tree_bzrdir = format.initialize_on_transport(tree_transport)
2400
 
            branch.BranchReferenceFormat().initialize(tree_bzrdir,
2401
 
                target_branch=new_branch)
 
2418
            tree_bzrdir.set_branch_reference(new_branch)
2402
2419
        else:
2403
2420
            tree_bzrdir = branch_bzrdir
2404
2421
        wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
2429
2446
        if not self.is_locked():
2430
2447
            raise errors.ObjectNotLocked(self)
2431
2448
 
2432
 
        inv = self.inventory
2433
2449
        if from_dir is None and include_root is True:
2434
 
            yield ('', 'V', 'directory', inv.root.file_id, inv.root)
 
2450
            yield ('', 'V', 'directory', self.get_root_id(), self.inventory.root)
2435
2451
        # Convert these into local objects to save lookup times
2436
2452
        pathjoin = osutils.pathjoin
2437
2453
        file_kind = self._kind
2444
2460
 
2445
2461
        # directory file_id, relative path, absolute path, reverse sorted children
2446
2462
        if from_dir is not None:
2447
 
            from_dir_id = inv.path2id(from_dir)
 
2463
            inv, from_dir_id = self._path2inv_file_id(from_dir)
2448
2464
            if from_dir_id is None:
2449
2465
                # Directory not versioned
2450
2466
                return
2451
2467
            from_dir_abspath = pathjoin(self.basedir, from_dir)
2452
2468
        else:
 
2469
            inv = self.inventory
2453
2470
            from_dir_id = inv.root.file_id
2454
2471
            from_dir_abspath = self.basedir
2455
2472
        children = os.listdir(from_dir_abspath)
2584
2601
        # check destination directory
2585
2602
        if isinstance(from_paths, basestring):
2586
2603
            raise ValueError()
2587
 
        inv = self.inventory
2588
2604
        to_abs = self.abspath(to_dir)
2589
2605
        if not isdir(to_abs):
2590
2606
            raise errors.BzrMoveFailedError('',to_dir,
2592
2608
        if not self.has_filename(to_dir):
2593
2609
            raise errors.BzrMoveFailedError('',to_dir,
2594
2610
                errors.NotInWorkingDirectory(to_dir))
2595
 
        to_dir_id = inv.path2id(to_dir)
 
2611
        to_inv, to_dir_id = self._path2inv_file_id(to_dir)
2596
2612
        if to_dir_id is None:
2597
2613
            raise errors.BzrMoveFailedError('',to_dir,
2598
2614
                errors.NotVersionedError(path=to_dir))
2599
2615
 
2600
 
        to_dir_ie = inv[to_dir_id]
 
2616
        to_dir_ie = to_inv[to_dir_id]
2601
2617
        if to_dir_ie.kind != 'directory':
2602
2618
            raise errors.BzrMoveFailedError('',to_dir,
2603
2619
                errors.NotADirectory(to_abs))
2605
2621
        # create rename entries and tuples
2606
2622
        for from_rel in from_paths:
2607
2623
            from_tail = splitpath(from_rel)[-1]
2608
 
            from_id = inv.path2id(from_rel)
 
2624
            from_inv, from_id = self._path2inv_file_id(from_rel)
2609
2625
            if from_id is None:
2610
2626
                raise errors.BzrMoveFailedError(from_rel,to_dir,
2611
2627
                    errors.NotVersionedError(path=from_rel))
2612
2628
 
2613
 
            from_entry = inv[from_id]
 
2629
            from_entry = from_inv[from_id]
2614
2630
            from_parent_id = from_entry.parent_id
2615
2631
            to_rel = pathjoin(to_dir, from_tail)
2616
2632
            rename_entry = InventoryWorkingTree._RenameEntry(
2635
2651
            # restore the inventory on error
2636
2652
            self._inventory_is_modified = original_modified
2637
2653
            raise
2638
 
        self._write_inventory(inv)
 
2654
        #FIXME: Should potentially also write the from_invs
 
2655
        self._write_inventory(to_inv)
2639
2656
        return rename_tuples
2640
2657
 
2641
2658
    @needs_tree_write_lock
2661
2678
 
2662
2679
        Everything else results in an error.
2663
2680
        """
2664
 
        inv = self.inventory
2665
2681
        rename_entries = []
2666
2682
 
2667
2683
        # create rename entries and tuples
2668
2684
        from_tail = splitpath(from_rel)[-1]
2669
 
        from_id = inv.path2id(from_rel)
 
2685
        from_inv, from_id = self._path2inv_file_id(from_rel)
2670
2686
        if from_id is None:
2671
2687
            # if file is missing in the inventory maybe it's in the basis_tree
2672
2688
            basis_tree = self.branch.basis_tree()
2676
2692
                    errors.NotVersionedError(path=from_rel))
2677
2693
            # put entry back in the inventory so we can rename it
2678
2694
            from_entry = basis_tree.inventory[from_id].copy()
2679
 
            inv.add(from_entry)
 
2695
            from_inv.add(from_entry)
2680
2696
        else:
2681
 
            from_entry = inv[from_id]
 
2697
            from_inv, from_inv_id = self._unpack_file_id(from_id)
 
2698
            from_entry = from_inv[from_inv_id]
2682
2699
        from_parent_id = from_entry.parent_id
2683
2700
        to_dir, to_tail = os.path.split(to_rel)
2684
 
        to_dir_id = inv.path2id(to_dir)
 
2701
        to_inv, to_dir_id = self._path2inv_file_id(to_dir)
2685
2702
        rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
2686
2703
                                     from_id=from_id,
2687
2704
                                     from_tail=from_tail,
2709
2726
               from_id, from_rel, to_rel, to_dir, to_dir_id)
2710
2727
 
2711
2728
        self._move(rename_entries)
2712
 
        self._write_inventory(inv)
 
2729
        self._write_inventory(to_inv)
2713
2730
 
2714
2731
    class _RenameEntry(object):
2715
2732
        def __init__(self, from_rel, from_id, from_tail, from_parent_id,
2782
2799
                # something is wrong, so lets determine what exactly
2783
2800
                if not self.has_filename(from_rel) and \
2784
2801
                   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))))
 
2802
                    raise errors.BzrRenameFailedError(from_rel, to_rel,
 
2803
                        errors.PathsDoNotExist(paths=(from_rel, to_rel)))
2788
2804
                else:
2789
2805
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
2790
2806
            rename_entry.only_change_inv = only_change_inv
2796
2812
        Depending on the value of the flag 'only_change_inv', the
2797
2813
        file will be moved on the file system or not.
2798
2814
        """
2799
 
        inv = self.inventory
2800
2815
        moved = []
2801
2816
 
2802
2817
        for entry in rename_entries:
2809
2824
 
2810
2825
    def _rollback_move(self, moved):
2811
2826
        """Try to rollback a previous move in case of an filesystem error."""
2812
 
        inv = self.inventory
2813
2827
        for entry in moved:
2814
2828
            try:
2815
2829
                self._move_entry(WorkingTree._RenameEntry(
2871
2885
 
2872
2886
    def stored_kind(self, file_id):
2873
2887
        """See Tree.stored_kind"""
2874
 
        return self.inventory[file_id].kind
 
2888
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2889
        return inv[inv_file_id].kind
2875
2890
 
2876
2891
    def extras(self):
2877
2892
        """Yield all unversioned files in this WorkingTree.
2927
2942
        """
2928
2943
        _directory = 'directory'
2929
2944
        # get the root in the inventory
2930
 
        inv = self.inventory
2931
 
        top_id = inv.path2id(prefix)
 
2945
        inv, top_id = self._path2inv_file_id(prefix)
2932
2946
        if top_id is None:
2933
2947
            pending = []
2934
2948
        else:
2955
2969
                if dir[2] == _directory:
2956
2970
                    pending.append(dir)
2957
2971
 
 
2972
    @needs_write_lock
 
2973
    def update_feature_flags(self, updated_flags):
 
2974
        """Update the feature flags for this branch.
 
2975
 
 
2976
        :param updated_flags: Dictionary mapping feature names to necessities
 
2977
            A necessity can be None to indicate the feature should be removed
 
2978
        """
 
2979
        self._format._update_feature_flags(updated_flags)
 
2980
        self.control_transport.put_bytes('format', self._format.as_string())
 
2981
 
2958
2982
 
2959
2983
class WorkingTreeFormatRegistry(controldir.ControlComponentFormatRegistry):
2960
2984
    """Registry for working tree formats."""
3016
3040
 
3017
3041
    supports_versioned_directories = None
3018
3042
 
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
 
            format_string = bzrdir.extract_format_string(format_string)
3034
 
            return format_registry.get(format_string)
3035
 
        except KeyError:
3036
 
            raise errors.UnknownFormatError(format=format_string,
3037
 
                                            kind="working tree")
3038
 
 
3039
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
 
3043
    def initialize(self, controldir, revision_id=None, from_branch=None,
3040
3044
                   accelerator_tree=None, hardlink=False):
3041
 
        """Initialize a new working tree in a_bzrdir.
 
3045
        """Initialize a new working tree in controldir.
3042
3046
 
3043
 
        :param a_bzrdir: BzrDir to initialize the working tree in.
 
3047
        :param controldir: ControlDir to initialize the working tree in.
3044
3048
        :param revision_id: allows creating a working tree at a different
3045
3049
            revision than the branch is at.
3046
3050
        :param from_branch: Branch to checkout
3066
3070
        """Return the current default format."""
3067
3071
        return format_registry.get_default()
3068
3072
 
3069
 
    def get_format_string(self):
3070
 
        """Return the ASCII format string that identifies this format."""
3071
 
        raise NotImplementedError(self.get_format_string)
3072
 
 
3073
3073
    def get_format_description(self):
3074
3074
        """Return the short description for this format."""
3075
3075
        raise NotImplementedError(self.get_format_description)
3127
3127
    def unregister_format(klass, format):
3128
3128
        format_registry.remove(format)
3129
3129
 
 
3130
    def get_controldir_for_branch(self):
 
3131
        """Get the control directory format for creating branches.
 
3132
 
 
3133
        This is to support testing of working tree formats that can not exist
 
3134
        in the same control directory as a branch.
 
3135
        """
 
3136
        return self._matchingbzrdir
 
3137
 
 
3138
 
 
3139
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
 
3140
    """Base class for working trees that live in bzr meta directories."""
 
3141
 
 
3142
    def __init__(self):
 
3143
        WorkingTreeFormat.__init__(self)
 
3144
        bzrdir.BzrFormat.__init__(self)
 
3145
 
 
3146
    @classmethod
 
3147
    def find_format_string(klass, controldir):
 
3148
        """Return format name for the working tree object in controldir."""
 
3149
        try:
 
3150
            transport = controldir.get_workingtree_transport(None)
 
3151
            return transport.get_bytes("format")
 
3152
        except errors.NoSuchFile:
 
3153
            raise errors.NoWorkingTree(base=transport.base)
 
3154
 
 
3155
    @classmethod
 
3156
    def find_format(klass, controldir):
 
3157
        """Return the format for the working tree object in controldir."""
 
3158
        format_string = klass.find_format_string(controldir)
 
3159
        return klass._find_format(format_registry, 'working tree',
 
3160
                format_string)
 
3161
 
 
3162
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
3163
            basedir=None):
 
3164
        WorkingTreeFormat.check_support_status(self,
 
3165
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
3166
            basedir=basedir)
 
3167
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
3168
            recommend_upgrade=recommend_upgrade, basedir=basedir)
 
3169
 
3130
3170
 
3131
3171
format_registry.register_lazy("Bazaar Working Tree Format 4 (bzr 0.15)\n",
3132
3172
    "bzrlib.workingtree_4", "WorkingTreeFormat4")