/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/bzr/workingtree.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

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
 
from __future__ import absolute_import
34
 
 
35
 
from bisect import bisect_left
36
 
import breezy
37
 
try:
38
 
    from collections.abc import deque
39
 
except ImportError:  # python < 3.7
40
 
    from collections import deque
41
 
import errno
42
 
import itertools
43
 
import operator
44
 
import os
45
 
import stat
46
 
import sys
47
 
 
48
 
# Explicitly import breezy.bzrdir so that the BzrProber
49
 
# is guaranteed to be registered.
50
 
from . import bzrdir
51
 
 
52
 
from .. import lazy_import
53
 
lazy_import.lazy_import(globals(), """
54
 
from breezy import (
55
 
    cache_utf8,
56
 
    cleanup,
57
 
    conflicts as _mod_conflicts,
58
 
    globbing,
59
 
    ignores,
60
 
    revision as _mod_revision,
61
 
    rio as _mod_rio,
62
 
    )
63
 
from breezy.bzr import (
64
 
    inventory,
65
 
    xml5,
66
 
    xml7,
67
 
    )
68
 
""")
69
 
 
70
 
from .. import (
71
 
    errors,
72
 
    osutils,
73
 
    )
74
 
from ..lock import LogicalLockResult
75
 
from .inventorytree import InventoryRevisionTree, MutableInventoryTree
76
 
from ..sixish import (
77
 
    BytesIO,
78
 
    text_type,
79
 
    )
80
 
from ..trace import mutter, note
81
 
from ..tree import (
82
 
    get_canonical_path,
83
 
    TreeDirectory,
84
 
    TreeEntry,
85
 
    TreeFile,
86
 
    TreeLink,
87
 
    TreeReference,
88
 
    )
89
 
from ..workingtree import (
90
 
    WorkingTree,
91
 
    WorkingTreeFormat,
92
 
    format_registry,
93
 
    )
94
 
 
95
 
 
96
 
MERGE_MODIFIED_HEADER_1 = b"BZR merge-modified list format 1"
97
 
# TODO: Modifying the conflict objects or their type is currently nearly
98
 
# impossible as there is no clear relationship between the working tree format
99
 
# and the conflict list file format.
100
 
CONFLICT_HEADER_1 = b"BZR conflict list format 1"
101
 
ERROR_PATH_NOT_FOUND = 3    # WindowsError errno code, equivalent to ENOENT
102
 
 
103
 
 
104
 
class InventoryWorkingTree(WorkingTree, MutableInventoryTree):
105
 
    """Base class for working trees that are inventory-oriented.
106
 
 
107
 
    The inventory is held in the `Branch` working-inventory, and the
108
 
    files are in a directory on disk.
109
 
 
110
 
    It is possible for a `WorkingTree` to have a filename which is
111
 
    not listed in the Inventory and vice versa.
112
 
    """
113
 
 
114
 
    def __init__(self, basedir='.',
115
 
                 branch=None,
116
 
                 _inventory=None,
117
 
                 _control_files=None,
118
 
                 _internal=False,
119
 
                 _format=None,
120
 
                 _controldir=None):
121
 
        """Construct a InventoryWorkingTree instance. This is not a public API.
122
 
 
123
 
        :param branch: A branch to override probing for the branch.
124
 
        """
125
 
        super(InventoryWorkingTree, self).__init__(
126
 
            basedir=basedir, branch=branch,
127
 
            _transport=_control_files._transport, _internal=_internal,
128
 
            _format=_format, _controldir=_controldir)
129
 
 
130
 
        self._control_files = _control_files
131
 
        self._detect_case_handling()
132
 
        self._setup_directory_is_tree_reference()
133
 
 
134
 
        if _inventory is None:
135
 
            # This will be acquired on lock_read() or lock_write()
136
 
            self._inventory_is_modified = False
137
 
            self._inventory = None
138
 
        else:
139
 
            # the caller of __init__ has provided an inventory,
140
 
            # we assume they know what they are doing - as its only
141
 
            # the Format factory and creation methods that are
142
 
            # permitted to do this.
143
 
            self._set_inventory(_inventory, dirty=False)
144
 
 
145
 
    def _set_inventory(self, inv, dirty):
146
 
        """Set the internal cached inventory.
147
 
 
148
 
        :param inv: The inventory to set.
149
 
        :param dirty: A boolean indicating whether the inventory is the same
150
 
            logical inventory as whats on disk. If True the inventory is not
151
 
            the same and should be written to disk or data will be lost, if
152
 
            False then the inventory is the same as that on disk and any
153
 
            serialisation would be unneeded overhead.
154
 
        """
155
 
        self._inventory = inv
156
 
        self._inventory_is_modified = dirty
157
 
 
158
 
    def _detect_case_handling(self):
159
 
        wt_trans = self.controldir.get_workingtree_transport(None)
160
 
        try:
161
 
            wt_trans.stat(self._format.case_sensitive_filename)
162
 
        except errors.NoSuchFile:
163
 
            self.case_sensitive = True
164
 
        else:
165
 
            self.case_sensitive = False
166
 
 
167
 
    def get_transform(self, pb=None):
168
 
        from ..transform import TreeTransform
169
 
        return TreeTransform(self, pb=pb)
170
 
 
171
 
    def _setup_directory_is_tree_reference(self):
172
 
        if self._branch.repository._format.supports_tree_reference:
173
 
            self._directory_is_tree_reference = \
174
 
                self._directory_may_be_tree_reference
175
 
        else:
176
 
            self._directory_is_tree_reference = \
177
 
                self._directory_is_never_tree_reference
178
 
 
179
 
    def _directory_is_never_tree_reference(self, relpath):
180
 
        return False
181
 
 
182
 
    def _directory_may_be_tree_reference(self, relpath):
183
 
        # as a special case, if a directory contains control files then
184
 
        # it's a tree reference, except that the root of the tree is not
185
 
        return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
186
 
        # TODO: We could ask all the control formats whether they
187
 
        # recognize this directory, but at the moment there's no cheap api
188
 
        # to do that.  Since we probably can only nest bzr checkouts and
189
 
        # they always use this name it's ok for now.  -- mbp 20060306
190
 
        #
191
 
        # FIXME: There is an unhandled case here of a subdirectory
192
 
        # containing .bzr but not a branch; that will probably blow up
193
 
        # when you try to commit it.  It might happen if there is a
194
 
        # checkout in a subdirectory.  This can be avoided by not adding
195
 
        # it.  mbp 20070306
196
 
 
197
 
    def _serialize(self, inventory, out_file):
198
 
        xml5.serializer_v5.write_inventory(
199
 
            self._inventory, out_file, working=True)
200
 
 
201
 
    def _deserialize(selt, in_file):
202
 
        return xml5.serializer_v5.read_inventory(in_file)
203
 
 
204
 
    def break_lock(self):
205
 
        """Break a lock if one is present from another instance.
206
 
 
207
 
        Uses the ui factory to ask for confirmation if the lock may be from
208
 
        an active process.
209
 
 
210
 
        This will probe the repository for its lock as well.
211
 
        """
212
 
        self._control_files.break_lock()
213
 
        self.branch.break_lock()
214
 
 
215
 
    def is_locked(self):
216
 
        return self._control_files.is_locked()
217
 
 
218
 
    def _must_be_locked(self):
219
 
        if not self.is_locked():
220
 
            raise errors.ObjectNotLocked(self)
221
 
 
222
 
    def lock_read(self):
223
 
        """Lock the tree for reading.
224
 
 
225
 
        This also locks the branch, and can be unlocked via self.unlock().
226
 
 
227
 
        :return: A breezy.lock.LogicalLockResult.
228
 
        """
229
 
        if not self.is_locked():
230
 
            self._reset_data()
231
 
        self.branch.lock_read()
232
 
        try:
233
 
            self._control_files.lock_read()
234
 
            return LogicalLockResult(self.unlock)
235
 
        except BaseException:
236
 
            self.branch.unlock()
237
 
            raise
238
 
 
239
 
    def lock_tree_write(self):
240
 
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
241
 
 
242
 
        :return: A breezy.lock.LogicalLockResult.
243
 
        """
244
 
        if not self.is_locked():
245
 
            self._reset_data()
246
 
        self.branch.lock_read()
247
 
        try:
248
 
            self._control_files.lock_write()
249
 
            return LogicalLockResult(self.unlock)
250
 
        except BaseException:
251
 
            self.branch.unlock()
252
 
            raise
253
 
 
254
 
    def lock_write(self):
255
 
        """See MutableTree.lock_write, and WorkingTree.unlock.
256
 
 
257
 
        :return: A breezy.lock.LogicalLockResult.
258
 
        """
259
 
        if not self.is_locked():
260
 
            self._reset_data()
261
 
        self.branch.lock_write()
262
 
        try:
263
 
            self._control_files.lock_write()
264
 
            return LogicalLockResult(self.unlock)
265
 
        except BaseException:
266
 
            self.branch.unlock()
267
 
            raise
268
 
 
269
 
    def get_physical_lock_status(self):
270
 
        return self._control_files.get_physical_lock_status()
271
 
 
272
 
    def _write_inventory(self, inv):
273
 
        """Write inventory as the current inventory."""
274
 
        with self.lock_tree_write():
275
 
            self._set_inventory(inv, dirty=True)
276
 
            self.flush()
277
 
 
278
 
    # XXX: This method should be deprecated in favour of taking in a proper
279
 
    # new Inventory object.
280
 
    def set_inventory(self, new_inventory_list):
281
 
        from .inventory import (
282
 
            Inventory,
283
 
            InventoryDirectory,
284
 
            InventoryFile,
285
 
            InventoryLink)
286
 
        with self.lock_tree_write():
287
 
            inv = Inventory(self.path2id(''))
288
 
            for path, file_id, parent, kind in new_inventory_list:
289
 
                name = os.path.basename(path)
290
 
                if name == "":
291
 
                    continue
292
 
                # fixme, there should be a factory function inv,add_??
293
 
                if kind == 'directory':
294
 
                    inv.add(InventoryDirectory(file_id, name, parent))
295
 
                elif kind == 'file':
296
 
                    inv.add(InventoryFile(file_id, name, parent))
297
 
                elif kind == 'symlink':
298
 
                    inv.add(InventoryLink(file_id, name, parent))
299
 
                else:
300
 
                    raise errors.BzrError("unknown kind %r" % kind)
301
 
            self._write_inventory(inv)
302
 
 
303
 
    def _write_basis_inventory(self, xml):
304
 
        """Write the basis inventory XML to the basis-inventory file"""
305
 
        path = self._basis_inventory_name()
306
 
        sio = BytesIO(b''.join(xml))
307
 
        self._transport.put_file(path, sio,
308
 
                                 mode=self.controldir._get_file_mode())
309
 
 
310
 
    def _reset_data(self):
311
 
        """Reset transient data that cannot be revalidated."""
312
 
        self._inventory_is_modified = False
313
 
        with self._transport.get('inventory') as f:
314
 
            result = self._deserialize(f)
315
 
        self._set_inventory(result, dirty=False)
316
 
 
317
 
    def store_uncommitted(self):
318
 
        """Store uncommitted changes from the tree in the branch."""
319
 
        with self.lock_write():
320
 
            target_tree = self.basis_tree()
321
 
            from ..shelf import ShelfCreator
322
 
            shelf_creator = ShelfCreator(self, target_tree)
323
 
            try:
324
 
                if not shelf_creator.shelve_all():
325
 
                    return
326
 
                self.branch.store_uncommitted(shelf_creator)
327
 
                shelf_creator.transform()
328
 
            finally:
329
 
                shelf_creator.finalize()
330
 
            note('Uncommitted changes stored in branch "%s".',
331
 
                 self.branch.nick)
332
 
 
333
 
    def restore_uncommitted(self):
334
 
        """Restore uncommitted changes from the branch into the tree."""
335
 
        with self.lock_write():
336
 
            unshelver = self.branch.get_unshelver(self)
337
 
            if unshelver is None:
338
 
                return
339
 
            try:
340
 
                merger = unshelver.make_merger()
341
 
                merger.ignore_zero = True
342
 
                merger.do_merge()
343
 
                self.branch.store_uncommitted(None)
344
 
            finally:
345
 
                unshelver.finalize()
346
 
 
347
 
    def get_shelf_manager(self):
348
 
        """Return the ShelfManager for this WorkingTree."""
349
 
        from ..shelf import ShelfManager
350
 
        return ShelfManager(self, self._transport)
351
 
 
352
 
    def _set_root_id(self, file_id):
353
 
        """Set the root id for this tree, in a format specific manner.
354
 
 
355
 
        :param file_id: The file id to assign to the root. It must not be
356
 
            present in the current inventory or an error will occur. It must
357
 
            not be None, but rather a valid file id.
358
 
        """
359
 
        inv = self._inventory
360
 
        orig_root_id = inv.root.file_id
361
 
        # TODO: it might be nice to exit early if there was nothing
362
 
        # to do, saving us from trigger a sync on unlock.
363
 
        self._inventory_is_modified = True
364
 
        # we preserve the root inventory entry object, but
365
 
        # unlinkit from the byid index
366
 
        inv.delete(inv.root.file_id)
367
 
        inv.root.file_id = file_id
368
 
        # and link it into the index with the new changed id.
369
 
        inv._byid[inv.root.file_id] = inv.root
370
 
        # and finally update all children to reference the new id.
371
 
        # XXX: this should be safe to just look at the root.children
372
 
        # list, not the WHOLE INVENTORY.
373
 
        for fid in inv.iter_all_ids():
374
 
            entry = inv.get_entry(fid)
375
 
            if entry.parent_id == orig_root_id:
376
 
                entry.parent_id = inv.root.file_id
377
 
 
378
 
    def remove(self, files, verbose=False, to_file=None, keep_files=True,
379
 
               force=False):
380
 
        """Remove nominated files from the working tree metadata.
381
 
 
382
 
        :files: File paths relative to the basedir.
383
 
        :keep_files: If true, the files will also be kept.
384
 
        :force: Delete files and directories, even if they are changed and
385
 
            even if the directories are not empty.
386
 
        """
387
 
        if isinstance(files, (str, text_type)):
388
 
            files = [files]
389
 
 
390
 
        inv_delta = []
391
 
 
392
 
        all_files = set()  # specified and nested files
393
 
        if to_file is None:
394
 
            to_file = sys.stdout
395
 
 
396
 
        files_to_backup = []
397
 
 
398
 
        def recurse_directory_to_add_files(directory):
399
 
            # Recurse directory and add all files
400
 
            # so we can check if they have changed.
401
 
            for parent_info, file_infos in self.walkdirs(directory):
402
 
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
403
 
                    # Is it versioned or ignored?
404
 
                    if self.is_versioned(relpath):
405
 
                        # Add nested content for deletion.
406
 
                        all_files.add(relpath)
407
 
                    else:
408
 
                        # Files which are not versioned
409
 
                        # should be treated as unknown.
410
 
                        files_to_backup.append(relpath)
411
 
 
412
 
        with self.lock_tree_write():
413
 
 
414
 
            for filename in files:
415
 
                # Get file name into canonical form.
416
 
                abspath = self.abspath(filename)
417
 
                filename = self.relpath(abspath)
418
 
                if len(filename) > 0:
419
 
                    all_files.add(filename)
420
 
                    recurse_directory_to_add_files(filename)
421
 
 
422
 
            files = list(all_files)
423
 
 
424
 
            if len(files) == 0:
425
 
                return  # nothing to do
426
 
 
427
 
            # Sort needed to first handle directory content before the
428
 
            # directory
429
 
            files.sort(reverse=True)
430
 
 
431
 
            # Bail out if we are going to delete files we shouldn't
432
 
            if not keep_files and not force:
433
 
                for change in self.iter_changes(
434
 
                        self.basis_tree(), include_unchanged=True,
435
 
                        require_versioned=False, want_unversioned=True,
436
 
                        specific_files=files):
437
 
                    if change.versioned[0] is False:
438
 
                        # The record is unknown or newly added
439
 
                        files_to_backup.append(change.path[1])
440
 
                    elif (change.changed_content and (change.kind[1] is not None)
441
 
                            and osutils.is_inside_any(files, change.path[1])):
442
 
                        # Versioned and changed, but not deleted, and still
443
 
                        # in one of the dirs to be deleted.
444
 
                        files_to_backup.append(change.path[1])
445
 
 
446
 
            def backup(file_to_backup):
447
 
                backup_name = self.controldir._available_backup_name(
448
 
                    file_to_backup)
449
 
                osutils.rename(abs_path, self.abspath(backup_name))
450
 
                return "removed %s (but kept a copy: %s)" % (file_to_backup,
451
 
                                                             backup_name)
452
 
 
453
 
            # Build inv_delta and delete files where applicable,
454
 
            # do this before any modifications to meta data.
455
 
            for f in files:
456
 
                fid = self.path2id(f)
457
 
                message = None
458
 
                if not fid:
459
 
                    message = "%s is not versioned." % (f,)
460
 
                else:
461
 
                    if verbose:
462
 
                        # having removed it, it must be either ignored or
463
 
                        # unknown
464
 
                        if self.is_ignored(f):
465
 
                            new_status = 'I'
466
 
                        else:
467
 
                            new_status = '?'
468
 
                        # XXX: Really should be a more abstract reporter
469
 
                        # interface
470
 
                        kind_ch = osutils.kind_marker(self.kind(f))
471
 
                        to_file.write(
472
 
                            new_status + '       ' + f + kind_ch + '\n')
473
 
                    # Unversion file
474
 
                    inv_delta.append((f, None, fid, None))
475
 
                    message = "removed %s" % (f,)
476
 
 
477
 
                if not keep_files:
478
 
                    abs_path = self.abspath(f)
479
 
                    if osutils.lexists(abs_path):
480
 
                        if (osutils.isdir(abs_path)
481
 
                                and len(os.listdir(abs_path)) > 0):
482
 
                            if force:
483
 
                                osutils.rmtree(abs_path)
484
 
                                message = "deleted %s" % (f,)
485
 
                            else:
486
 
                                message = backup(f)
487
 
                        else:
488
 
                            if f in files_to_backup:
489
 
                                message = backup(f)
490
 
                            else:
491
 
                                osutils.delete_any(abs_path)
492
 
                                message = "deleted %s" % (f,)
493
 
                    elif message is not None:
494
 
                        # Only care if we haven't done anything yet.
495
 
                        message = "%s does not exist." % (f,)
496
 
 
497
 
                # Print only one message (if any) per file.
498
 
                if message is not None:
499
 
                    note(message)
500
 
            self.apply_inventory_delta(inv_delta)
501
 
 
502
 
    def get_nested_tree(self, path):
503
 
        return WorkingTree.open(self.abspath(path))
504
 
 
505
 
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
506
 
        """See MutableTree.set_parent_trees."""
507
 
        parent_ids = [rev for (rev, tree) in parents_list]
508
 
        for revision_id in parent_ids:
509
 
            _mod_revision.check_not_reserved_id(revision_id)
510
 
 
511
 
        with self.lock_tree_write():
512
 
            self._check_parents_for_ghosts(parent_ids,
513
 
                                           allow_leftmost_as_ghost=allow_leftmost_as_ghost)
514
 
 
515
 
            parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
516
 
 
517
 
            if len(parent_ids) == 0:
518
 
                leftmost_parent_id = _mod_revision.NULL_REVISION
519
 
                leftmost_parent_tree = None
520
 
            else:
521
 
                leftmost_parent_id, leftmost_parent_tree = parents_list[0]
522
 
 
523
 
            if self._change_last_revision(leftmost_parent_id):
524
 
                if leftmost_parent_tree is None:
525
 
                    # If we don't have a tree, fall back to reading the
526
 
                    # parent tree from the repository.
527
 
                    self._cache_basis_inventory(leftmost_parent_id)
528
 
                else:
529
 
                    inv = leftmost_parent_tree.root_inventory
530
 
                    xml = self._create_basis_xml_from_inventory(
531
 
                        leftmost_parent_id, inv)
532
 
                    self._write_basis_inventory(xml)
533
 
            self._set_merges_from_parent_ids(parent_ids)
534
 
 
535
 
    def _cache_basis_inventory(self, new_revision):
536
 
        """Cache new_revision as the basis inventory."""
537
 
        # TODO: this should allow the ready-to-use inventory to be passed in,
538
 
        # as commit already has that ready-to-use [while the format is the
539
 
        # same, that is].
540
 
        try:
541
 
            # this double handles the inventory - unpack and repack -
542
 
            # but is easier to understand. We can/should put a conditional
543
 
            # in here based on whether the inventory is in the latest format
544
 
            # - perhaps we should repack all inventories on a repository
545
 
            # upgrade ?
546
 
            # the fast path is to copy the raw xml from the repository. If the
547
 
            # xml contains 'revision_id="', then we assume the right
548
 
            # revision_id is set. We must check for this full string, because a
549
 
            # root node id can legitimately look like 'revision_id' but cannot
550
 
            # contain a '"'.
551
 
            lines = self.branch.repository._get_inventory_xml(new_revision)
552
 
            firstline = lines[0]
553
 
            if (b'revision_id="' not in firstline
554
 
                    or b'format="7"' not in firstline):
555
 
                inv = self.branch.repository._serializer.read_inventory_from_lines(
556
 
                    lines, new_revision)
557
 
                lines = self._create_basis_xml_from_inventory(new_revision, inv)
558
 
            self._write_basis_inventory(lines)
559
 
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
560
 
            pass
561
 
 
562
 
    def _basis_inventory_name(self):
563
 
        return 'basis-inventory-cache'
564
 
 
565
 
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
566
 
        """Create the text that will be saved in basis-inventory"""
567
 
        inventory.revision_id = revision_id
568
 
        return xml7.serializer_v7.write_inventory_to_lines(inventory)
569
 
 
570
 
    def set_conflicts(self, conflicts):
571
 
        with self.lock_tree_write():
572
 
            self._put_rio('conflicts', conflicts.to_stanzas(),
573
 
                          CONFLICT_HEADER_1)
574
 
 
575
 
    def add_conflicts(self, new_conflicts):
576
 
        with self.lock_tree_write():
577
 
            conflict_set = set(self.conflicts())
578
 
            conflict_set.update(set(list(new_conflicts)))
579
 
            self.set_conflicts(_mod_conflicts.ConflictList(
580
 
                sorted(conflict_set, key=_mod_conflicts.Conflict.sort_key)))
581
 
 
582
 
    def conflicts(self):
583
 
        with self.lock_read():
584
 
            try:
585
 
                confile = self._transport.get('conflicts')
586
 
            except errors.NoSuchFile:
587
 
                return _mod_conflicts.ConflictList()
588
 
            try:
589
 
                try:
590
 
                    if next(confile) != CONFLICT_HEADER_1 + b'\n':
591
 
                        raise errors.ConflictFormatError()
592
 
                except StopIteration:
593
 
                    raise errors.ConflictFormatError()
594
 
                reader = _mod_rio.RioReader(confile)
595
 
                return _mod_conflicts.ConflictList.from_stanzas(reader)
596
 
            finally:
597
 
                confile.close()
598
 
 
599
 
    def get_ignore_list(self):
600
 
        """Return list of ignore patterns.
601
 
 
602
 
        Cached in the Tree object after the first call.
603
 
        """
604
 
        ignoreset = getattr(self, '_ignoreset', None)
605
 
        if ignoreset is not None:
606
 
            return ignoreset
607
 
 
608
 
        ignore_globs = set()
609
 
        ignore_globs.update(ignores.get_runtime_ignores())
610
 
        ignore_globs.update(ignores.get_user_ignores())
611
 
        if self.has_filename(self._format.ignore_filename):
612
 
            with self.get_file(self._format.ignore_filename) as f:
613
 
                ignore_globs.update(ignores.parse_ignore_file(f))
614
 
        self._ignoreset = ignore_globs
615
 
        return ignore_globs
616
 
 
617
 
    def _cleanup(self):
618
 
        self._flush_ignore_list_cache()
619
 
 
620
 
    def _flush_ignore_list_cache(self):
621
 
        """Resets the cached ignore list to force a cache rebuild."""
622
 
        self._ignoreset = None
623
 
        self._ignoreglobster = None
624
 
 
625
 
    def is_ignored(self, filename):
626
 
        r"""Check whether the filename matches an ignore pattern.
627
 
 
628
 
        Patterns containing '/' or '\' need to match the whole path;
629
 
        others match against only the last component.  Patterns starting
630
 
        with '!' are ignore exceptions.  Exceptions take precedence
631
 
        over regular patterns and cause the filename to not be ignored.
632
 
 
633
 
        If the file is ignored, returns the pattern which caused it to
634
 
        be ignored, otherwise None.  So this can simply be used as a
635
 
        boolean if desired."""
636
 
        if getattr(self, '_ignoreglobster', None) is None:
637
 
            self._ignoreglobster = globbing.ExceptionGlobster(
638
 
                self.get_ignore_list())
639
 
        return self._ignoreglobster.match(filename)
640
 
 
641
 
    def read_basis_inventory(self):
642
 
        """Read the cached basis inventory."""
643
 
        path = self._basis_inventory_name()
644
 
        return osutils.split_lines(self._transport.get_bytes(path))
645
 
 
646
 
    def read_working_inventory(self):
647
 
        """Read the working inventory.
648
 
 
649
 
        :raises errors.InventoryModified: read_working_inventory will fail
650
 
            when the current in memory inventory has been modified.
651
 
        """
652
 
        # conceptually this should be an implementation detail of the tree.
653
 
        # XXX: Deprecate this.
654
 
        # ElementTree does its own conversion from UTF-8, so open in
655
 
        # binary.
656
 
        with self.lock_read():
657
 
            if self._inventory_is_modified:
658
 
                raise errors.InventoryModified(self)
659
 
            with self._transport.get('inventory') as f:
660
 
                result = self._deserialize(f)
661
 
            self._set_inventory(result, dirty=False)
662
 
            return result
663
 
 
664
 
    def all_file_ids(self):
665
 
        """Iterate through file_ids for this tree.
666
 
 
667
 
        file_ids are in a WorkingTree if they are in the working inventory
668
 
        and the working file exists.
669
 
        """
670
 
        return {ie.file_id for path, ie in self.iter_entries_by_dir()}
671
 
 
672
 
    def all_versioned_paths(self):
673
 
        return {path for path, ie in self.iter_entries_by_dir()}
674
 
 
675
 
    def set_last_revision(self, new_revision):
676
 
        """Change the last revision in the working tree."""
677
 
        with self.lock_tree_write():
678
 
            if self._change_last_revision(new_revision):
679
 
                self._cache_basis_inventory(new_revision)
680
 
 
681
 
    def _get_check_refs(self):
682
 
        """Return the references needed to perform a check of this tree.
683
 
 
684
 
        The default implementation returns no refs, and is only suitable for
685
 
        trees that have no local caching and can commit on ghosts at any time.
686
 
 
687
 
        :seealso: breezy.check for details about check_refs.
688
 
        """
689
 
        return []
690
 
 
691
 
    def _check(self, references):
692
 
        """Check the tree for consistency.
693
 
 
694
 
        :param references: A dict with keys matching the items returned by
695
 
            self._get_check_refs(), and values from looking those keys up in
696
 
            the repository.
697
 
        """
698
 
        with self.lock_read():
699
 
            tree_basis = self.basis_tree()
700
 
            with tree_basis.lock_read():
701
 
                repo_basis = references[('trees', self.last_revision())]
702
 
                if len(list(repo_basis.iter_changes(tree_basis))) > 0:
703
 
                    raise errors.BzrCheckError(
704
 
                        "Mismatched basis inventory content.")
705
 
                self._validate()
706
 
 
707
 
    def check_state(self):
708
 
        """Check that the working state is/isn't valid."""
709
 
        with self.lock_read():
710
 
            check_refs = self._get_check_refs()
711
 
            refs = {}
712
 
            for ref in check_refs:
713
 
                kind, value = ref
714
 
                if kind == 'trees':
715
 
                    refs[ref] = self.branch.repository.revision_tree(value)
716
 
            self._check(refs)
717
 
 
718
 
    def reset_state(self, revision_ids=None):
719
 
        """Reset the state of the working tree.
720
 
 
721
 
        This does a hard-reset to a last-known-good state. This is a way to
722
 
        fix if something got corrupted (like the .bzr/checkout/dirstate file)
723
 
        """
724
 
        with self.lock_tree_write():
725
 
            if revision_ids is None:
726
 
                revision_ids = self.get_parent_ids()
727
 
            if not revision_ids:
728
 
                rt = self.branch.repository.revision_tree(
729
 
                    _mod_revision.NULL_REVISION)
730
 
            else:
731
 
                rt = self.branch.repository.revision_tree(revision_ids[0])
732
 
            self._write_inventory(rt.root_inventory)
733
 
            self.set_parent_ids(revision_ids)
734
 
 
735
 
    def flush(self):
736
 
        """Write the in memory inventory to disk."""
737
 
        # TODO: Maybe this should only write on dirty ?
738
 
        if self._control_files._lock_mode != 'w':
739
 
            raise errors.NotWriteLocked(self)
740
 
        sio = BytesIO()
741
 
        self._serialize(self._inventory, sio)
742
 
        sio.seek(0)
743
 
        self._transport.put_file('inventory', sio,
744
 
                                 mode=self.controldir._get_file_mode())
745
 
        self._inventory_is_modified = False
746
 
 
747
 
    def get_file_mtime(self, path):
748
 
        """See Tree.get_file_mtime."""
749
 
        try:
750
 
            return os.lstat(self.abspath(path)).st_mtime
751
 
        except OSError as e:
752
 
            if e.errno == errno.ENOENT:
753
 
                raise errors.NoSuchFile(path)
754
 
            raise
755
 
 
756
 
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
757
 
        try:
758
 
            return self._path2ie(path).executable
759
 
        except errors.NoSuchFile:
760
 
            # For unversioned files on win32, we just assume they are not
761
 
            # executable
762
 
            return False
763
 
 
764
 
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
765
 
        mode = stat_result.st_mode
766
 
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
767
 
 
768
 
    def is_executable(self, path):
769
 
        if not self._supports_executable():
770
 
            ie = self._path2ie(path)
771
 
            return ie.executable
772
 
        else:
773
 
            mode = os.lstat(self.abspath(path)).st_mode
774
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
775
 
 
776
 
    def _is_executable_from_path_and_stat(self, path, stat_result):
777
 
        if not self._supports_executable():
778
 
            return self._is_executable_from_path_and_stat_from_basis(
779
 
                path, stat_result)
780
 
        else:
781
 
            return self._is_executable_from_path_and_stat_from_stat(
782
 
                path, stat_result)
783
 
 
784
 
    def _add(self, files, ids, kinds):
785
 
        """See MutableTree._add."""
786
 
        with self.lock_tree_write():
787
 
            # TODO: Re-adding a file that is removed in the working copy
788
 
            # should probably put it back with the previous ID.
789
 
            # the read and write working inventory should not occur in this
790
 
            # function - they should be part of lock_write and unlock.
791
 
            # FIXME: nested trees
792
 
            inv = self.root_inventory
793
 
            for f, file_id, kind in zip(files, ids, kinds):
794
 
                if file_id is None:
795
 
                    inv.add_path(f, kind=kind)
796
 
                else:
797
 
                    inv.add_path(f, kind=kind, file_id=file_id)
798
 
                self._inventory_is_modified = True
799
 
 
800
 
    def revision_tree(self, revision_id):
801
 
        """See WorkingTree.revision_id."""
802
 
        if revision_id == self.last_revision():
803
 
            try:
804
 
                xml_lines = self.read_basis_inventory()
805
 
            except errors.NoSuchFile:
806
 
                pass
807
 
            else:
808
 
                try:
809
 
                    inv = xml7.serializer_v7.read_inventory_from_lines(xml_lines)
810
 
                    # dont use the repository revision_tree api because we want
811
 
                    # to supply the inventory.
812
 
                    if inv.revision_id == revision_id:
813
 
                        return InventoryRevisionTree(
814
 
                            self.branch.repository, inv, revision_id)
815
 
                except errors.BadInventoryFormat:
816
 
                    pass
817
 
        # raise if there was no inventory, or if we read the wrong inventory.
818
 
        raise errors.NoSuchRevisionInTree(self, revision_id)
819
 
 
820
 
    def annotate_iter(self, path,
821
 
                      default_revision=_mod_revision.CURRENT_REVISION):
822
 
        """See Tree.annotate_iter
823
 
 
824
 
        This implementation will use the basis tree implementation if possible.
825
 
        Lines not in the basis are attributed to CURRENT_REVISION
826
 
 
827
 
        If there are pending merges, lines added by those merges will be
828
 
        incorrectly attributed to CURRENT_REVISION (but after committing, the
829
 
        attribution will be correct).
830
 
        """
831
 
        with self.lock_read():
832
 
            file_id = self.path2id(path)
833
 
            if file_id is None:
834
 
                raise errors.NoSuchFile(path)
835
 
            maybe_file_parent_keys = []
836
 
            for parent_id in self.get_parent_ids():
837
 
                try:
838
 
                    parent_tree = self.revision_tree(parent_id)
839
 
                except errors.NoSuchRevisionInTree:
840
 
                    parent_tree = self.branch.repository.revision_tree(
841
 
                        parent_id)
842
 
                with parent_tree.lock_read():
843
 
 
844
 
                    try:
845
 
                        kind = parent_tree.kind(path)
846
 
                    except errors.NoSuchFile:
847
 
                        continue
848
 
                    if kind != 'file':
849
 
                        # Note: this is slightly unnecessary, because symlinks
850
 
                        # and directories have a "text" which is the empty
851
 
                        # text, and we know that won't mess up annotations. But
852
 
                        # it seems cleaner
853
 
                        continue
854
 
                    parent_path = parent_tree.id2path(file_id)
855
 
                    parent_text_key = (
856
 
                        file_id,
857
 
                        parent_tree.get_file_revision(parent_path))
858
 
                    if parent_text_key not in maybe_file_parent_keys:
859
 
                        maybe_file_parent_keys.append(parent_text_key)
860
 
            graph = self.branch.repository.get_file_graph()
861
 
            heads = graph.heads(maybe_file_parent_keys)
862
 
            file_parent_keys = []
863
 
            for key in maybe_file_parent_keys:
864
 
                if key in heads:
865
 
                    file_parent_keys.append(key)
866
 
 
867
 
            # Now we have the parents of this content
868
 
            annotator = self.branch.repository.texts.get_annotator()
869
 
            text = self.get_file_text(path)
870
 
            this_key = (file_id, default_revision)
871
 
            annotator.add_special_text(this_key, file_parent_keys, text)
872
 
            annotations = [(key[-1], line)
873
 
                           for key, line in annotator.annotate_flat(this_key)]
874
 
            return annotations
875
 
 
876
 
    def _put_rio(self, filename, stanzas, header):
877
 
        self._must_be_locked()
878
 
        my_file = _mod_rio.rio_file(stanzas, header)
879
 
        self._transport.put_file(filename, my_file,
880
 
                                 mode=self.controldir._get_file_mode())
881
 
 
882
 
    def set_merge_modified(self, modified_hashes):
883
 
        def iter_stanzas():
884
 
            for path, sha1 in modified_hashes.items():
885
 
                file_id = self.path2id(path)
886
 
                if file_id is None:
887
 
                    continue
888
 
                yield _mod_rio.Stanza(file_id=file_id.decode('utf8'),
889
 
                                      hash=sha1)
890
 
        with self.lock_tree_write():
891
 
            self._put_rio('merge-hashes', iter_stanzas(),
892
 
                          MERGE_MODIFIED_HEADER_1)
893
 
 
894
 
    def merge_modified(self):
895
 
        """Return a dictionary of files modified by a merge.
896
 
 
897
 
        The list is initialized by WorkingTree.set_merge_modified, which is
898
 
        typically called after we make some automatic updates to the tree
899
 
        because of a merge.
900
 
 
901
 
        This returns a map of file_id->sha1, containing only files which are
902
 
        still in the working inventory and have that text hash.
903
 
        """
904
 
        with self.lock_read():
905
 
            try:
906
 
                hashfile = self._transport.get('merge-hashes')
907
 
            except errors.NoSuchFile:
908
 
                return {}
909
 
            try:
910
 
                merge_hashes = {}
911
 
                try:
912
 
                    if next(hashfile) != MERGE_MODIFIED_HEADER_1 + b'\n':
913
 
                        raise errors.MergeModifiedFormatError()
914
 
                except StopIteration:
915
 
                    raise errors.MergeModifiedFormatError()
916
 
                for s in _mod_rio.RioReader(hashfile):
917
 
                    # RioReader reads in Unicode, so convert file_ids back to
918
 
                    # utf8
919
 
                    file_id = cache_utf8.encode(s.get("file_id"))
920
 
                    try:
921
 
                        path = self.id2path(file_id)
922
 
                    except errors.NoSuchId:
923
 
                        continue
924
 
                    text_hash = s.get("hash").encode('ascii')
925
 
                    if text_hash == self.get_file_sha1(path):
926
 
                        merge_hashes[path] = text_hash
927
 
                return merge_hashes
928
 
            finally:
929
 
                hashfile.close()
930
 
 
931
 
    def subsume(self, other_tree):
932
 
        def add_children(inventory, entry):
933
 
            for child_entry in entry.children.values():
934
 
                inventory._byid[child_entry.file_id] = child_entry
935
 
                if child_entry.kind == 'directory':
936
 
                    add_children(inventory, child_entry)
937
 
        with self.lock_write():
938
 
            if other_tree.path2id('') == self.path2id(''):
939
 
                raise errors.BadSubsumeSource(self, other_tree,
940
 
                                              'Trees have the same root')
941
 
            try:
942
 
                other_tree_path = self.relpath(other_tree.basedir)
943
 
            except errors.PathNotChild:
944
 
                raise errors.BadSubsumeSource(
945
 
                    self, other_tree, 'Tree is not contained by the other')
946
 
            new_root_parent = self.path2id(osutils.dirname(other_tree_path))
947
 
            if new_root_parent is None:
948
 
                raise errors.BadSubsumeSource(
949
 
                    self, other_tree, 'Parent directory is not versioned.')
950
 
            # We need to ensure that the result of a fetch will have a
951
 
            # versionedfile for the other_tree root, and only fetching into
952
 
            # RepositoryKnit2 guarantees that.
953
 
            if not self.branch.repository.supports_rich_root():
954
 
                raise errors.SubsumeTargetNeedsUpgrade(other_tree)
955
 
            with other_tree.lock_tree_write():
956
 
                other_root = other_tree.root_inventory.root
957
 
                other_root.parent_id = new_root_parent
958
 
                other_root.name = osutils.basename(other_tree_path)
959
 
                self.root_inventory.add(other_root)
960
 
                add_children(self.root_inventory, other_root)
961
 
                self._write_inventory(self.root_inventory)
962
 
                # normally we don't want to fetch whole repositories, but i
963
 
                # think here we really do want to consolidate the whole thing.
964
 
                for parent_id in other_tree.get_parent_ids():
965
 
                    self.branch.fetch(other_tree.branch, parent_id)
966
 
                    self.add_parent_tree_id(parent_id)
967
 
            other_tree.controldir.retire_bzrdir()
968
 
 
969
 
    def extract(self, sub_path, format=None):
970
 
        """Extract a subtree from this tree.
971
 
 
972
 
        A new branch will be created, relative to the path for this tree.
973
 
        """
974
 
        def mkdirs(path):
975
 
            segments = osutils.splitpath(path)
976
 
            transport = self.branch.controldir.root_transport
977
 
            for name in segments:
978
 
                transport = transport.clone(name)
979
 
                transport.ensure_base()
980
 
            return transport
981
 
 
982
 
        with self.lock_tree_write():
983
 
            self.flush()
984
 
            branch_transport = mkdirs(sub_path)
985
 
            if format is None:
986
 
                format = self.controldir.cloning_metadir()
987
 
            branch_transport.ensure_base()
988
 
            branch_bzrdir = format.initialize_on_transport(branch_transport)
989
 
            try:
990
 
                repo = branch_bzrdir.find_repository()
991
 
            except errors.NoRepositoryPresent:
992
 
                repo = branch_bzrdir.create_repository()
993
 
            if not repo.supports_rich_root():
994
 
                raise errors.RootNotRich()
995
 
            new_branch = branch_bzrdir.create_branch()
996
 
            new_branch.pull(self.branch)
997
 
            for parent_id in self.get_parent_ids():
998
 
                new_branch.fetch(self.branch, parent_id)
999
 
            tree_transport = self.controldir.root_transport.clone(sub_path)
1000
 
            if tree_transport.base != branch_transport.base:
1001
 
                tree_bzrdir = format.initialize_on_transport(tree_transport)
1002
 
                tree_bzrdir.set_branch_reference(new_branch)
1003
 
            else:
1004
 
                tree_bzrdir = branch_bzrdir
1005
 
            wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
1006
 
            wt.set_parent_ids(self.get_parent_ids())
1007
 
            # FIXME: Support nested trees
1008
 
            my_inv = self.root_inventory
1009
 
            child_inv = inventory.Inventory(root_id=None)
1010
 
            file_id = self.path2id(sub_path)
1011
 
            new_root = my_inv.get_entry(file_id)
1012
 
            my_inv.remove_recursive_id(file_id)
1013
 
            new_root.parent_id = None
1014
 
            child_inv.add(new_root)
1015
 
            self._write_inventory(my_inv)
1016
 
            wt._write_inventory(child_inv)
1017
 
            return wt
1018
 
 
1019
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
1020
 
                   recurse_nested=False):
1021
 
        """List all files as (path, class, kind, id, entry).
1022
 
 
1023
 
        Lists, but does not descend into unversioned directories.
1024
 
        This does not include files that have been deleted in this
1025
 
        tree. Skips the control directory.
1026
 
 
1027
 
        :param include_root: if True, return an entry for the root
1028
 
        :param from_dir: start from this directory or None for the root
1029
 
        :param recursive: whether to recurse into subdirectories or not
1030
 
        """
1031
 
        with cleanup.ExitStack() as exit_stack:
1032
 
            exit_stack.enter_context(self.lock_read())
1033
 
            if from_dir is None and include_root is True:
1034
 
                yield ('', 'V', 'directory', self.root_inventory.root)
1035
 
            # Convert these into local objects to save lookup times
1036
 
            pathjoin = osutils.pathjoin
1037
 
 
1038
 
            # transport.base ends in a slash, we want the piece
1039
 
            # between the last two slashes
1040
 
            transport_base_dir = self.controldir.transport.base.rsplit(
1041
 
                '/', 2)[1]
1042
 
 
1043
 
            fk_entries = {
1044
 
                'directory': TreeDirectory,
1045
 
                'file': TreeFile,
1046
 
                'symlink': TreeLink,
1047
 
                'tree-reference': TreeReference,
1048
 
                }
1049
 
 
1050
 
            # directory file_id, relative path, absolute path, reverse sorted
1051
 
            # children
1052
 
            if from_dir is not None:
1053
 
                from_inv, from_dir_id = self._path2inv_file_id(from_dir)
1054
 
                if from_dir_id is None:
1055
 
                    # Directory not versioned
1056
 
                    return
1057
 
                from_dir_abspath = pathjoin(self.basedir, from_dir)
1058
 
            else:
1059
 
                from_inv = self.root_inventory
1060
 
                from_dir_id = from_inv.root.file_id
1061
 
                from_dir_abspath = self.basedir
1062
 
            children = sorted(os.listdir(from_dir_abspath))
1063
 
            # jam 20060527 The kernel sized tree seems equivalent whether we
1064
 
            # use a deque and popleft to keep them sorted, or if we use a plain
1065
 
            # list and just reverse() them.
1066
 
            children = deque(children)
1067
 
            stack = [(from_inv, from_dir_id, u'', from_dir_abspath, children)]
1068
 
            while stack:
1069
 
                (inv, from_dir_id, from_dir_relpath, from_dir_abspath,
1070
 
                 children) = stack[-1]
1071
 
 
1072
 
                while children:
1073
 
                    f = children.popleft()
1074
 
                    # TODO: If we find a subdirectory with its own .bzr
1075
 
                    # directory, then that is a separate tree and we
1076
 
                    # should exclude it.
1077
 
 
1078
 
                    # the bzrdir for this tree
1079
 
                    if transport_base_dir == f:
1080
 
                        continue
1081
 
 
1082
 
                    # we know that from_dir_relpath and from_dir_abspath never
1083
 
                    # end in a slash and 'f' doesn't begin with one, we can do
1084
 
                    # a string op, rather than the checks of pathjoin(), all
1085
 
                    # relative paths will have an extra slash at the beginning
1086
 
                    fp = from_dir_relpath + '/' + f
1087
 
 
1088
 
                    # absolute path
1089
 
                    fap = from_dir_abspath + '/' + f
1090
 
 
1091
 
                    dir_ie = inv.get_entry(from_dir_id)
1092
 
                    if dir_ie.kind == 'directory':
1093
 
                        f_ie = dir_ie.children.get(f)
1094
 
                    else:
1095
 
                        f_ie = None
1096
 
                    if f_ie:
1097
 
                        c = 'V'
1098
 
                    elif self.is_ignored(fp[1:]):
1099
 
                        c = 'I'
1100
 
                    else:
1101
 
                        # we may not have found this file, because of a unicode
1102
 
                        # issue, or because the directory was actually a
1103
 
                        # symlink.
1104
 
                        f_norm, can_access = osutils.normalized_filename(f)
1105
 
                        if f == f_norm or not can_access:
1106
 
                            # No change, so treat this file normally
1107
 
                            c = '?'
1108
 
                        else:
1109
 
                            # this file can be accessed by a normalized path
1110
 
                            # check again if it is versioned
1111
 
                            # these lines are repeated here for performance
1112
 
                            f = f_norm
1113
 
                            fp = from_dir_relpath + '/' + f
1114
 
                            fap = from_dir_abspath + '/' + f
1115
 
                            f_ie = inv.get_child(from_dir_id, f)
1116
 
                            if f_ie:
1117
 
                                c = 'V'
1118
 
                            elif self.is_ignored(fp[1:]):
1119
 
                                c = 'I'
1120
 
                            else:
1121
 
                                c = '?'
1122
 
 
1123
 
                    fk = osutils.file_kind(fap)
1124
 
                    if fk == 'directory' and self._directory_is_tree_reference(f):
1125
 
                        if not recurse_nested:
1126
 
                            fk = 'tree-reference'
1127
 
                        else:
1128
 
                            subtree = self.get_nested_tree(f)
1129
 
                            exit_stack.enter_context(subtree.lock_read())
1130
 
                            inv = subtree.root_inventory
1131
 
                            f_ie = inv.get_entry(f_ie.file_id)
1132
 
                            fk = 'directory'
1133
 
 
1134
 
                    # make a last minute entry
1135
 
                    if f_ie:
1136
 
                        yield fp[1:], c, fk, f_ie
1137
 
                    else:
1138
 
                        try:
1139
 
                            yield fp[1:], c, fk, fk_entries[fk]()
1140
 
                        except KeyError:
1141
 
                            yield fp[1:], c, fk, TreeEntry()
1142
 
                        continue
1143
 
 
1144
 
                    if fk != 'directory':
1145
 
                        continue
1146
 
 
1147
 
                    # But do this child first if recursing down
1148
 
                    if recursive:
1149
 
                        new_children = sorted(os.listdir(fap))
1150
 
                        new_children = deque(new_children)
1151
 
                        stack.append((inv, f_ie.file_id, fp, fap, new_children))
1152
 
                        # Break out of inner loop,
1153
 
                        # so that we start outer loop with child
1154
 
                        break
1155
 
                else:
1156
 
                    # if we finished all children, pop it off the stack
1157
 
                    stack.pop()
1158
 
 
1159
 
    def move(self, from_paths, to_dir=None, after=False):
1160
 
        """Rename files.
1161
 
 
1162
 
        to_dir must exist in the inventory.
1163
 
 
1164
 
        If to_dir exists and is a directory, the files are moved into
1165
 
        it, keeping their old names.
1166
 
 
1167
 
        Note that to_dir is only the last component of the new name;
1168
 
        this doesn't change the directory.
1169
 
 
1170
 
        For each entry in from_paths the move mode will be determined
1171
 
        independently.
1172
 
 
1173
 
        The first mode moves the file in the filesystem and updates the
1174
 
        inventory. The second mode only updates the inventory without
1175
 
        touching the file on the filesystem.
1176
 
 
1177
 
        move uses the second mode if 'after == True' and the target is
1178
 
        either not versioned or newly added, and present in the working tree.
1179
 
 
1180
 
        move uses the second mode if 'after == False' and the source is
1181
 
        versioned but no longer in the working tree, and the target is not
1182
 
        versioned but present in the working tree.
1183
 
 
1184
 
        move uses the first mode if 'after == False' and the source is
1185
 
        versioned and present in the working tree, and the target is not
1186
 
        versioned and not present in the working tree.
1187
 
 
1188
 
        Everything else results in an error.
1189
 
 
1190
 
        This returns a list of (from_path, to_path) pairs for each
1191
 
        entry that is moved.
1192
 
        """
1193
 
        rename_entries = []
1194
 
        rename_tuples = []
1195
 
 
1196
 
        # check for deprecated use of signature
1197
 
        if to_dir is None:
1198
 
            raise TypeError('You must supply a target directory')
1199
 
        # check destination directory
1200
 
        if isinstance(from_paths, (str, text_type)):
1201
 
            raise ValueError()
1202
 
        with self.lock_tree_write():
1203
 
            to_abs = self.abspath(to_dir)
1204
 
            if not osutils.isdir(to_abs):
1205
 
                raise errors.BzrMoveFailedError(
1206
 
                    '', to_dir, errors.NotADirectory(to_abs))
1207
 
            if not self.has_filename(to_dir):
1208
 
                raise errors.BzrMoveFailedError(
1209
 
                    '', to_dir, errors.NotInWorkingDirectory(to_dir))
1210
 
            to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1211
 
            if to_dir_id is None:
1212
 
                raise errors.BzrMoveFailedError(
1213
 
                    '', to_dir, errors.NotVersionedError(path=to_dir))
1214
 
 
1215
 
            to_dir_ie = to_inv.get_entry(to_dir_id)
1216
 
            if to_dir_ie.kind != 'directory':
1217
 
                raise errors.BzrMoveFailedError(
1218
 
                    '', to_dir, errors.NotADirectory(to_abs))
1219
 
 
1220
 
            # create rename entries and tuples
1221
 
            for from_rel in from_paths:
1222
 
                from_tail = osutils.splitpath(from_rel)[-1]
1223
 
                from_inv, from_id = self._path2inv_file_id(from_rel)
1224
 
                if from_id is None:
1225
 
                    raise errors.BzrMoveFailedError(from_rel, to_dir,
1226
 
                                                    errors.NotVersionedError(path=from_rel))
1227
 
 
1228
 
                from_entry = from_inv.get_entry(from_id)
1229
 
                from_parent_id = from_entry.parent_id
1230
 
                to_rel = osutils.pathjoin(to_dir, from_tail)
1231
 
                rename_entry = InventoryWorkingTree._RenameEntry(
1232
 
                    from_rel=from_rel,
1233
 
                    from_id=from_id,
1234
 
                    from_tail=from_tail,
1235
 
                    from_parent_id=from_parent_id,
1236
 
                    to_rel=to_rel, to_tail=from_tail,
1237
 
                    to_parent_id=to_dir_id)
1238
 
                rename_entries.append(rename_entry)
1239
 
                rename_tuples.append((from_rel, to_rel))
1240
 
 
1241
 
            # determine which move mode to use. checks also for movability
1242
 
            rename_entries = self._determine_mv_mode(rename_entries, after)
1243
 
 
1244
 
            original_modified = self._inventory_is_modified
1245
 
            try:
1246
 
                if len(from_paths):
1247
 
                    self._inventory_is_modified = True
1248
 
                self._move(rename_entries)
1249
 
            except BaseException:
1250
 
                # restore the inventory on error
1251
 
                self._inventory_is_modified = original_modified
1252
 
                raise
1253
 
            # TODO(jelmer): what about the from_invs?
1254
 
            self._write_inventory(to_inv)
1255
 
            return rename_tuples
1256
 
 
1257
 
    def rename_one(self, from_rel, to_rel, after=False):
1258
 
        """Rename one file.
1259
 
 
1260
 
        This can change the directory or the filename or both.
1261
 
 
1262
 
        rename_one has several 'modes' to work. First, it can rename a physical
1263
 
        file and change the file_id. That is the normal mode. Second, it can
1264
 
        only change the file_id without touching any physical file.
1265
 
 
1266
 
        rename_one uses the second mode if 'after == True' and 'to_rel' is not
1267
 
        versioned but present in the working tree.
1268
 
 
1269
 
        rename_one uses the second mode if 'after == False' and 'from_rel' is
1270
 
        versioned but no longer in the working tree, and 'to_rel' is not
1271
 
        versioned but present in the working tree.
1272
 
 
1273
 
        rename_one uses the first mode if 'after == False' and 'from_rel' is
1274
 
        versioned and present in the working tree, and 'to_rel' is not
1275
 
        versioned and not present in the working tree.
1276
 
 
1277
 
        Everything else results in an error.
1278
 
        """
1279
 
        with self.lock_tree_write():
1280
 
            rename_entries = []
1281
 
 
1282
 
            # create rename entries and tuples
1283
 
            from_tail = osutils.splitpath(from_rel)[-1]
1284
 
            from_inv, from_id = self._path2inv_file_id(from_rel)
1285
 
            if from_id is None:
1286
 
                # if file is missing in the inventory maybe it's in the
1287
 
                # basis_tree
1288
 
                # TODO(jelmer): This is going to break with nested trees.
1289
 
                from_inv = self.root_inventory
1290
 
                basis_tree = self.branch.basis_tree()
1291
 
                basis_from_inv, from_id = basis_tree._path2inv_file_id(from_rel)
1292
 
                if from_id is None:
1293
 
                    raise errors.BzrRenameFailedError(
1294
 
                        from_rel, to_rel,
1295
 
                        errors.NotVersionedError(path=from_rel))
1296
 
                try:
1297
 
                    from_entry = from_inv.get_entry(from_id)
1298
 
                except errors.NoSuchId:
1299
 
                    # put entry back in the inventory so we can rename it
1300
 
                    from_entry = basis_from_inv.get_entry(from_id).copy()
1301
 
                    from_inv.add(from_entry)
1302
 
            else:
1303
 
                from_inv, from_inv_id = self._unpack_file_id(from_id)
1304
 
                from_entry = from_inv.get_entry(from_inv_id)
1305
 
            from_parent_id = from_entry.parent_id
1306
 
            to_dir, to_tail = os.path.split(to_rel)
1307
 
            to_inv, to_dir_id = self._path2inv_file_id(to_dir)
1308
 
            rename_entry = InventoryWorkingTree._RenameEntry(
1309
 
                from_rel=from_rel,
1310
 
                from_id=from_id,
1311
 
                from_tail=from_tail,
1312
 
                from_parent_id=from_parent_id,
1313
 
                to_rel=to_rel, to_tail=to_tail,
1314
 
                to_parent_id=to_dir_id)
1315
 
            rename_entries.append(rename_entry)
1316
 
 
1317
 
            # determine which move mode to use. checks also for movability
1318
 
            rename_entries = self._determine_mv_mode(rename_entries, after)
1319
 
 
1320
 
            # check if the target changed directory and if the target directory
1321
 
            # is versioned
1322
 
            if to_dir_id is None:
1323
 
                raise errors.BzrMoveFailedError(
1324
 
                    from_rel, to_rel, errors.NotVersionedError(path=to_dir))
1325
 
 
1326
 
            # all checks done. now we can continue with our actual work
1327
 
            mutter('rename_one:\n'
1328
 
                   '  from_id   {%s}\n'
1329
 
                   '  from_rel: %r\n'
1330
 
                   '  to_rel:   %r\n'
1331
 
                   '  to_dir    %r\n'
1332
 
                   '  to_dir_id {%s}\n',
1333
 
                   from_id, from_rel, to_rel, to_dir, to_dir_id)
1334
 
 
1335
 
            self._move(rename_entries)
1336
 
            self._write_inventory(to_inv)
1337
 
 
1338
 
    class _RenameEntry(object):
1339
 
        def __init__(self, from_rel, from_id, from_tail, from_parent_id,
1340
 
                     to_rel, to_tail, to_parent_id, only_change_inv=False,
1341
 
                     change_id=False):
1342
 
            self.from_rel = from_rel
1343
 
            self.from_id = from_id
1344
 
            self.from_tail = from_tail
1345
 
            self.from_parent_id = from_parent_id
1346
 
            self.to_rel = to_rel
1347
 
            self.to_tail = to_tail
1348
 
            self.to_parent_id = to_parent_id
1349
 
            self.change_id = change_id
1350
 
            self.only_change_inv = only_change_inv
1351
 
 
1352
 
    def _determine_mv_mode(self, rename_entries, after=False):
1353
 
        """Determines for each from-to pair if both inventory and working tree
1354
 
        or only the inventory has to be changed.
1355
 
 
1356
 
        Also does basic plausability tests.
1357
 
        """
1358
 
        # FIXME: Handling of nested trees
1359
 
        inv = self.root_inventory
1360
 
 
1361
 
        for rename_entry in rename_entries:
1362
 
            # store to local variables for easier reference
1363
 
            from_rel = rename_entry.from_rel
1364
 
            from_id = rename_entry.from_id
1365
 
            to_rel = rename_entry.to_rel
1366
 
            to_id = inv.path2id(to_rel)
1367
 
            only_change_inv = False
1368
 
 
1369
 
            # check the inventory for source and destination
1370
 
            if from_id is None:
1371
 
                raise errors.BzrMoveFailedError(
1372
 
                    from_rel, to_rel, errors.NotVersionedError(path=from_rel))
1373
 
            if to_id is not None:
1374
 
                allowed = False
1375
 
                # allow it with --after but only if dest is newly added
1376
 
                if after:
1377
 
                    basis = self.basis_tree()
1378
 
                    with basis.lock_read():
1379
 
                        try:
1380
 
                            basis.id2path(to_id)
1381
 
                        except errors.NoSuchId:
1382
 
                            rename_entry.change_id = True
1383
 
                            allowed = True
1384
 
                if not allowed:
1385
 
                    raise errors.BzrMoveFailedError(
1386
 
                        from_rel, to_rel,
1387
 
                        errors.AlreadyVersionedError(path=to_rel))
1388
 
 
1389
 
            # try to determine the mode for rename (only change inv or change
1390
 
            # inv and file system)
1391
 
            if after:
1392
 
                if not self.has_filename(to_rel):
1393
 
                    raise errors.BzrMoveFailedError(
1394
 
                        from_rel, to_rel,
1395
 
                        errors.NoSuchFile(
1396
 
                            path=to_rel,
1397
 
                            extra="New file has not been created yet"))
1398
 
                only_change_inv = True
1399
 
            elif not self.has_filename(from_rel) and self.has_filename(to_rel):
1400
 
                only_change_inv = True
1401
 
            elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1402
 
                only_change_inv = False
1403
 
            elif (not self.case_sensitive and
1404
 
                  from_rel.lower() == to_rel.lower() and
1405
 
                  self.has_filename(from_rel)):
1406
 
                only_change_inv = False
1407
 
            else:
1408
 
                # something is wrong, so lets determine what exactly
1409
 
                if not self.has_filename(from_rel) and \
1410
 
                   not self.has_filename(to_rel):
1411
 
                    raise errors.BzrRenameFailedError(
1412
 
                        from_rel, to_rel,
1413
 
                        errors.PathsDoNotExist(paths=(from_rel, to_rel)))
1414
 
                else:
1415
 
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
1416
 
            rename_entry.only_change_inv = only_change_inv
1417
 
        return rename_entries
1418
 
 
1419
 
    def _move(self, rename_entries):
1420
 
        """Moves a list of files.
1421
 
 
1422
 
        Depending on the value of the flag 'only_change_inv', the
1423
 
        file will be moved on the file system or not.
1424
 
        """
1425
 
        moved = []
1426
 
 
1427
 
        for entry in rename_entries:
1428
 
            try:
1429
 
                self._move_entry(entry)
1430
 
            except BaseException:
1431
 
                self._rollback_move(moved)
1432
 
                raise
1433
 
            moved.append(entry)
1434
 
 
1435
 
    def _rollback_move(self, moved):
1436
 
        """Try to rollback a previous move in case of an filesystem error."""
1437
 
        for entry in moved:
1438
 
            try:
1439
 
                self._move_entry(WorkingTree._RenameEntry(
1440
 
                    entry.to_rel, entry.from_id,
1441
 
                    entry.to_tail, entry.to_parent_id, entry.from_rel,
1442
 
                    entry.from_tail, entry.from_parent_id,
1443
 
                    entry.only_change_inv))
1444
 
            except errors.BzrMoveFailedError as e:
1445
 
                raise errors.BzrMoveFailedError(
1446
 
                    '', '', "Rollback failed."
1447
 
                    " The working tree is in an inconsistent state."
1448
 
                    " Please consider doing a 'bzr revert'."
1449
 
                    " Error message is: %s" % e)
1450
 
 
1451
 
    def _move_entry(self, entry):
1452
 
        inv = self.root_inventory
1453
 
        from_rel_abs = self.abspath(entry.from_rel)
1454
 
        to_rel_abs = self.abspath(entry.to_rel)
1455
 
        if from_rel_abs == to_rel_abs:
1456
 
            raise errors.BzrMoveFailedError(entry.from_rel, entry.to_rel,
1457
 
                                            "Source and target are identical.")
1458
 
 
1459
 
        if not entry.only_change_inv:
1460
 
            try:
1461
 
                osutils.rename(from_rel_abs, to_rel_abs)
1462
 
            except OSError as e:
1463
 
                raise errors.BzrMoveFailedError(
1464
 
                    entry.from_rel, entry.to_rel, e[1])
1465
 
        if entry.change_id:
1466
 
            to_id = inv.path2id(entry.to_rel)
1467
 
            inv.remove_recursive_id(to_id)
1468
 
        inv.rename(entry.from_id, entry.to_parent_id, entry.to_tail)
1469
 
 
1470
 
    def unversion(self, paths):
1471
 
        """Remove the paths in paths from the current versioned set.
1472
 
 
1473
 
        When a path is unversioned, all of its children are automatically
1474
 
        unversioned.
1475
 
 
1476
 
        :param paths: The paths to stop versioning.
1477
 
        :raises NoSuchFile: if any path is not currently versioned.
1478
 
        """
1479
 
        with self.lock_tree_write():
1480
 
            file_ids = set()
1481
 
            for path in paths:
1482
 
                file_id = self._inventory.path2id(path)
1483
 
                if file_id is None:
1484
 
                    raise errors.NoSuchFile(path, self)
1485
 
                file_ids.add(file_id)
1486
 
            for file_id in file_ids:
1487
 
                if self._inventory.has_id(file_id):
1488
 
                    self._inventory.remove_recursive_id(file_id)
1489
 
            if len(file_ids):
1490
 
                # in the future this should just set a dirty bit to wait for
1491
 
                # the final unlock. However, until all methods of workingtree
1492
 
                # start with the current in -memory inventory rather than
1493
 
                # triggering a read, it is more complex - we need to teach
1494
 
                # read_inventory to know when to read, and when to not read
1495
 
                # first... and possibly to save first when the in memory one
1496
 
                # may be corrupted.  so for now, we just only write it if it is
1497
 
                # indeed dirty.  - RBC 20060907
1498
 
                self._write_inventory(self._inventory)
1499
 
 
1500
 
    def stored_kind(self, path):
1501
 
        """See Tree.stored_kind"""
1502
 
        return self._path2ie(path).kind
1503
 
 
1504
 
    def extras(self):
1505
 
        """Yield all unversioned files in this WorkingTree.
1506
 
 
1507
 
        If there are any unversioned directories then only the directory is
1508
 
        returned, not all its children.  But if there are unversioned files
1509
 
        under a versioned subdirectory, they are returned.
1510
 
 
1511
 
        Currently returned depth-first, sorted by name within directories.
1512
 
        This is the same order used by 'osutils.walkdirs'.
1513
 
        """
1514
 
        # TODO: Work from given directory downwards
1515
 
        for path, dir_entry in self.iter_entries_by_dir():
1516
 
            if dir_entry.kind != 'directory':
1517
 
                continue
1518
 
            # mutter("search for unknowns in %r", path)
1519
 
            dirabs = self.abspath(path)
1520
 
            if not osutils.isdir(dirabs):
1521
 
                # e.g. directory deleted
1522
 
                continue
1523
 
 
1524
 
            fl = []
1525
 
            for subf in os.listdir(dirabs.encode(osutils._fs_enc)):
1526
 
                try:
1527
 
                    subf = subf.decode(osutils._fs_enc)
1528
 
                except UnicodeDecodeError:
1529
 
                    path_os_enc = path.encode(osutils._fs_enc)
1530
 
                    relpath = path_os_enc + b'/' + subf
1531
 
                    raise errors.BadFilenameEncoding(relpath,
1532
 
                                                     osutils._fs_enc)
1533
 
 
1534
 
                if self.controldir.is_control_filename(subf):
1535
 
                    continue
1536
 
                if subf not in dir_entry.children:
1537
 
                    try:
1538
 
                        (subf_norm,
1539
 
                         can_access) = osutils.normalized_filename(subf)
1540
 
                    except UnicodeDecodeError:
1541
 
                        path_os_enc = path.encode(osutils._fs_enc)
1542
 
                        relpath = path_os_enc + '/' + subf
1543
 
                        raise errors.BadFilenameEncoding(relpath,
1544
 
                                                         osutils._fs_enc)
1545
 
                    if subf_norm != subf and can_access:
1546
 
                        if subf_norm not in dir_entry.children:
1547
 
                            fl.append(subf_norm)
1548
 
                    else:
1549
 
                        fl.append(subf)
1550
 
 
1551
 
            fl.sort()
1552
 
            for subf in fl:
1553
 
                subp = osutils.pathjoin(path, subf)
1554
 
                yield subp
1555
 
 
1556
 
    def walkdirs(self, prefix=""):
1557
 
        """Walk the directories of this tree.
1558
 
 
1559
 
        returns a generator which yields items in the form:
1560
 
                ((curren_directory_path, fileid),
1561
 
                 [(file1_path, file1_name, file1_kind, (lstat), file1_id,
1562
 
                   file1_kind), ... ])
1563
 
 
1564
 
        This API returns a generator, which is only valid during the current
1565
 
        tree transaction - within a single lock_read or lock_write duration.
1566
 
 
1567
 
        If the tree is not locked, it may cause an error to be raised,
1568
 
        depending on the tree implementation.
1569
 
        """
1570
 
        disk_top = self.abspath(prefix)
1571
 
        if disk_top.endswith('/'):
1572
 
            disk_top = disk_top[:-1]
1573
 
        top_strip_len = len(disk_top) + 1
1574
 
        inventory_iterator = self._walkdirs(prefix)
1575
 
        disk_iterator = osutils.walkdirs(disk_top, prefix)
1576
 
        try:
1577
 
            current_disk = next(disk_iterator)
1578
 
            disk_finished = False
1579
 
        except OSError as e:
1580
 
            if not (e.errno == errno.ENOENT
1581
 
                    or (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
1582
 
                raise
1583
 
            current_disk = None
1584
 
            disk_finished = True
1585
 
        try:
1586
 
            current_inv = next(inventory_iterator)
1587
 
            inv_finished = False
1588
 
        except StopIteration:
1589
 
            current_inv = None
1590
 
            inv_finished = True
1591
 
        while not inv_finished or not disk_finished:
1592
 
            if current_disk:
1593
 
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
1594
 
                    cur_disk_dir_content) = current_disk
1595
 
            else:
1596
 
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
1597
 
                    cur_disk_dir_content) = ((None, None), None)
1598
 
            if not disk_finished:
1599
 
                # strip out .bzr dirs
1600
 
                if (cur_disk_dir_path_from_top[top_strip_len:] == ''
1601
 
                        and len(cur_disk_dir_content) > 0):
1602
 
                    # osutils.walkdirs can be made nicer -
1603
 
                    # yield the path-from-prefix rather than the pathjoined
1604
 
                    # value.
1605
 
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
1606
 
                                             ('.bzr', '.bzr'))
1607
 
                    if (bzrdir_loc < len(cur_disk_dir_content) and
1608
 
                        self.controldir.is_control_filename(
1609
 
                            cur_disk_dir_content[bzrdir_loc][0])):
1610
 
                        # we dont yield the contents of, or, .bzr itself.
1611
 
                        del cur_disk_dir_content[bzrdir_loc]
1612
 
            if inv_finished:
1613
 
                # everything is unknown
1614
 
                direction = 1
1615
 
            elif disk_finished:
1616
 
                # everything is missing
1617
 
                direction = -1
1618
 
            else:
1619
 
                direction = ((current_inv[0][0] > cur_disk_dir_relpath)
1620
 
                             - (current_inv[0][0] < cur_disk_dir_relpath))
1621
 
 
1622
 
            if direction > 0:
1623
 
                # disk is before inventory - unknown
1624
 
                dirblock = [(relpath, basename, kind, stat, None, None) for
1625
 
                            relpath, basename, kind, stat, top_path in
1626
 
                            cur_disk_dir_content]
1627
 
                yield (cur_disk_dir_relpath, None), dirblock
1628
 
                try:
1629
 
                    current_disk = next(disk_iterator)
1630
 
                except StopIteration:
1631
 
                    disk_finished = True
1632
 
            elif direction < 0:
1633
 
                # inventory is before disk - missing.
1634
 
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1635
 
                            for relpath, basename, dkind, stat, fileid, kind in
1636
 
                            current_inv[1]]
1637
 
                yield (current_inv[0][0], current_inv[0][1]), dirblock
1638
 
                try:
1639
 
                    current_inv = next(inventory_iterator)
1640
 
                except StopIteration:
1641
 
                    inv_finished = True
1642
 
            else:
1643
 
                # versioned present directory
1644
 
                # merge the inventory and disk data together
1645
 
                dirblock = []
1646
 
                for relpath, subiterator in itertools.groupby(sorted(
1647
 
                        current_inv[1] + cur_disk_dir_content,
1648
 
                        key=operator.itemgetter(0)), operator.itemgetter(1)):
1649
 
                    path_elements = list(subiterator)
1650
 
                    if len(path_elements) == 2:
1651
 
                        inv_row, disk_row = path_elements
1652
 
                        # versioned, present file
1653
 
                        dirblock.append((inv_row[0],
1654
 
                                         inv_row[1], disk_row[2],
1655
 
                                         disk_row[3], inv_row[4],
1656
 
                                         inv_row[5]))
1657
 
                    elif len(path_elements[0]) == 5:
1658
 
                        # unknown disk file
1659
 
                        dirblock.append(
1660
 
                            (path_elements[0][0], path_elements[0][1],
1661
 
                             path_elements[0][2], path_elements[0][3], None,
1662
 
                             None))
1663
 
                    elif len(path_elements[0]) == 6:
1664
 
                        # versioned, absent file.
1665
 
                        dirblock.append(
1666
 
                            (path_elements[0][0], path_elements[0][1],
1667
 
                             'unknown', None, path_elements[0][4],
1668
 
                             path_elements[0][5]))
1669
 
                    else:
1670
 
                        raise NotImplementedError('unreachable code')
1671
 
                yield current_inv[0], dirblock
1672
 
                try:
1673
 
                    current_inv = next(inventory_iterator)
1674
 
                except StopIteration:
1675
 
                    inv_finished = True
1676
 
                try:
1677
 
                    current_disk = next(disk_iterator)
1678
 
                except StopIteration:
1679
 
                    disk_finished = True
1680
 
 
1681
 
    def _walkdirs(self, prefix=""):
1682
 
        """Walk the directories of this tree.
1683
 
 
1684
 
        :param prefix: is used as the directrory to start with.
1685
 
        :returns: a generator which yields items in the form::
1686
 
 
1687
 
            ((curren_directory_path, fileid),
1688
 
             [(file1_path, file1_name, file1_kind, None, file1_id,
1689
 
               file1_kind), ... ])
1690
 
        """
1691
 
        _directory = 'directory'
1692
 
        # get the root in the inventory
1693
 
        inv, top_id = self._path2inv_file_id(prefix)
1694
 
        if top_id is None:
1695
 
            pending = []
1696
 
        else:
1697
 
            pending = [(prefix, '', _directory, None, top_id, None)]
1698
 
        while pending:
1699
 
            dirblock = []
1700
 
            currentdir = pending.pop()
1701
 
            # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-id, 5-kind
1702
 
            top_id = currentdir[4]
1703
 
            if currentdir[0]:
1704
 
                relroot = currentdir[0] + '/'
1705
 
            else:
1706
 
                relroot = ""
1707
 
            # FIXME: stash the node in pending
1708
 
            entry = inv.get_entry(top_id)
1709
 
            if entry.kind == 'directory':
1710
 
                for name, child in entry.sorted_children():
1711
 
                    dirblock.append((relroot + name, name, child.kind, None,
1712
 
                                     child.file_id, child.kind
1713
 
                                     ))
1714
 
            yield (currentdir[0], entry.file_id), dirblock
1715
 
            # push the user specified dirs from dirblock
1716
 
            for dir in reversed(dirblock):
1717
 
                if dir[2] == _directory:
1718
 
                    pending.append(dir)
1719
 
 
1720
 
    def update_feature_flags(self, updated_flags):
1721
 
        """Update the feature flags for this branch.
1722
 
 
1723
 
        :param updated_flags: Dictionary mapping feature names to necessities
1724
 
            A necessity can be None to indicate the feature should be removed
1725
 
        """
1726
 
        with self.lock_write():
1727
 
            self._format._update_feature_flags(updated_flags)
1728
 
            self.control_transport.put_bytes(
1729
 
                'format', self._format.as_string())
1730
 
 
1731
 
    def _check_for_tree_references(self, iterator, recurse_nested, specific_files=None):
1732
 
        """See if directories have become tree-references."""
1733
 
        blocked_parent_ids = set()
1734
 
        for path, ie in iterator:
1735
 
            if ie.parent_id in blocked_parent_ids:
1736
 
                # This entry was pruned because one of its parents became a
1737
 
                # TreeReference. If this is a directory, mark it as blocked.
1738
 
                if ie.kind == 'directory':
1739
 
                    blocked_parent_ids.add(ie.file_id)
1740
 
                continue
1741
 
            if (ie.kind == 'directory' and
1742
 
                    self._directory_is_tree_reference(path)):
1743
 
 
1744
 
                # This InventoryDirectory needs to be a TreeReference
1745
 
                ie = inventory.TreeReference(ie.file_id, ie.name, ie.parent_id)
1746
 
                blocked_parent_ids.add(ie.file_id)
1747
 
 
1748
 
            if ie.kind == 'tree-reference' and recurse_nested:
1749
 
                subtree = self.get_nested_tree(path)
1750
 
                for subpath, ie in subtree.iter_entries_by_dir(
1751
 
                        recurse_nested=recurse_nested,
1752
 
                        specific_files=specific_files):
1753
 
                    if subpath:
1754
 
                        full_subpath = osutils.pathjoin(path, subpath)
1755
 
                    else:
1756
 
                        full_subpath = path
1757
 
                    yield full_subpath, ie
1758
 
            else:
1759
 
                yield path, ie
1760
 
 
1761
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
1762
 
        """See Tree.iter_entries_by_dir()"""
1763
 
        # The only trick here is that if we supports_tree_reference then we
1764
 
        # need to detect if a directory becomes a tree-reference.
1765
 
        iterator = super(WorkingTree, self).iter_entries_by_dir(
1766
 
            specific_files=specific_files)
1767
 
        if not self.supports_tree_reference():
1768
 
            return iterator
1769
 
        else:
1770
 
            return self._check_for_tree_references(
1771
 
                iterator, recurse_nested=recurse_nested,
1772
 
                specific_files=specific_files)
1773
 
 
1774
 
    def get_canonical_paths(self, paths):
1775
 
        """Look up canonical paths for multiple items.
1776
 
 
1777
 
        :param paths: A sequence of paths relative to the root of the tree.
1778
 
        :return: A iterator over paths, with each item the corresponding input
1779
 
            path adjusted to account for existing elements that match case
1780
 
            insensitively.
1781
 
        """
1782
 
        with self.lock_read():
1783
 
            if not self.case_sensitive:
1784
 
                def normalize(x):
1785
 
                    return x.lower()
1786
 
            elif sys.platform == 'darwin':
1787
 
                import unicodedata
1788
 
 
1789
 
                def normalize(x):
1790
 
                    return unicodedata.normalize('NFC', x)
1791
 
            else:
1792
 
                normalize = None
1793
 
            for path in paths:
1794
 
                if normalize is None or self.is_versioned(path):
1795
 
                    yield path
1796
 
                else:
1797
 
                    yield get_canonical_path(self, path, normalize)
1798
 
 
1799
 
    def get_reference_info(self, path, branch=None):
1800
 
        file_id = self.path2id(path)
1801
 
        if file_id is None:
1802
 
            return None
1803
 
        return self.branch.get_reference_info(file_id)[0]
1804
 
 
1805
 
    def set_reference_info(self, tree_path, branch_location):
1806
 
        file_id = self.path2id(tree_path)
1807
 
        if file_id is None:
1808
 
            raise errors.NoSuchFile(tree_path)
1809
 
        self.branch.set_reference_info(file_id, branch_location, tree_path)
1810
 
 
1811
 
    def reference_parent(self, path, branch=None, possible_transports=None):
1812
 
        return self.branch.reference_parent(
1813
 
            self.path2id(path),
1814
 
            path, possible_transports=possible_transports)
1815
 
 
1816
 
 
1817
 
class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
1818
 
    """Base class for working trees that live in bzr meta directories."""
1819
 
 
1820
 
    ignore_filename = '.bzrignore'
1821
 
 
1822
 
    def __init__(self):
1823
 
        WorkingTreeFormat.__init__(self)
1824
 
        bzrdir.BzrFormat.__init__(self)
1825
 
 
1826
 
    @classmethod
1827
 
    def find_format_string(klass, controldir):
1828
 
        """Return format name for the working tree object in controldir."""
1829
 
        try:
1830
 
            transport = controldir.get_workingtree_transport(None)
1831
 
            return transport.get_bytes("format")
1832
 
        except errors.NoSuchFile:
1833
 
            raise errors.NoWorkingTree(base=transport.base)
1834
 
 
1835
 
    @classmethod
1836
 
    def find_format(klass, controldir):
1837
 
        """Return the format for the working tree object in controldir."""
1838
 
        format_string = klass.find_format_string(controldir)
1839
 
        return klass._find_format(format_registry, 'working tree',
1840
 
                                  format_string)
1841
 
 
1842
 
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
1843
 
                             basedir=None):
1844
 
        WorkingTreeFormat.check_support_status(
1845
 
            self, allow_unsupported=allow_unsupported,
1846
 
            recommend_upgrade=recommend_upgrade, basedir=basedir)
1847
 
        bzrdir.BzrFormat.check_support_status(
1848
 
            self, allow_unsupported=allow_unsupported,
1849
 
            recommend_upgrade=recommend_upgrade, basedir=basedir)