/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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