/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/workingtree.py

  • Committer: Jelmer Vernooij
  • Date: 2017-08-29 21:07:17 UTC
  • mfrom: (6772 brz)
  • mto: This revision was merged to the branch mainline in revision 6773.
  • Revision ID: jelmer@jelmer.uk-20170829210717-ud3w6rh8x8fv48l9
Merge trunk.

Show diffs side-by-side

added added

removed removed

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