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