/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/bzrworkingtree.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-07 01:17:16 UTC
  • mto: (6653.3.7 bzrwt)
  • mto: This revision was merged to the branch mainline in revision 6668.
  • Revision ID: jelmer@jelmer.uk-20170607011716-hsqdox2pyaqcm4kn
Fix some more tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""InventoryWorkingTree object and friends.
 
18
 
 
19
A WorkingTree represents the editable working copy of a branch.
 
20
Operations which represent the WorkingTree are also done here,
 
21
such as renaming or adding files.  The WorkingTree has an inventory
 
22
which is updated by these operations.  A commit produces a
 
23
new revision based on the workingtree and its inventory.
 
24
 
 
25
At the moment every WorkingTree has its own branch.  Remote
 
26
WorkingTrees aren't supported.
 
27
 
 
28
To get a WorkingTree, call bzrdir.open_workingtree() or
 
29
WorkingTree.open(dir).
 
30
"""
 
31
 
 
32
 
 
33
 
 
34
from __future__ import absolute_import
 
35
 
 
36
import collections
 
37
import os
 
38
import stat
 
39
 
 
40
# Explicitly import breezy.bzrdir so that the BzrProber
 
41
# is guaranteed to be registered.
 
42
from . import bzrdir
 
43
 
 
44
from . import lazy_import
 
45
lazy_import.lazy_import(globals(), """
 
46
from breezy import (
 
47
    cache_utf8,
 
48
    conflicts as _mod_conflicts,
 
49
    errors,
 
50
    graph as _mod_graph,
 
51
    inventory,
 
52
    mutabletree,
 
53
    osutils,
 
54
    revision as _mod_revision,
 
55
    revisiontree,
 
56
    rio as _mod_rio,
 
57
    transport,
 
58
    xml5,
 
59
    xml7,
 
60
    )
 
61
""")
 
62
 
 
63
from .decorators import needs_write_lock, needs_read_lock
 
64
from .lock import _RelockDebugMixin, LogicalLockResult
 
65
from .mutabletree import needs_tree_write_lock
 
66
from .sixish import (
 
67
    BytesIO,
 
68
    )
 
69
from .trace import mutter
 
70
from .workingtree import (
 
71
    TreeDirectory,
 
72
    TreeFile,
 
73
    TreeLink,
 
74
    WorkingTree,
 
75
    WorkingTreeFormat,
 
76
    format_registry,
 
77
    )
 
78
 
 
79
 
 
80
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
 
81
# TODO: Modifying the conflict objects or their type is currently nearly
 
82
# impossible as there is no clear relationship between the working tree format
 
83
# and the conflict list file format.
 
84
CONFLICT_HEADER_1 = "BZR conflict list format 1"
 
85
 
 
86
 
 
87
class InventoryWorkingTree(WorkingTree,
 
88
        mutabletree.MutableInventoryTree):
 
89
    """Base class for working trees that are inventory-oriented.
 
90
 
 
91
    The inventory is held in the `Branch` working-inventory, and the
 
92
    files are in a directory on disk.
 
93
 
 
94
    It is possible for a `WorkingTree` to have a filename which is
 
95
    not listed in the Inventory and vice versa.
 
96
    """
 
97
 
 
98
    def __init__(self, basedir='.',
 
99
                 branch=None,
 
100
                 _inventory=None,
 
101
                 _control_files=None,
 
102
                 _internal=False,
 
103
                 _format=None,
 
104
                 _bzrdir=None):
 
105
        """Construct a InventoryWorkingTree instance. This is not a public API.
 
106
 
 
107
        :param branch: A branch to override probing for the branch.
 
108
        """
 
109
        super(InventoryWorkingTree, self).__init__(basedir=basedir,
 
110
            branch=branch, _transport=_control_files._transport,
 
111
            _internal=_internal, _format=_format, _bzrdir=_bzrdir)
 
112
 
 
113
        self._control_files = _control_files
 
114
        self._detect_case_handling()
 
115
 
 
116
        if _inventory is None:
 
117
            # This will be acquired on lock_read() or lock_write()
 
118
            self._inventory_is_modified = False
 
119
            self._inventory = None
 
120
        else:
 
121
            # the caller of __init__ has provided an inventory,
 
122
            # we assume they know what they are doing - as its only
 
123
            # the Format factory and creation methods that are
 
124
            # permitted to do this.
 
125
            self._set_inventory(_inventory, dirty=False)
 
126
 
 
127
    def _set_inventory(self, inv, dirty):
 
128
        """Set the internal cached inventory.
 
129
 
 
130
        :param inv: The inventory to set.
 
131
        :param dirty: A boolean indicating whether the inventory is the same
 
132
            logical inventory as whats on disk. If True the inventory is not
 
133
            the same and should be written to disk or data will be lost, if
 
134
            False then the inventory is the same as that on disk and any
 
135
            serialisation would be unneeded overhead.
 
136
        """
 
137
        self._inventory = inv
 
138
        self._inventory_is_modified = dirty
 
139
 
 
140
    def _detect_case_handling(self):
 
141
        wt_trans = self.bzrdir.get_workingtree_transport(None)
 
142
        try:
 
143
            wt_trans.stat(self._format.case_sensitive_filename)
 
144
        except errors.NoSuchFile:
 
145
            self.case_sensitive = True
 
146
        else:
 
147
            self.case_sensitive = False
 
148
 
 
149
        self._setup_directory_is_tree_reference()
 
150
 
 
151
    def _serialize(self, inventory, out_file):
 
152
        xml5.serializer_v5.write_inventory(self._inventory, out_file,
 
153
            working=True)
 
154
 
 
155
    def _deserialize(selt, in_file):
 
156
        return xml5.serializer_v5.read_inventory(in_file)
 
157
 
 
158
    def break_lock(self):
 
159
        """Break a lock if one is present from another instance.
 
160
 
 
161
        Uses the ui factory to ask for confirmation if the lock may be from
 
162
        an active process.
 
163
 
 
164
        This will probe the repository for its lock as well.
 
165
        """
 
166
        self._control_files.break_lock()
 
167
        self.branch.break_lock()
 
168
 
 
169
    def is_locked(self):
 
170
        return self._control_files.is_locked()
 
171
 
 
172
    def _must_be_locked(self):
 
173
        if not self.is_locked():
 
174
            raise errors.ObjectNotLocked(self)
 
175
 
 
176
    def lock_read(self):
 
177
        """Lock the tree for reading.
 
178
 
 
179
        This also locks the branch, and can be unlocked via self.unlock().
 
180
 
 
181
        :return: A breezy.lock.LogicalLockResult.
 
182
        """
 
183
        if not self.is_locked():
 
184
            self._reset_data()
 
185
        self.branch.lock_read()
 
186
        try:
 
187
            self._control_files.lock_read()
 
188
            return LogicalLockResult(self.unlock)
 
189
        except:
 
190
            self.branch.unlock()
 
191
            raise
 
192
 
 
193
    def lock_tree_write(self):
 
194
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
195
 
 
196
        :return: A breezy.lock.LogicalLockResult.
 
197
        """
 
198
        if not self.is_locked():
 
199
            self._reset_data()
 
200
        self.branch.lock_read()
 
201
        try:
 
202
            self._control_files.lock_write()
 
203
            return LogicalLockResult(self.unlock)
 
204
        except:
 
205
            self.branch.unlock()
 
206
            raise
 
207
 
 
208
    def lock_write(self):
 
209
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
210
 
 
211
        :return: A breezy.lock.LogicalLockResult.
 
212
        """
 
213
        if not self.is_locked():
 
214
            self._reset_data()
 
215
        self.branch.lock_write()
 
216
        try:
 
217
            self._control_files.lock_write()
 
218
            return LogicalLockResult(self.unlock)
 
219
        except:
 
220
            self.branch.unlock()
 
221
            raise
 
222
 
 
223
    def get_physical_lock_status(self):
 
224
        return self._control_files.get_physical_lock_status()
 
225
 
 
226
    @needs_tree_write_lock
 
227
    def _write_inventory(self, inv):
 
228
        """Write inventory as the current inventory."""
 
229
        self._set_inventory(inv, dirty=True)
 
230
        self.flush()
 
231
 
 
232
    # XXX: This method should be deprecated in favour of taking in a proper
 
233
    # new Inventory object.
 
234
    @needs_tree_write_lock
 
235
    def set_inventory(self, new_inventory_list):
 
236
        from .inventory import (
 
237
            Inventory,
 
238
            InventoryDirectory,
 
239
            InventoryFile,
 
240
            InventoryLink)
 
241
        inv = Inventory(self.get_root_id())
 
242
        for path, file_id, parent, kind in new_inventory_list:
 
243
            name = os.path.basename(path)
 
244
            if name == "":
 
245
                continue
 
246
            # fixme, there should be a factory function inv,add_??
 
247
            if kind == 'directory':
 
248
                inv.add(InventoryDirectory(file_id, name, parent))
 
249
            elif kind == 'file':
 
250
                inv.add(InventoryFile(file_id, name, parent))
 
251
            elif kind == 'symlink':
 
252
                inv.add(InventoryLink(file_id, name, parent))
 
253
            else:
 
254
                raise errors.BzrError("unknown kind %r" % kind)
 
255
        self._write_inventory(inv)
 
256
 
 
257
    def _write_basis_inventory(self, xml):
 
258
        """Write the basis inventory XML to the basis-inventory file"""
 
259
        path = self._basis_inventory_name()
 
260
        sio = BytesIO(xml)
 
261
        self._transport.put_file(path, sio,
 
262
            mode=self.bzrdir._get_file_mode())
 
263
 
 
264
    def _reset_data(self):
 
265
        """Reset transient data that cannot be revalidated."""
 
266
        self._inventory_is_modified = False
 
267
        f = self._transport.get('inventory')
 
268
        try:
 
269
            result = self._deserialize(f)
 
270
        finally:
 
271
            f.close()
 
272
        self._set_inventory(result, dirty=False)
 
273
 
 
274
    def _set_root_id(self, file_id):
 
275
        """Set the root id for this tree, in a format specific manner.
 
276
 
 
277
        :param file_id: The file id to assign to the root. It must not be
 
278
            present in the current inventory or an error will occur. It must
 
279
            not be None, but rather a valid file id.
 
280
        """
 
281
        inv = self._inventory
 
282
        orig_root_id = inv.root.file_id
 
283
        # TODO: it might be nice to exit early if there was nothing
 
284
        # to do, saving us from trigger a sync on unlock.
 
285
        self._inventory_is_modified = True
 
286
        # we preserve the root inventory entry object, but
 
287
        # unlinkit from the byid index
 
288
        del inv._byid[inv.root.file_id]
 
289
        inv.root.file_id = file_id
 
290
        # and link it into the index with the new changed id.
 
291
        inv._byid[inv.root.file_id] = inv.root
 
292
        # and finally update all children to reference the new id.
 
293
        # XXX: this should be safe to just look at the root.children
 
294
        # list, not the WHOLE INVENTORY.
 
295
        for fid in inv:
 
296
            entry = inv[fid]
 
297
            if entry.parent_id == orig_root_id:
 
298
                entry.parent_id = inv.root.file_id
 
299
 
 
300
    @needs_tree_write_lock
 
301
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
 
302
        """See MutableTree.set_parent_trees."""
 
303
        parent_ids = [rev for (rev, tree) in parents_list]
 
304
        for revision_id in parent_ids:
 
305
            _mod_revision.check_not_reserved_id(revision_id)
 
306
 
 
307
        self._check_parents_for_ghosts(parent_ids,
 
308
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
309
 
 
310
        parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
 
311
 
 
312
        if len(parent_ids) == 0:
 
313
            leftmost_parent_id = _mod_revision.NULL_REVISION
 
314
            leftmost_parent_tree = None
 
315
        else:
 
316
            leftmost_parent_id, leftmost_parent_tree = parents_list[0]
 
317
 
 
318
        if self._change_last_revision(leftmost_parent_id):
 
319
            if leftmost_parent_tree is None:
 
320
                # If we don't have a tree, fall back to reading the
 
321
                # parent tree from the repository.
 
322
                self._cache_basis_inventory(leftmost_parent_id)
 
323
            else:
 
324
                inv = leftmost_parent_tree.root_inventory
 
325
                xml = self._create_basis_xml_from_inventory(
 
326
                                        leftmost_parent_id, inv)
 
327
                self._write_basis_inventory(xml)
 
328
        self._set_merges_from_parent_ids(parent_ids)
 
329
 
 
330
    def _cache_basis_inventory(self, new_revision):
 
331
        """Cache new_revision as the basis inventory."""
 
332
        # TODO: this should allow the ready-to-use inventory to be passed in,
 
333
        # as commit already has that ready-to-use [while the format is the
 
334
        # same, that is].
 
335
        try:
 
336
            # this double handles the inventory - unpack and repack -
 
337
            # but is easier to understand. We can/should put a conditional
 
338
            # in here based on whether the inventory is in the latest format
 
339
            # - perhaps we should repack all inventories on a repository
 
340
            # upgrade ?
 
341
            # the fast path is to copy the raw xml from the repository. If the
 
342
            # xml contains 'revision_id="', then we assume the right
 
343
            # revision_id is set. We must check for this full string, because a
 
344
            # root node id can legitimately look like 'revision_id' but cannot
 
345
            # contain a '"'.
 
346
            xml = self.branch.repository._get_inventory_xml(new_revision)
 
347
            firstline = xml.split('\n', 1)[0]
 
348
            if (not 'revision_id="' in firstline or
 
349
                'format="7"' not in firstline):
 
350
                inv = self.branch.repository._serializer.read_inventory_from_string(
 
351
                    xml, new_revision)
 
352
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
 
353
            self._write_basis_inventory(xml)
 
354
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
 
355
            pass
 
356
 
 
357
    def _basis_inventory_name(self):
 
358
        return 'basis-inventory-cache'
 
359
 
 
360
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
 
361
        """Create the text that will be saved in basis-inventory"""
 
362
        inventory.revision_id = revision_id
 
363
        return xml7.serializer_v7.write_inventory_to_string(inventory)
 
364
 
 
365
    @needs_tree_write_lock
 
366
    def set_conflicts(self, conflicts):
 
367
        self._put_rio('conflicts', conflicts.to_stanzas(),
 
368
                      CONFLICT_HEADER_1)
 
369
 
 
370
    @needs_tree_write_lock
 
371
    def add_conflicts(self, new_conflicts):
 
372
        conflict_set = set(self.conflicts())
 
373
        conflict_set.update(set(list(new_conflicts)))
 
374
        self.set_conflicts(_mod_conflicts.ConflictList(sorted(conflict_set,
 
375
                                       key=_mod_conflicts.Conflict.sort_key)))
 
376
 
 
377
    @needs_read_lock
 
378
    def conflicts(self):
 
379
        try:
 
380
            confile = self._transport.get('conflicts')
 
381
        except errors.NoSuchFile:
 
382
            return _mod_conflicts.ConflictList()
 
383
        try:
 
384
            try:
 
385
                if next(confile) != CONFLICT_HEADER_1 + '\n':
 
386
                    raise errors.ConflictFormatError()
 
387
            except StopIteration:
 
388
                raise errors.ConflictFormatError()
 
389
            reader = _mod_rio.RioReader(confile)
 
390
            return _mod_conflicts.ConflictList.from_stanzas(reader)
 
391
        finally:
 
392
            confile.close()
 
393
 
 
394
    def read_basis_inventory(self):
 
395
        """Read the cached basis inventory."""
 
396
        path = self._basis_inventory_name()
 
397
        return self._transport.get_bytes(path)
 
398
 
 
399
    @needs_read_lock
 
400
    def read_working_inventory(self):
 
401
        """Read the working inventory.
 
402
 
 
403
        :raises errors.InventoryModified: read_working_inventory will fail
 
404
            when the current in memory inventory has been modified.
 
405
        """
 
406
        # conceptually this should be an implementation detail of the tree.
 
407
        # XXX: Deprecate this.
 
408
        # ElementTree does its own conversion from UTF-8, so open in
 
409
        # binary.
 
410
        if self._inventory_is_modified:
 
411
            raise errors.InventoryModified(self)
 
412
        f = self._transport.get('inventory')
 
413
        try:
 
414
            result = self._deserialize(f)
 
415
        finally:
 
416
            f.close()
 
417
        self._set_inventory(result, dirty=False)
 
418
        return result
 
419
 
 
420
    @needs_read_lock
 
421
    def get_root_id(self):
 
422
        """Return the id of this trees root"""
 
423
        return self._inventory.root.file_id
 
424
 
 
425
    def has_id(self, file_id):
 
426
        # files that have been deleted are excluded
 
427
        inv, inv_file_id = self._unpack_file_id(file_id)
 
428
        if not inv.has_id(inv_file_id):
 
429
            return False
 
430
        path = inv.id2path(inv_file_id)
 
431
        return osutils.lexists(self.abspath(path))
 
432
 
 
433
    def has_or_had_id(self, file_id):
 
434
        if file_id == self.get_root_id():
 
435
            return True
 
436
        inv, inv_file_id = self._unpack_file_id(file_id)
 
437
        return inv.has_id(inv_file_id)
 
438
 
 
439
    def all_file_ids(self):
 
440
        """Iterate through file_ids for this tree.
 
441
 
 
442
        file_ids are in a WorkingTree if they are in the working inventory
 
443
        and the working file exists.
 
444
        """
 
445
        ret = set()
 
446
        for path, ie in self.iter_entries_by_dir():
 
447
            ret.add(ie.file_id)
 
448
        return ret
 
449
 
 
450
    @needs_tree_write_lock
 
451
    def set_last_revision(self, new_revision):
 
452
        """Change the last revision in the working tree."""
 
453
        if self._change_last_revision(new_revision):
 
454
            self._cache_basis_inventory(new_revision)
 
455
 
 
456
    def _get_check_refs(self):
 
457
        """Return the references needed to perform a check of this tree.
 
458
        
 
459
        The default implementation returns no refs, and is only suitable for
 
460
        trees that have no local caching and can commit on ghosts at any time.
 
461
 
 
462
        :seealso: breezy.check for details about check_refs.
 
463
        """
 
464
        return []
 
465
 
 
466
    @needs_read_lock
 
467
    def _check(self, references):
 
468
        """Check the tree for consistency.
 
469
 
 
470
        :param references: A dict with keys matching the items returned by
 
471
            self._get_check_refs(), and values from looking those keys up in
 
472
            the repository.
 
473
        """
 
474
        tree_basis = self.basis_tree()
 
475
        tree_basis.lock_read()
 
476
        try:
 
477
            repo_basis = references[('trees', self.last_revision())]
 
478
            if len(list(repo_basis.iter_changes(tree_basis))) > 0:
 
479
                raise errors.BzrCheckError(
 
480
                    "Mismatched basis inventory content.")
 
481
            self._validate()
 
482
        finally:
 
483
            tree_basis.unlock()
 
484
 
 
485
    @needs_read_lock
 
486
    def check_state(self):
 
487
        """Check that the working state is/isn't valid."""
 
488
        check_refs = self._get_check_refs()
 
489
        refs = {}
 
490
        for ref in check_refs:
 
491
            kind, value = ref
 
492
            if kind == 'trees':
 
493
                refs[ref] = self.branch.repository.revision_tree(value)
 
494
        self._check(refs)
 
495
 
 
496
    @needs_tree_write_lock
 
497
    def reset_state(self, revision_ids=None):
 
498
        """Reset the state of the working tree.
 
499
 
 
500
        This does a hard-reset to a last-known-good state. This is a way to
 
501
        fix if something got corrupted (like the .bzr/checkout/dirstate file)
 
502
        """
 
503
        if revision_ids is None:
 
504
            revision_ids = self.get_parent_ids()
 
505
        if not revision_ids:
 
506
            rt = self.branch.repository.revision_tree(
 
507
                _mod_revision.NULL_REVISION)
 
508
        else:
 
509
            rt = self.branch.repository.revision_tree(revision_ids[0])
 
510
        self._write_inventory(rt.root_inventory)
 
511
        self.set_parent_ids(revision_ids)
 
512
 
 
513
    def flush(self):
 
514
        """Write the in memory inventory to disk."""
 
515
        # TODO: Maybe this should only write on dirty ?
 
516
        if self._control_files._lock_mode != 'w':
 
517
            raise errors.NotWriteLocked(self)
 
518
        sio = BytesIO()
 
519
        self._serialize(self._inventory, sio)
 
520
        sio.seek(0)
 
521
        self._transport.put_file('inventory', sio,
 
522
            mode=self.bzrdir._get_file_mode())
 
523
        self._inventory_is_modified = False
 
524
 
 
525
    def get_file_mtime(self, file_id, path=None):
 
526
        """See Tree.get_file_mtime."""
 
527
        if not path:
 
528
            path = self.id2path(file_id)
 
529
        try:
 
530
            return os.lstat(self.abspath(path)).st_mtime
 
531
        except OSError as e:
 
532
            if e.errno == errno.ENOENT:
 
533
                raise errors.FileTimestampUnavailable(path)
 
534
            raise
 
535
 
 
536
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
 
537
        inv, file_id = self._path2inv_file_id(path)
 
538
        if file_id is None:
 
539
            # For unversioned files on win32, we just assume they are not
 
540
            # executable
 
541
            return False
 
542
        return inv[file_id].executable
 
543
 
 
544
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
 
545
        mode = stat_result.st_mode
 
546
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
547
 
 
548
    def is_executable(self, file_id, path=None):
 
549
        if not self._supports_executable():
 
550
            inv, inv_file_id = self._unpack_file_id(file_id)
 
551
            return inv[inv_file_id].executable
 
552
        else:
 
553
            if not path:
 
554
                path = self.id2path(file_id)
 
555
            mode = os.lstat(self.abspath(path)).st_mode
 
556
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
557
 
 
558
    def _is_executable_from_path_and_stat(self, path, stat_result):
 
559
        if not self._supports_executable():
 
560
            return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
 
561
        else:
 
562
            return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
 
563
 
 
564
    @needs_tree_write_lock
 
565
    def _add(self, files, ids, kinds):
 
566
        """See MutableTree._add."""
 
567
        # TODO: Re-adding a file that is removed in the working copy
 
568
        # should probably put it back with the previous ID.
 
569
        # the read and write working inventory should not occur in this
 
570
        # function - they should be part of lock_write and unlock.
 
571
        # FIXME: nested trees
 
572
        inv = self.root_inventory
 
573
        for f, file_id, kind in zip(files, ids, kinds):
 
574
            if file_id is None:
 
575
                inv.add_path(f, kind=kind)
 
576
            else:
 
577
                inv.add_path(f, kind=kind, file_id=file_id)
 
578
            self._inventory_is_modified = True
 
579
 
 
580
    def revision_tree(self, revision_id):
 
581
        """See WorkingTree.revision_id."""
 
582
        if revision_id == self.last_revision():
 
583
            try:
 
584
                xml = self.read_basis_inventory()
 
585
            except errors.NoSuchFile:
 
586
                pass
 
587
            else:
 
588
                try:
 
589
                    inv = xml7.serializer_v7.read_inventory_from_string(xml)
 
590
                    # dont use the repository revision_tree api because we want
 
591
                    # to supply the inventory.
 
592
                    if inv.revision_id == revision_id:
 
593
                        return revisiontree.InventoryRevisionTree(
 
594
                            self.branch.repository, inv, revision_id)
 
595
                except errors.BadInventoryFormat:
 
596
                    pass
 
597
        # raise if there was no inventory, or if we read the wrong inventory.
 
598
        raise errors.NoSuchRevisionInTree(self, revision_id)
 
599
 
 
600
    @needs_read_lock
 
601
    def annotate_iter(self, file_id,
 
602
                      default_revision=_mod_revision.CURRENT_REVISION):
 
603
        """See Tree.annotate_iter
 
604
 
 
605
        This implementation will use the basis tree implementation if possible.
 
606
        Lines not in the basis are attributed to CURRENT_REVISION
 
607
 
 
608
        If there are pending merges, lines added by those merges will be
 
609
        incorrectly attributed to CURRENT_REVISION (but after committing, the
 
610
        attribution will be correct).
 
611
        """
 
612
        maybe_file_parent_keys = []
 
613
        for parent_id in self.get_parent_ids():
 
614
            try:
 
615
                parent_tree = self.revision_tree(parent_id)
 
616
            except errors.NoSuchRevisionInTree:
 
617
                parent_tree = self.branch.repository.revision_tree(parent_id)
 
618
            parent_tree.lock_read()
 
619
            try:
 
620
                try:
 
621
                    kind = parent_tree.kind(file_id)
 
622
                except errors.NoSuchId:
 
623
                    continue
 
624
                if kind != 'file':
 
625
                    # Note: this is slightly unnecessary, because symlinks and
 
626
                    # directories have a "text" which is the empty text, and we
 
627
                    # know that won't mess up annotations. But it seems cleaner
 
628
                    continue
 
629
                parent_text_key = (
 
630
                    file_id, parent_tree.get_file_revision(file_id))
 
631
                if parent_text_key not in maybe_file_parent_keys:
 
632
                    maybe_file_parent_keys.append(parent_text_key)
 
633
            finally:
 
634
                parent_tree.unlock()
 
635
        graph = _mod_graph.Graph(self.branch.repository.texts)
 
636
        heads = graph.heads(maybe_file_parent_keys)
 
637
        file_parent_keys = []
 
638
        for key in maybe_file_parent_keys:
 
639
            if key in heads:
 
640
                file_parent_keys.append(key)
 
641
 
 
642
        # Now we have the parents of this content
 
643
        annotator = self.branch.repository.texts.get_annotator()
 
644
        text = self.get_file_text(file_id)
 
645
        this_key =(file_id, default_revision)
 
646
        annotator.add_special_text(this_key, file_parent_keys, text)
 
647
        annotations = [(key[-1], line)
 
648
                       for key, line in annotator.annotate_flat(this_key)]
 
649
        return annotations
 
650
 
 
651
    def _put_rio(self, filename, stanzas, header):
 
652
        self._must_be_locked()
 
653
        my_file = _mod_rio.rio_file(stanzas, header)
 
654
        self._transport.put_file(filename, my_file,
 
655
            mode=self.bzrdir._get_file_mode())
 
656
 
 
657
    @needs_tree_write_lock
 
658
    def set_merge_modified(self, modified_hashes):
 
659
        def iter_stanzas():
 
660
            for file_id in modified_hashes:
 
661
                yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
 
662
                    hash=modified_hashes[file_id])
 
663
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
 
664
 
 
665
    @needs_read_lock
 
666
    def merge_modified(self):
 
667
        """Return a dictionary of files modified by a merge.
 
668
 
 
669
        The list is initialized by WorkingTree.set_merge_modified, which is
 
670
        typically called after we make some automatic updates to the tree
 
671
        because of a merge.
 
672
 
 
673
        This returns a map of file_id->sha1, containing only files which are
 
674
        still in the working inventory and have that text hash.
 
675
        """
 
676
        try:
 
677
            hashfile = self._transport.get('merge-hashes')
 
678
        except errors.NoSuchFile:
 
679
            return {}
 
680
        try:
 
681
            merge_hashes = {}
 
682
            try:
 
683
                if next(hashfile) != MERGE_MODIFIED_HEADER_1 + '\n':
 
684
                    raise errors.MergeModifiedFormatError()
 
685
            except StopIteration:
 
686
                raise errors.MergeModifiedFormatError()
 
687
            for s in _mod_rio.RioReader(hashfile):
 
688
                # RioReader reads in Unicode, so convert file_ids back to utf8
 
689
                file_id = cache_utf8.encode(s.get("file_id"))
 
690
                if not self.has_id(file_id):
 
691
                    continue
 
692
                text_hash = s.get("hash")
 
693
                if text_hash == self.get_file_sha1(file_id):
 
694
                    merge_hashes[file_id] = text_hash
 
695
            return merge_hashes
 
696
        finally:
 
697
            hashfile.close()
 
698
 
 
699
    @needs_write_lock
 
700
    def subsume(self, other_tree):
 
701
        def add_children(inventory, entry):
 
702
            for child_entry in entry.children.values():
 
703
                inventory._byid[child_entry.file_id] = child_entry
 
704
                if child_entry.kind == 'directory':
 
705
                    add_children(inventory, child_entry)
 
706
        if other_tree.get_root_id() == self.get_root_id():
 
707
            raise errors.BadSubsumeSource(self, other_tree,
 
708
                                          'Trees have the same root')
 
709
        try:
 
710
            other_tree_path = self.relpath(other_tree.basedir)
 
711
        except errors.PathNotChild:
 
712
            raise errors.BadSubsumeSource(self, other_tree,
 
713
                'Tree is not contained by the other')
 
714
        new_root_parent = self.path2id(osutils.dirname(other_tree_path))
 
715
        if new_root_parent is None:
 
716
            raise errors.BadSubsumeSource(self, other_tree,
 
717
                'Parent directory is not versioned.')
 
718
        # We need to ensure that the result of a fetch will have a
 
719
        # versionedfile for the other_tree root, and only fetching into
 
720
        # RepositoryKnit2 guarantees that.
 
721
        if not self.branch.repository.supports_rich_root():
 
722
            raise errors.SubsumeTargetNeedsUpgrade(other_tree)
 
723
        other_tree.lock_tree_write()
 
724
        try:
 
725
            new_parents = other_tree.get_parent_ids()
 
726
            other_root = other_tree.root_inventory.root
 
727
            other_root.parent_id = new_root_parent
 
728
            other_root.name = osutils.basename(other_tree_path)
 
729
            self.root_inventory.add(other_root)
 
730
            add_children(self.root_inventory, other_root)
 
731
            self._write_inventory(self.root_inventory)
 
732
            # normally we don't want to fetch whole repositories, but i think
 
733
            # here we really do want to consolidate the whole thing.
 
734
            for parent_id in other_tree.get_parent_ids():
 
735
                self.branch.fetch(other_tree.branch, parent_id)
 
736
                self.add_parent_tree_id(parent_id)
 
737
        finally:
 
738
            other_tree.unlock()
 
739
        other_tree.bzrdir.retire_bzrdir()
 
740
 
 
741
    @needs_tree_write_lock
 
742
    def extract(self, file_id, format=None):
 
743
        """Extract a subtree from this tree.
 
744
 
 
745
        A new branch will be created, relative to the path for this tree.
 
746
        """
 
747
        self.flush()
 
748
        def mkdirs(path):
 
749
            segments = osutils.splitpath(path)
 
750
            transport = self.branch.bzrdir.root_transport
 
751
            for name in segments:
 
752
                transport = transport.clone(name)
 
753
                transport.ensure_base()
 
754
            return transport
 
755
 
 
756
        sub_path = self.id2path(file_id)
 
757
        branch_transport = mkdirs(sub_path)
 
758
        if format is None:
 
759
            format = self.bzrdir.cloning_metadir()
 
760
        branch_transport.ensure_base()
 
761
        branch_bzrdir = format.initialize_on_transport(branch_transport)
 
762
        try:
 
763
            repo = branch_bzrdir.find_repository()
 
764
        except errors.NoRepositoryPresent:
 
765
            repo = branch_bzrdir.create_repository()
 
766
        if not repo.supports_rich_root():
 
767
            raise errors.RootNotRich()
 
768
        new_branch = branch_bzrdir.create_branch()
 
769
        new_branch.pull(self.branch)
 
770
        for parent_id in self.get_parent_ids():
 
771
            new_branch.fetch(self.branch, parent_id)
 
772
        tree_transport = self.bzrdir.root_transport.clone(sub_path)
 
773
        if tree_transport.base != branch_transport.base:
 
774
            tree_bzrdir = format.initialize_on_transport(tree_transport)
 
775
            tree_bzrdir.set_branch_reference(new_branch)
 
776
        else:
 
777
            tree_bzrdir = branch_bzrdir
 
778
        wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
 
779
        wt.set_parent_ids(self.get_parent_ids())
 
780
        # FIXME: Support nested trees
 
781
        my_inv = self.root_inventory
 
782
        child_inv = inventory.Inventory(root_id=None)
 
783
        new_root = my_inv[file_id]
 
784
        my_inv.remove_recursive_id(file_id)
 
785
        new_root.parent_id = None
 
786
        child_inv.add(new_root)
 
787
        self._write_inventory(my_inv)
 
788
        wt._write_inventory(child_inv)
 
789
        return wt
 
790
 
 
791
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
792
        """List all files as (path, class, kind, id, entry).
 
793
 
 
794
        Lists, but does not descend into unversioned directories.
 
795
        This does not include files that have been deleted in this
 
796
        tree. Skips the control directory.
 
797
 
 
798
        :param include_root: if True, return an entry for the root
 
799
        :param from_dir: start from this directory or None for the root
 
800
        :param recursive: whether to recurse into subdirectories or not
 
801
        """
 
802
        # list_files is an iterator, so @needs_read_lock doesn't work properly
 
803
        # with it. So callers should be careful to always read_lock the tree.
 
804
        if not self.is_locked():
 
805
            raise errors.ObjectNotLocked(self)
 
806
 
 
807
        if from_dir is None and include_root is True:
 
808
            yield ('', 'V', 'directory', self.get_root_id(), self.root_inventory.root)
 
809
        # Convert these into local objects to save lookup times
 
810
        pathjoin = osutils.pathjoin
 
811
        file_kind = self._kind
 
812
 
 
813
        # transport.base ends in a slash, we want the piece
 
814
        # between the last two slashes
 
815
        transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
 
816
 
 
817
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
 
818
 
 
819
        # directory file_id, relative path, absolute path, reverse sorted children
 
820
        if from_dir is not None:
 
821
            inv, from_dir_id = self._path2inv_file_id(from_dir)
 
822
            if from_dir_id is None:
 
823
                # Directory not versioned
 
824
                return
 
825
            from_dir_abspath = pathjoin(self.basedir, from_dir)
 
826
        else:
 
827
            inv = self.root_inventory
 
828
            from_dir_id = inv.root.file_id
 
829
            from_dir_abspath = self.basedir
 
830
        children = sorted(os.listdir(from_dir_abspath))
 
831
        # jam 20060527 The kernel sized tree seems equivalent whether we
 
832
        # use a deque and popleft to keep them sorted, or if we use a plain
 
833
        # list and just reverse() them.
 
834
        children = collections.deque(children)
 
835
        stack = [(from_dir_id, u'', from_dir_abspath, children)]
 
836
        while stack:
 
837
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
 
838
 
 
839
            while children:
 
840
                f = children.popleft()
 
841
                ## TODO: If we find a subdirectory with its own .bzr
 
842
                ## directory, then that is a separate tree and we
 
843
                ## should exclude it.
 
844
 
 
845
                # the bzrdir for this tree
 
846
                if transport_base_dir == f:
 
847
                    continue
 
848
 
 
849
                # we know that from_dir_relpath and from_dir_abspath never end in a slash
 
850
                # and 'f' doesn't begin with one, we can do a string op, rather
 
851
                # than the checks of pathjoin(), all relative paths will have an extra slash
 
852
                # at the beginning
 
853
                fp = from_dir_relpath + '/' + f
 
854
 
 
855
                # absolute path
 
856
                fap = from_dir_abspath + '/' + f
 
857
 
 
858
                dir_ie = inv[from_dir_id]
 
859
                if dir_ie.kind == 'directory':
 
860
                    f_ie = dir_ie.children.get(f)
 
861
                else:
 
862
                    f_ie = None
 
863
                if f_ie:
 
864
                    c = 'V'
 
865
                elif self.is_ignored(fp[1:]):
 
866
                    c = 'I'
 
867
                else:
 
868
                    # we may not have found this file, because of a unicode
 
869
                    # issue, or because the directory was actually a symlink.
 
870
                    f_norm, can_access = osutils.normalized_filename(f)
 
871
                    if f == f_norm or not can_access:
 
872
                        # No change, so treat this file normally
 
873
                        c = '?'
 
874
                    else:
 
875
                        # this file can be accessed by a normalized path
 
876
                        # check again if it is versioned
 
877
                        # these lines are repeated here for performance
 
878
                        f = f_norm
 
879
                        fp = from_dir_relpath + '/' + f
 
880
                        fap = from_dir_abspath + '/' + f
 
881
                        f_ie = inv.get_child(from_dir_id, f)
 
882
                        if f_ie:
 
883
                            c = 'V'
 
884
                        elif self.is_ignored(fp[1:]):
 
885
                            c = 'I'
 
886
                        else:
 
887
                            c = '?'
 
888
 
 
889
                fk = osutils.file_kind(fap)
 
890
 
 
891
                # make a last minute entry
 
892
                if f_ie:
 
893
                    yield fp[1:], c, fk, f_ie.file_id, f_ie
 
894
                else:
 
895
                    try:
 
896
                        yield fp[1:], c, fk, None, fk_entries[fk]()
 
897
                    except KeyError:
 
898
                        yield fp[1:], c, fk, None, TreeEntry()
 
899
                    continue
 
900
 
 
901
                if fk != 'directory':
 
902
                    continue
 
903
 
 
904
                # But do this child first if recursing down
 
905
                if recursive:
 
906
                    new_children = sorted(os.listdir(fap))
 
907
                    new_children = collections.deque(new_children)
 
908
                    stack.append((f_ie.file_id, fp, fap, new_children))
 
909
                    # Break out of inner loop,
 
910
                    # so that we start outer loop with child
 
911
                    break
 
912
            else:
 
913
                # if we finished all children, pop it off the stack
 
914
                stack.pop()
 
915
 
 
916
    @needs_tree_write_lock
 
917
    def move(self, from_paths, to_dir=None, after=False):
 
918
        """Rename files.
 
919
 
 
920
        to_dir must exist in the inventory.
 
921
 
 
922
        If to_dir exists and is a directory, the files are moved into
 
923
        it, keeping their old names.
 
924
 
 
925
        Note that to_dir is only the last component of the new name;
 
926
        this doesn't change the directory.
 
927
 
 
928
        For each entry in from_paths the move mode will be determined
 
929
        independently.
 
930
 
 
931
        The first mode moves the file in the filesystem and updates the
 
932
        inventory. The second mode only updates the inventory without
 
933
        touching the file on the filesystem.
 
934
 
 
935
        move uses the second mode if 'after == True' and the target is
 
936
        either not versioned or newly added, and present in the working tree.
 
937
 
 
938
        move uses the second mode if 'after == False' and the source is
 
939
        versioned but no longer in the working tree, and the target is not
 
940
        versioned but present in the working tree.
 
941
 
 
942
        move uses the first mode if 'after == False' and the source is
 
943
        versioned and present in the working tree, and the target is not
 
944
        versioned and not present in the working tree.
 
945
 
 
946
        Everything else results in an error.
 
947
 
 
948
        This returns a list of (from_path, to_path) pairs for each
 
949
        entry that is moved.
 
950
        """
 
951
        rename_entries = []
 
952
        rename_tuples = []
 
953
 
 
954
        invs_to_write = set()
 
955
 
 
956
        # check for deprecated use of signature
 
957
        if to_dir is None:
 
958
            raise TypeError('You must supply a target directory')
 
959
        # check destination directory
 
960
        if isinstance(from_paths, basestring):
 
961
            raise ValueError()
 
962
        to_abs = self.abspath(to_dir)
 
963
        if not osutils.isdir(to_abs):
 
964
            raise errors.BzrMoveFailedError('',to_dir,
 
965
                errors.NotADirectory(to_abs))
 
966
        if not self.has_filename(to_dir):
 
967
            raise errors.BzrMoveFailedError('',to_dir,
 
968
                errors.NotInWorkingDirectory(to_dir))
 
969
        to_inv, to_dir_id = self._path2inv_file_id(to_dir)
 
970
        if to_dir_id is None:
 
971
            raise errors.BzrMoveFailedError('',to_dir,
 
972
                errors.NotVersionedError(path=to_dir))
 
973
 
 
974
        to_dir_ie = to_inv[to_dir_id]
 
975
        if to_dir_ie.kind != 'directory':
 
976
            raise errors.BzrMoveFailedError('',to_dir,
 
977
                errors.NotADirectory(to_abs))
 
978
 
 
979
        # create rename entries and tuples
 
980
        for from_rel in from_paths:
 
981
            from_tail = osutils.splitpath(from_rel)[-1]
 
982
            from_inv, from_id = self._path2inv_file_id(from_rel)
 
983
            if from_id is None:
 
984
                raise errors.BzrMoveFailedError(from_rel,to_dir,
 
985
                    errors.NotVersionedError(path=from_rel))
 
986
 
 
987
            from_entry = from_inv[from_id]
 
988
            from_parent_id = from_entry.parent_id
 
989
            to_rel = osutils.pathjoin(to_dir, from_tail)
 
990
            rename_entry = InventoryWorkingTree._RenameEntry(
 
991
                from_rel=from_rel,
 
992
                from_id=from_id,
 
993
                from_tail=from_tail,
 
994
                from_parent_id=from_parent_id,
 
995
                to_rel=to_rel, to_tail=from_tail,
 
996
                to_parent_id=to_dir_id)
 
997
            rename_entries.append(rename_entry)
 
998
            rename_tuples.append((from_rel, to_rel))
 
999
 
 
1000
        # determine which move mode to use. checks also for movability
 
1001
        rename_entries = self._determine_mv_mode(rename_entries, after)
 
1002
 
 
1003
        original_modified = self._inventory_is_modified
 
1004
        try:
 
1005
            if len(from_paths):
 
1006
                self._inventory_is_modified = True
 
1007
            self._move(rename_entries)
 
1008
        except:
 
1009
            # restore the inventory on error
 
1010
            self._inventory_is_modified = original_modified
 
1011
            raise
 
1012
        #FIXME: Should potentially also write the from_invs
 
1013
        self._write_inventory(to_inv)
 
1014
        return rename_tuples
 
1015
 
 
1016
    @needs_tree_write_lock
 
1017
    def rename_one(self, from_rel, to_rel, after=False):
 
1018
        """Rename one file.
 
1019
 
 
1020
        This can change the directory or the filename or both.
 
1021
 
 
1022
        rename_one has several 'modes' to work. First, it can rename a physical
 
1023
        file and change the file_id. That is the normal mode. Second, it can
 
1024
        only change the file_id without touching any physical file.
 
1025
 
 
1026
        rename_one uses the second mode if 'after == True' and 'to_rel' is not
 
1027
        versioned but present in the working tree.
 
1028
 
 
1029
        rename_one uses the second mode if 'after == False' and 'from_rel' is
 
1030
        versioned but no longer in the working tree, and 'to_rel' is not
 
1031
        versioned but present in the working tree.
 
1032
 
 
1033
        rename_one uses the first mode if 'after == False' and 'from_rel' is
 
1034
        versioned and present in the working tree, and 'to_rel' is not
 
1035
        versioned and not present in the working tree.
 
1036
 
 
1037
        Everything else results in an error.
 
1038
        """
 
1039
        rename_entries = []
 
1040
 
 
1041
        # create rename entries and tuples
 
1042
        from_tail = osutils.splitpath(from_rel)[-1]
 
1043
        from_inv, from_id = self._path2inv_file_id(from_rel)
 
1044
        if from_id is None:
 
1045
            # if file is missing in the inventory maybe it's in the basis_tree
 
1046
            basis_tree = self.branch.basis_tree()
 
1047
            from_id = basis_tree.path2id(from_rel)
 
1048
            if from_id is None:
 
1049
                raise errors.BzrRenameFailedError(from_rel,to_rel,
 
1050
                    errors.NotVersionedError(path=from_rel))
 
1051
            # put entry back in the inventory so we can rename it
 
1052
            from_entry = basis_tree.root_inventory[from_id].copy()
 
1053
            from_inv.add(from_entry)
 
1054
        else:
 
1055
            from_inv, from_inv_id = self._unpack_file_id(from_id)
 
1056
            from_entry = from_inv[from_inv_id]
 
1057
        from_parent_id = from_entry.parent_id
 
1058
        to_dir, to_tail = os.path.split(to_rel)
 
1059
        to_inv, to_dir_id = self._path2inv_file_id(to_dir)
 
1060
        rename_entry = InventoryWorkingTree._RenameEntry(from_rel=from_rel,
 
1061
                                     from_id=from_id,
 
1062
                                     from_tail=from_tail,
 
1063
                                     from_parent_id=from_parent_id,
 
1064
                                     to_rel=to_rel, to_tail=to_tail,
 
1065
                                     to_parent_id=to_dir_id)
 
1066
        rename_entries.append(rename_entry)
 
1067
 
 
1068
        # determine which move mode to use. checks also for movability
 
1069
        rename_entries = self._determine_mv_mode(rename_entries, after)
 
1070
 
 
1071
        # check if the target changed directory and if the target directory is
 
1072
        # versioned
 
1073
        if to_dir_id is None:
 
1074
            raise errors.BzrMoveFailedError(from_rel,to_rel,
 
1075
                errors.NotVersionedError(path=to_dir))
 
1076
 
 
1077
        # all checks done. now we can continue with our actual work
 
1078
        mutter('rename_one:\n'
 
1079
               '  from_id   {%s}\n'
 
1080
               '  from_rel: %r\n'
 
1081
               '  to_rel:   %r\n'
 
1082
               '  to_dir    %r\n'
 
1083
               '  to_dir_id {%s}\n',
 
1084
               from_id, from_rel, to_rel, to_dir, to_dir_id)
 
1085
 
 
1086
        self._move(rename_entries)
 
1087
        self._write_inventory(to_inv)
 
1088
 
 
1089
    class _RenameEntry(object):
 
1090
        def __init__(self, from_rel, from_id, from_tail, from_parent_id,
 
1091
                     to_rel, to_tail, to_parent_id, only_change_inv=False,
 
1092
                     change_id=False):
 
1093
            self.from_rel = from_rel
 
1094
            self.from_id = from_id
 
1095
            self.from_tail = from_tail
 
1096
            self.from_parent_id = from_parent_id
 
1097
            self.to_rel = to_rel
 
1098
            self.to_tail = to_tail
 
1099
            self.to_parent_id = to_parent_id
 
1100
            self.change_id = change_id
 
1101
            self.only_change_inv = only_change_inv
 
1102
 
 
1103
    def _determine_mv_mode(self, rename_entries, after=False):
 
1104
        """Determines for each from-to pair if both inventory and working tree
 
1105
        or only the inventory has to be changed.
 
1106
 
 
1107
        Also does basic plausability tests.
 
1108
        """
 
1109
        # FIXME: Handling of nested trees
 
1110
        inv = self.root_inventory
 
1111
 
 
1112
        for rename_entry in rename_entries:
 
1113
            # store to local variables for easier reference
 
1114
            from_rel = rename_entry.from_rel
 
1115
            from_id = rename_entry.from_id
 
1116
            to_rel = rename_entry.to_rel
 
1117
            to_id = inv.path2id(to_rel)
 
1118
            only_change_inv = False
 
1119
            change_id = False
 
1120
 
 
1121
            # check the inventory for source and destination
 
1122
            if from_id is None:
 
1123
                raise errors.BzrMoveFailedError(from_rel,to_rel,
 
1124
                    errors.NotVersionedError(path=from_rel))
 
1125
            if to_id is not None:
 
1126
                allowed = False
 
1127
                # allow it with --after but only if dest is newly added
 
1128
                if after:
 
1129
                    basis = self.basis_tree()
 
1130
                    basis.lock_read()
 
1131
                    try:
 
1132
                        if not basis.has_id(to_id):
 
1133
                            rename_entry.change_id = True
 
1134
                            allowed = True
 
1135
                    finally:
 
1136
                        basis.unlock()
 
1137
                if not allowed:
 
1138
                    raise errors.BzrMoveFailedError(from_rel,to_rel,
 
1139
                        errors.AlreadyVersionedError(path=to_rel))
 
1140
 
 
1141
            # try to determine the mode for rename (only change inv or change
 
1142
            # inv and file system)
 
1143
            if after:
 
1144
                if not self.has_filename(to_rel):
 
1145
                    raise errors.BzrMoveFailedError(from_id,to_rel,
 
1146
                        errors.NoSuchFile(path=to_rel,
 
1147
                        extra="New file has not been created yet"))
 
1148
                only_change_inv = True
 
1149
            elif not self.has_filename(from_rel) and self.has_filename(to_rel):
 
1150
                only_change_inv = True
 
1151
            elif self.has_filename(from_rel) and not self.has_filename(to_rel):
 
1152
                only_change_inv = False
 
1153
            elif (not self.case_sensitive
 
1154
                  and from_rel.lower() == to_rel.lower()
 
1155
                  and self.has_filename(from_rel)):
 
1156
                only_change_inv = False
 
1157
            else:
 
1158
                # something is wrong, so lets determine what exactly
 
1159
                if not self.has_filename(from_rel) and \
 
1160
                   not self.has_filename(to_rel):
 
1161
                    raise errors.BzrRenameFailedError(from_rel, to_rel,
 
1162
                        errors.PathsDoNotExist(paths=(from_rel, to_rel)))
 
1163
                else:
 
1164
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
 
1165
            rename_entry.only_change_inv = only_change_inv
 
1166
        return rename_entries
 
1167
 
 
1168
    def _move(self, rename_entries):
 
1169
        """Moves a list of files.
 
1170
 
 
1171
        Depending on the value of the flag 'only_change_inv', the
 
1172
        file will be moved on the file system or not.
 
1173
        """
 
1174
        moved = []
 
1175
 
 
1176
        for entry in rename_entries:
 
1177
            try:
 
1178
                self._move_entry(entry)
 
1179
            except:
 
1180
                self._rollback_move(moved)
 
1181
                raise
 
1182
            moved.append(entry)
 
1183
 
 
1184
    def _rollback_move(self, moved):
 
1185
        """Try to rollback a previous move in case of an filesystem error."""
 
1186
        for entry in moved:
 
1187
            try:
 
1188
                self._move_entry(WorkingTree._RenameEntry(
 
1189
                    entry.to_rel, entry.from_id,
 
1190
                    entry.to_tail, entry.to_parent_id, entry.from_rel,
 
1191
                    entry.from_tail, entry.from_parent_id,
 
1192
                    entry.only_change_inv))
 
1193
            except errors.BzrMoveFailedError as e:
 
1194
                raise errors.BzrMoveFailedError( '', '', "Rollback failed."
 
1195
                        " The working tree is in an inconsistent state."
 
1196
                        " Please consider doing a 'bzr revert'."
 
1197
                        " Error message is: %s" % e)
 
1198
 
 
1199
    def _move_entry(self, entry):
 
1200
        inv = self.root_inventory
 
1201
        from_rel_abs = self.abspath(entry.from_rel)
 
1202
        to_rel_abs = self.abspath(entry.to_rel)
 
1203
        if from_rel_abs == to_rel_abs:
 
1204
            raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
 
1205
                "Source and target are identical.")
 
1206
 
 
1207
        if not entry.only_change_inv:
 
1208
            try:
 
1209
                osutils.rename(from_rel_abs, to_rel_abs)
 
1210
            except OSError as e:
 
1211
                raise errors.BzrMoveFailedError(entry.from_rel,
 
1212
                    entry.to_rel, e[1])
 
1213
        if entry.change_id:
 
1214
            to_id = inv.path2id(entry.to_rel)
 
1215
            inv.remove_recursive_id(to_id)
 
1216
        inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
 
1217
 
 
1218
    @needs_tree_write_lock
 
1219
    def unversion(self, file_ids):
 
1220
        """Remove the file ids in file_ids from the current versioned set.
 
1221
 
 
1222
        When a file_id is unversioned, all of its children are automatically
 
1223
        unversioned.
 
1224
 
 
1225
        :param file_ids: The file ids to stop versioning.
 
1226
        :raises: NoSuchId if any fileid is not currently versioned.
 
1227
        """
 
1228
        for file_id in file_ids:
 
1229
            if not self._inventory.has_id(file_id):
 
1230
                raise errors.NoSuchId(self, file_id)
 
1231
        for file_id in file_ids:
 
1232
            if self._inventory.has_id(file_id):
 
1233
                self._inventory.remove_recursive_id(file_id)
 
1234
        if len(file_ids):
 
1235
            # in the future this should just set a dirty bit to wait for the
 
1236
            # final unlock. However, until all methods of workingtree start
 
1237
            # with the current in -memory inventory rather than triggering
 
1238
            # a read, it is more complex - we need to teach read_inventory
 
1239
            # to know when to read, and when to not read first... and possibly
 
1240
            # to save first when the in memory one may be corrupted.
 
1241
            # so for now, we just only write it if it is indeed dirty.
 
1242
            # - RBC 20060907
 
1243
            self._write_inventory(self._inventory)
 
1244
 
 
1245
    def stored_kind(self, file_id):
 
1246
        """See Tree.stored_kind"""
 
1247
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1248
        return inv[inv_file_id].kind
 
1249
 
 
1250
    def extras(self):
 
1251
        """Yield all unversioned files in this WorkingTree.
 
1252
 
 
1253
        If there are any unversioned directories then only the directory is
 
1254
        returned, not all its children.  But if there are unversioned files
 
1255
        under a versioned subdirectory, they are returned.
 
1256
 
 
1257
        Currently returned depth-first, sorted by name within directories.
 
1258
        This is the same order used by 'osutils.walkdirs'.
 
1259
        """
 
1260
        ## TODO: Work from given directory downwards
 
1261
        for path, dir_entry in self.iter_entries_by_dir():
 
1262
            if dir_entry.kind != 'directory':
 
1263
                continue
 
1264
            # mutter("search for unknowns in %r", path)
 
1265
            dirabs = self.abspath(path)
 
1266
            if not osutils.isdir(dirabs):
 
1267
                # e.g. directory deleted
 
1268
                continue
 
1269
 
 
1270
            fl = []
 
1271
            for subf in os.listdir(dirabs):
 
1272
                if self.bzrdir.is_control_filename(subf):
 
1273
                    continue
 
1274
                if subf not in dir_entry.children:
 
1275
                    try:
 
1276
                        (subf_norm,
 
1277
                         can_access) = osutils.normalized_filename(subf)
 
1278
                    except UnicodeDecodeError:
 
1279
                        path_os_enc = path.encode(osutils._fs_enc)
 
1280
                        relpath = path_os_enc + '/' + subf
 
1281
                        raise errors.BadFilenameEncoding(relpath,
 
1282
                                                         osutils._fs_enc)
 
1283
                    if subf_norm != subf and can_access:
 
1284
                        if subf_norm not in dir_entry.children:
 
1285
                            fl.append(subf_norm)
 
1286
                    else:
 
1287
                        fl.append(subf)
 
1288
 
 
1289
            fl.sort()
 
1290
            for subf in fl:
 
1291
                subp = osutils.pathjoin(path, subf)
 
1292
                yield subp
 
1293
 
 
1294
    def _walkdirs(self, prefix=""):
 
1295
        """Walk the directories of this tree.
 
1296
 
 
1297
        :param prefix: is used as the directrory to start with.
 
1298
        :returns: a generator which yields items in the form::
 
1299
 
 
1300
            ((curren_directory_path, fileid),
 
1301
             [(file1_path, file1_name, file1_kind, None, file1_id,
 
1302
               file1_kind), ... ])
 
1303
        """
 
1304
        _directory = 'directory'
 
1305
        # get the root in the inventory
 
1306
        inv, top_id = self._path2inv_file_id(prefix)
 
1307
        if top_id is None:
 
1308
            pending = []
 
1309
        else:
 
1310
            pending = [(prefix, '', _directory, None, top_id, None)]
 
1311
        while pending:
 
1312
            dirblock = []
 
1313
            currentdir = pending.pop()
 
1314
            # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
 
1315
            top_id = currentdir[4]
 
1316
            if currentdir[0]:
 
1317
                relroot = currentdir[0] + '/'
 
1318
            else:
 
1319
                relroot = ""
 
1320
            # FIXME: stash the node in pending
 
1321
            entry = inv[top_id]
 
1322
            if entry.kind == 'directory':
 
1323
                for name, child in entry.sorted_children():
 
1324
                    dirblock.append((relroot + name, name, child.kind, None,
 
1325
                        child.file_id, child.kind
 
1326
                        ))
 
1327
            yield (currentdir[0], entry.file_id), dirblock
 
1328
            # push the user specified dirs from dirblock
 
1329
            for dir in reversed(dirblock):
 
1330
                if dir[2] == _directory:
 
1331
                    pending.append(dir)
 
1332
 
 
1333
    @needs_write_lock
 
1334
    def update_feature_flags(self, updated_flags):
 
1335
        """Update the feature flags for this branch.
 
1336
 
 
1337
        :param updated_flags: Dictionary mapping feature names to necessities
 
1338
            A necessity can be None to indicate the feature should be removed
 
1339
        """
 
1340
        self._format._update_feature_flags(updated_flags)
 
1341
        self.control_transport.put_bytes('format', self._format.as_string())
 
1342
 
 
1343
    def _check_for_tree_references(self, iterator):
 
1344
        """See if directories have become tree-references."""
 
1345
        blocked_parent_ids = set()
 
1346
        for path, ie in iterator:
 
1347
            if ie.parent_id in blocked_parent_ids:
 
1348
                # This entry was pruned because one of its parents became a
 
1349
                # TreeReference. If this is a directory, mark it as blocked.
 
1350
                if ie.kind == 'directory':
 
1351
                    blocked_parent_ids.add(ie.file_id)
 
1352
                continue
 
1353
            if ie.kind == 'directory' and self._directory_is_tree_reference(path):
 
1354
                # This InventoryDirectory needs to be a TreeReference
 
1355
                ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
 
1356
                blocked_parent_ids.add(ie.file_id)
 
1357
            yield path, ie
 
1358
 
 
1359
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
1360
        """See Tree.iter_entries_by_dir()"""
 
1361
        # The only trick here is that if we supports_tree_reference then we
 
1362
        # need to detect if a directory becomes a tree-reference.
 
1363
        iterator = super(WorkingTree, self).iter_entries_by_dir(
 
1364
                specific_file_ids=specific_file_ids,
 
1365
                yield_parents=yield_parents)
 
1366
        if not self.supports_tree_reference():
 
1367
            return iterator
 
1368
        else:
 
1369
            return self._check_for_tree_references(iterator)
 
1370
 
 
1371
 
 
1372
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
 
1373
    """Base class for working trees that live in bzr meta directories."""
 
1374
 
 
1375
    def __init__(self):
 
1376
        WorkingTreeFormat.__init__(self)
 
1377
        bzrdir.BzrFormat.__init__(self)
 
1378
 
 
1379
    @classmethod
 
1380
    def find_format_string(klass, controldir):
 
1381
        """Return format name for the working tree object in controldir."""
 
1382
        try:
 
1383
            transport = controldir.get_workingtree_transport(None)
 
1384
            return transport.get_bytes("format")
 
1385
        except errors.NoSuchFile:
 
1386
            raise errors.NoWorkingTree(base=transport.base)
 
1387
 
 
1388
    @classmethod
 
1389
    def find_format(klass, controldir):
 
1390
        """Return the format for the working tree object in controldir."""
 
1391
        format_string = klass.find_format_string(controldir)
 
1392
        return klass._find_format(format_registry, 'working tree',
 
1393
                format_string)
 
1394
 
 
1395
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
1396
            basedir=None):
 
1397
        WorkingTreeFormat.check_support_status(self,
 
1398
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
1399
            basedir=basedir)
 
1400
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
1401
            recommend_upgrade=recommend_upgrade, basedir=basedir)
 
1402
 
 
1403
    def get_controldir_for_branch(self):
 
1404
        """Get the control directory format for creating branches.
 
1405
 
 
1406
        This is to support testing of working tree formats that can not exist
 
1407
        in the same control directory as a branch.
 
1408
        """
 
1409
        return self._matchingbzrdir
 
1410
 
 
1411
 
 
1412
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
 
1413
    """Base class for working trees that live in bzr meta directories."""
 
1414
 
 
1415
    def __init__(self):
 
1416
        WorkingTreeFormat.__init__(self)
 
1417
        bzrdir.BzrFormat.__init__(self)
 
1418
 
 
1419
    @classmethod
 
1420
    def find_format_string(klass, controldir):
 
1421
        """Return format name for the working tree object in controldir."""
 
1422
        try:
 
1423
            transport = controldir.get_workingtree_transport(None)
 
1424
            return transport.get_bytes("format")
 
1425
        except errors.NoSuchFile:
 
1426
            raise errors.NoWorkingTree(base=transport.base)
 
1427
 
 
1428
    @classmethod
 
1429
    def find_format(klass, controldir):
 
1430
        """Return the format for the working tree object in controldir."""
 
1431
        format_string = klass.find_format_string(controldir)
 
1432
        return klass._find_format(format_registry, 'working tree',
 
1433
                format_string)
 
1434
 
 
1435
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
1436
            basedir=None):
 
1437
        WorkingTreeFormat.check_support_status(self,
 
1438
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
1439
            basedir=basedir)
 
1440
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
1441
            recommend_upgrade=recommend_upgrade, basedir=basedir)