/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: Jelmer Vernooij
  • Date: 2020-01-24 00:06:05 UTC
  • mto: This revision was merged to the branch mainline in revision 7459.
  • Revision ID: jelmer@jelmer.uk-20200124000605-qw2v9i7pjfrcy12m
Support importing Git submodules as tree references.

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(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
            xml = self.branch.repository._get_inventory_xml(new_revision)
 
552
            firstline = xml.split(b'\n', 1)[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_string(
 
556
                    xml, new_revision)
 
557
                xml = self._create_basis_xml_from_inventory(new_revision, inv)
 
558
            self._write_basis_inventory(xml)
 
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_string(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 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 = self.read_basis_inventory()
 
805
            except errors.NoSuchFile:
 
806
                pass
 
807
            else:
 
808
                try:
 
809
                    inv = xml7.serializer_v7.read_inventory_from_string(xml)
 
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)