1# Copyright (C) 2007-2012 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"""WorkingTree4 format and implementation.
18
19WorkingTree4 provides the dirstate based working tree logic.
20
21To get a WorkingTree, call bzrdir.open_workingtree() or
22WorkingTree.open(dir).
23"""
24
25from io import BytesIO
26import os
27
28from ..lazy_import import lazy_import
29lazy_import(globals(), """
30import contextlib
31import errno
32import stat
33
34from breezy import (
35    branch as _mod_branch,
36    cache_utf8,
37    controldir,
38    debug,
39    filters as _mod_filters,
40    osutils,
41    revision as _mod_revision,
42    revisiontree,
43    trace,
44    views,
45    )
46from breezy.bzr import (
47    dirstate,
48    generate_ids,
49    transform as bzr_transform,
50    )
51""")
52
53from .. import (
54    errors,
55    )
56from .inventory import Inventory, ROOT_ID, entry_factory
57from ..lock import LogicalLockResult
58from ..lockable_files import LockableFiles
59from ..lockdir import LockDir
60from .inventorytree import (
61    InventoryTree,
62    InterInventoryTree,
63    InventoryRevisionTree,
64    )
65from ..mutabletree import (
66    BadReferenceTarget,
67    MutableTree,
68    )
69from ..osutils import (
70    file_kind,
71    isdir,
72    pathjoin,
73    realpath,
74    safe_unicode,
75    )
76from ..transport import get_transport_from_path
77from ..transport.local import LocalTransport
78from ..tree import (
79    FileTimestampUnavailable,
80    InterTree,
81    MissingNestedTree,
82    )
83from ..workingtree import (
84    WorkingTree,
85    )
86from .workingtree import (
87    InventoryWorkingTree,
88    WorkingTreeFormatMetaDir,
89    )
90
91
92class DirStateWorkingTree(InventoryWorkingTree):
93
94    def __init__(self, basedir,
95                 branch,
96                 _control_files=None,
97                 _format=None,
98                 _controldir=None):
99        """Construct a WorkingTree for basedir.
100
101        If the branch is not supplied, it is opened automatically.
102        If the branch is supplied, it must be the branch for this basedir.
103        (branch.base is not cross checked, because for remote branches that
104        would be meaningless).
105        """
106        self._format = _format
107        self.controldir = _controldir
108        basedir = safe_unicode(basedir)
109        trace.mutter("opening working tree %r", basedir)
110        self._branch = branch
111        self.basedir = realpath(basedir)
112        # if branch is at our basedir and is a format 6 or less
113        # assume all other formats have their own control files.
114        self._control_files = _control_files
115        self._transport = self._control_files._transport
116        self._dirty = None
117        # -------------
118        # during a read or write lock these objects are set, and are
119        # None the rest of the time.
120        self._dirstate = None
121        self._inventory = None
122        # -------------
123        self._setup_directory_is_tree_reference()
124        self._detect_case_handling()
125        self._rules_searcher = None
126        self.views = self._make_views()
127        # --- allow tests to select the dirstate iter_changes implementation
128        self._iter_changes = dirstate._process_entry
129        self._repo_supports_tree_reference = getattr(
130            self._branch.repository._format, "supports_tree_reference",
131            False)
132
133    def _add(self, files, ids, kinds):
134        """See MutableTree._add."""
135        with self.lock_tree_write():
136            state = self.current_dirstate()
137            for f, file_id, kind in zip(files, ids, kinds):
138                f = f.strip(u'/')
139                if self.path2id(f):
140                    # special case tree root handling.
141                    if f == b'' and self.path2id(f) == ROOT_ID:
142                        state.set_path_id(b'', generate_ids.gen_file_id(f))
143                    continue
144                if file_id is None:
145                    file_id = generate_ids.gen_file_id(f)
146                # deliberately add the file with no cached stat or sha1
147                # - on the first access it will be gathered, and we can
148                # always change this once tests are all passing.
149                state.add(f, file_id, kind, None, b'')
150            self._make_dirty(reset_inventory=True)
151
152    def _get_check_refs(self):
153        """Return the references needed to perform a check of this tree."""
154        return [('trees', self.last_revision())]
155
156    def _make_dirty(self, reset_inventory):
157        """Make the tree state dirty.
158
159        :param reset_inventory: True if the cached inventory should be removed
160            (presuming there is one).
161        """
162        self._dirty = True
163        if reset_inventory and self._inventory is not None:
164            self._inventory = None
165
166    def add_reference(self, sub_tree):
167        # use standard implementation, which calls back to self._add
168        #
169        # So we don't store the reference_revision in the working dirstate,
170        # it's just recorded at the moment of commit.
171        with self.lock_tree_write():
172            try:
173                sub_tree_path = self.relpath(sub_tree.basedir)
174            except errors.PathNotChild:
175                raise BadReferenceTarget(self, sub_tree,
176                                         'Target not inside tree.')
177            sub_tree_id = sub_tree.path2id('')
178            if sub_tree_id == self.path2id(''):
179                raise BadReferenceTarget(self, sub_tree,
180                                         'Trees have the same root id.')
181            try:
182                self.id2path(sub_tree_id)
183            except errors.NoSuchId:
184                pass
185            else:
186                raise BadReferenceTarget(
187                    self, sub_tree, 'Root id already present in tree')
188            self._add([sub_tree_path], [sub_tree_id], ['tree-reference'])
189
190    def break_lock(self):
191        """Break a lock if one is present from another instance.
192
193        Uses the ui factory to ask for confirmation if the lock may be from
194        an active process.
195
196        This will probe the repository for its lock as well.
197        """
198        # if the dirstate is locked by an active process, reject the break lock
199        # call.
200        try:
201            if self._dirstate is None:
202                clear = True
203            else:
204                clear = False
205            state = self._current_dirstate()
206            if state._lock_token is not None:
207                # we already have it locked. sheese, cant break our own lock.
208                raise errors.LockActive(self.basedir)
209            else:
210                try:
211                    # try for a write lock - need permission to get one anyhow
212                    # to break locks.
213                    state.lock_write()
214                except errors.LockContention:
215                    # oslocks fail when a process is still live: fail.
216                    # TODO: get the locked lockdir info and give to the user to
217                    # assist in debugging.
218                    raise errors.LockActive(self.basedir)
219                else:
220                    state.unlock()
221        finally:
222            if clear:
223                self._dirstate = None
224        self._control_files.break_lock()
225        self.branch.break_lock()
226
227    def _comparison_data(self, entry, path):
228        kind, executable, stat_value = \
229            WorkingTree._comparison_data(self, entry, path)
230        # it looks like a plain directory, but it's really a reference -- see
231        # also kind()
232        if (self._repo_supports_tree_reference and kind == 'directory'
233                and entry is not None and entry.kind == 'tree-reference'):
234            kind = 'tree-reference'
235        return kind, executable, stat_value
236
237    def commit(self, message=None, revprops=None, *args, **kwargs):
238        with self.lock_write():
239            # mark the tree as dirty post commit - commit
240            # can change the current versioned list by doing deletes.
241            result = WorkingTree.commit(self, message, revprops, *args,
242                                        **kwargs)
243            self._make_dirty(reset_inventory=True)
244            return result
245
246    def current_dirstate(self):
247        """Return the current dirstate object.
248
249        This is not part of the tree interface and only exposed for ease of
250        testing.
251
252        :raises errors.NotWriteLocked: when not in a lock.
253        """
254        self._must_be_locked()
255        return self._current_dirstate()
256
257    def _current_dirstate(self):
258        """Internal function that does not check lock status.
259
260        This is needed for break_lock which also needs the dirstate.
261        """
262        if self._dirstate is not None:
263            return self._dirstate
264        local_path = self.controldir.get_workingtree_transport(
265            None).local_abspath('dirstate')
266        self._dirstate = dirstate.DirState.on_file(
267            local_path, self._sha1_provider(), self._worth_saving_limit(),
268            self._supports_executable())
269        return self._dirstate
270
271    def _sha1_provider(self):
272        """A function that returns a SHA1Provider suitable for this tree.
273
274        :return: None if content filtering is not supported by this tree.
275          Otherwise, a SHA1Provider is returned that sha's the canonical
276          form of files, i.e. after read filters are applied.
277        """
278        if self.supports_content_filtering():
279            return ContentFilterAwareSHA1Provider(self)
280        else:
281            return None
282
283    def _worth_saving_limit(self):
284        """How many hash changes are ok before we must save the dirstate.
285
286        :return: an integer. -1 means never save.
287        """
288        conf = self.get_config_stack()
289        return conf.get('bzr.workingtree.worth_saving_limit')
290
291    def filter_unversioned_files(self, paths):
292        """Filter out paths that are versioned.
293
294        :return: set of paths.
295        """
296        # TODO: make a generic multi-bisect routine roughly that should list
297        # the paths, then process one half at a time recursively, and feed the
298        # results of each bisect in further still
299        paths = sorted(paths)
300        result = set()
301        state = self.current_dirstate()
302        # TODO we want a paths_to_dirblocks helper I think
303        for path in paths:
304            dirname, basename = os.path.split(path.encode('utf8'))
305            _, _, _, path_is_versioned = state._get_block_entry_index(
306                dirname, basename, 0)
307            if not path_is_versioned:
308                result.add(path)
309        return result
310
311    def flush(self):
312        """Write all cached data to disk."""
313        if self._control_files._lock_mode != 'w':
314            raise errors.NotWriteLocked(self)
315        self.current_dirstate().save()
316        self._inventory = None
317        self._dirty = False
318
319    def _gather_kinds(self, files, kinds):
320        """See MutableTree._gather_kinds."""
321        with self.lock_tree_write():
322            for pos, f in enumerate(files):
323                if kinds[pos] is None:
324                    kinds[pos] = self.kind(f)
325
326    def _generate_inventory(self):
327        """Create and set self.inventory from the dirstate object.
328
329        This is relatively expensive: we have to walk the entire dirstate.
330        Ideally we would not, and can deprecate this function.
331        """
332        #: uncomment to trap on inventory requests.
333        # import pdb;pdb.set_trace()
334        state = self.current_dirstate()
335        state._read_dirblocks_if_needed()
336        root_key, current_entry = self._get_entry(path='')
337        current_id = root_key[2]
338        if not (current_entry[0][0] == b'd'):  # directory
339            raise AssertionError(current_entry)
340        inv = Inventory(root_id=current_id)
341        # Turn some things into local variables
342        minikind_to_kind = dirstate.DirState._minikind_to_kind
343        factory = entry_factory
344        utf8_decode = cache_utf8._utf8_decode
345        inv_byid = inv._byid
346        # we could do this straight out of the dirstate; it might be fast
347        # and should be profiled - RBC 20070216
348        parent_ies = {b'': inv.root}
349        for block in state._dirblocks[1:]:  # skip the root
350            dirname = block[0]
351            try:
352                parent_ie = parent_ies[dirname]
353            except KeyError:
354                # all the paths in this block are not versioned in this tree
355                continue
356            for key, entry in block[1]:
357                minikind, link_or_sha1, size, executable, stat = entry[0]
358                if minikind in (b'a', b'r'):  # absent, relocated
359                    # a parent tree only entry
360                    continue
361                name = key[1]
362                name_unicode = utf8_decode(name)[0]
363                file_id = key[2]
364                kind = minikind_to_kind[minikind]
365                inv_entry = factory[kind](file_id, name_unicode,
366                                          parent_ie.file_id)
367                if kind == 'file':
368                    # This is only needed on win32, where this is the only way
369                    # we know the executable bit.
370                    inv_entry.executable = executable
371                    # not strictly needed: working tree
372                    # inv_entry.text_size = size
373                    # inv_entry.text_sha1 = sha1
374                elif kind == 'directory':
375                    # add this entry to the parent map.
376                    parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
377                elif kind == 'tree-reference':
378                    inv_entry.reference_revision = link_or_sha1 or None
379                elif kind != 'symlink':
380                    raise AssertionError("unknown kind %r" % kind)
381                # These checks cost us around 40ms on a 55k entry tree
382                if file_id in inv_byid:
383                    raise AssertionError(
384                        'file_id %s already in'
385                        ' inventory as %s' % (file_id, inv_byid[file_id]))
386                if name_unicode in parent_ie.children:
387                    raise AssertionError('name %r already in parent'
388                                         % (name_unicode,))
389                inv_byid[file_id] = inv_entry
390                parent_ie.children[name_unicode] = inv_entry
391        self._inventory = inv
392
393    def _get_entry(self, file_id=None, path=None):
394        """Get the dirstate row for file_id or path.
395
396        If either file_id or path is supplied, it is used as the key to lookup.
397        If both are supplied, the fastest lookup is used, and an error is
398        raised if they do not both point at the same row.
399
400        :param file_id: An optional unicode file_id to be looked up.
401        :param path: An optional unicode path to be looked up.
402        :return: The dirstate row tuple for path/file_id, or (None, None)
403        """
404        if file_id is None and path is None:
405            raise errors.BzrError('must supply file_id or path')
406        state = self.current_dirstate()
407        if path is not None:
408            path = path.encode('utf8')
409        return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
410
411    def get_file_sha1(self, path, stat_value=None):
412        # check file id is valid unconditionally.
413        entry = self._get_entry(path=path)
414        if entry[0] is None:
415            raise errors.NoSuchFile(self, path)
416        if path is None:
417            path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
418
419        file_abspath = self.abspath(path)
420        state = self.current_dirstate()
421        if stat_value is None:
422            try:
423                stat_value = osutils.lstat(file_abspath)
424            except OSError as e:
425                if e.errno == errno.ENOENT:
426                    return None
427                else:
428                    raise
429        link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
430                                             stat_value=stat_value)
431        if entry[1][0][0] == b'f':
432            if link_or_sha1 is None:
433                file_obj, statvalue = self.get_file_with_stat(path)
434                try:
435                    sha1 = osutils.sha_file(file_obj)
436                finally:
437                    file_obj.close()
438                self._observed_sha1(path, (sha1, statvalue))
439                return sha1
440            else:
441                return link_or_sha1
442        return None
443
444    def _get_root_inventory(self):
445        """Get the inventory for the tree. This is only valid within a lock."""
446        if 'evil' in debug.debug_flags:
447            trace.mutter_callsite(
448                2, "accessing .inventory forces a size of tree translation.")
449        if self._inventory is not None:
450            return self._inventory
451        self._must_be_locked()
452        self._generate_inventory()
453        return self._inventory
454
455    root_inventory = property(_get_root_inventory,
456                              "Root inventory of this tree")
457
458    def get_parent_ids(self):
459        """See Tree.get_parent_ids.
460
461        This implementation requests the ids list from the dirstate file.
462        """
463        with self.lock_read():
464            return self.current_dirstate().get_parent_ids()
465
466    def get_reference_revision(self, path):
467        # referenced tree's revision is whatever's currently there
468        try:
469            return self.get_nested_tree(path).last_revision()
470        except errors.NotBranchError:
471            entry = self._get_entry(path=path)
472            if entry == (None, None):
473                return False
474            return entry[1][0][1]
475
476    def get_nested_tree(self, path):
477        return WorkingTree.open(self.abspath(path))
478
479    def id2path(self, file_id, recurse='down'):
480        "Convert a file-id to a path."
481        with self.lock_read():
482            state = self.current_dirstate()
483            entry = self._get_entry(file_id=file_id)
484            if entry == (None, None):
485                if recurse == 'down':
486                    if 'evil' in debug.debug_flags:
487                        trace.mutter_callsite(
488                            2, "Tree.id2path scans all nested trees.")
489                    for nested_path in self.iter_references():
490                        nested_tree = self.get_nested_tree(nested_path)
491                        try:
492                            return osutils.pathjoin(
493                                nested_path, nested_tree.id2path(file_id))
494                        except errors.NoSuchId:
495                            pass
496                raise errors.NoSuchId(tree=self, file_id=file_id)
497            path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
498            return path_utf8.decode('utf8')
499
500    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
501        entry = self._get_entry(path=path)
502        if entry == (None, None):
503            return False  # Missing entries are not executable
504        return entry[1][0][3]  # Executable?
505
506    def is_executable(self, path):
507        """Test if a file is executable or not.
508
509        Note: The caller is expected to take a read-lock before calling this.
510        """
511        if not self._supports_executable():
512            entry = self._get_entry(path=path)
513            if entry == (None, None):
514                return False
515            return entry[1][0][3]
516        else:
517            self._must_be_locked()
518            mode = osutils.lstat(self.abspath(path)).st_mode
519            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
520
521    def all_file_ids(self):
522        """See Tree.iter_all_file_ids"""
523        self._must_be_locked()
524        result = set()
525        for key, tree_details in self.current_dirstate()._iter_entries():
526            if tree_details[0][0] in (b'a', b'r'):  # relocated
527                continue
528            result.add(key[2])
529        return result
530
531    def all_versioned_paths(self):
532        self._must_be_locked()
533        return {path for path, entry in
534                self.root_inventory.iter_entries(recursive=True)}
535
536    def __iter__(self):
537        """Iterate through file_ids for this tree.
538
539        file_ids are in a WorkingTree if they are in the working inventory
540        and the working file exists.
541        """
542        with self.lock_read():
543            result = []
544            for key, tree_details in self.current_dirstate()._iter_entries():
545                if tree_details[0][0] in (b'a', b'r'):  # absent, relocated
546                    # not relevant to the working tree
547                    continue
548                path = pathjoin(self.basedir, key[0].decode(
549                    'utf8'), key[1].decode('utf8'))
550                if osutils.lexists(path):
551                    result.append(key[2])
552            return iter(result)
553
554    def iter_references(self):
555        if not self._repo_supports_tree_reference:
556            # When the repo doesn't support references, we will have nothing to
557            # return
558            return
559        with self.lock_read():
560            for key, tree_details in self.current_dirstate()._iter_entries():
561                if tree_details[0][0] in (b'a', b'r'):  # absent, relocated
562                    # not relevant to the working tree
563                    continue
564                if not key[1]:
565                    # the root is not a reference.
566                    continue
567                relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
568                try:
569                    if self.kind(relpath) == 'tree-reference':
570                        yield relpath
571                except errors.NoSuchFile:
572                    # path is missing on disk.
573                    continue
574
575    def _observed_sha1(self, path, sha_and_stat):
576        """See MutableTree._observed_sha1."""
577        state = self.current_dirstate()
578        entry = self._get_entry(path=path)
579        state._observed_sha1(entry, *sha_and_stat)
580
581    def kind(self, relpath):
582        abspath = self.abspath(relpath)
583        kind = file_kind(abspath)
584        if (self._repo_supports_tree_reference and kind == 'directory'):
585            with self.lock_read():
586                entry = self._get_entry(path=relpath)
587                if entry[1] is not None:
588                    if entry[1][0][0] == b't':
589                        kind = 'tree-reference'
590        return kind
591
592    def _last_revision(self):
593        """See Mutable.last_revision."""
594        with self.lock_read():
595            parent_ids = self.current_dirstate().get_parent_ids()
596            if parent_ids:
597                return parent_ids[0]
598            else:
599                return _mod_revision.NULL_REVISION
600
601    def lock_read(self):
602        """See Branch.lock_read, and WorkingTree.unlock.
603
604        :return: A breezy.lock.LogicalLockResult.
605        """
606        self.branch.lock_read()
607        try:
608            self._control_files.lock_read()
609            try:
610                state = self.current_dirstate()
611                if not state._lock_token:
612                    state.lock_read()
613                # set our support for tree references from the repository in
614                # use.
615                self._repo_supports_tree_reference = getattr(
616                    self.branch.repository._format, "supports_tree_reference",
617                    False)
618            except BaseException:
619                self._control_files.unlock()
620                raise
621        except BaseException:
622            self.branch.unlock()
623            raise
624        return LogicalLockResult(self.unlock)
625
626    def _lock_self_write(self):
627        """This should be called after the branch is locked."""
628        try:
629            self._control_files.lock_write()
630            try:
631                state = self.current_dirstate()
632                if not state._lock_token:
633                    state.lock_write()
634                # set our support for tree references from the repository in
635                # use.
636                self._repo_supports_tree_reference = getattr(
637                    self.branch.repository._format, "supports_tree_reference",
638                    False)
639            except BaseException:
640                self._control_files.unlock()
641                raise
642        except BaseException:
643            self.branch.unlock()
644            raise
645        return LogicalLockResult(self.unlock)
646
647    def lock_tree_write(self):
648        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
649
650        :return: A breezy.lock.LogicalLockResult.
651        """
652        self.branch.lock_read()
653        return self._lock_self_write()
654
655    def lock_write(self):
656        """See MutableTree.lock_write, and WorkingTree.unlock.
657
658        :return: A breezy.lock.LogicalLockResult.
659        """
660        self.branch.lock_write()
661        return self._lock_self_write()
662
663    def move(self, from_paths, to_dir, after=False):
664        """See WorkingTree.move()."""
665        result = []
666        if not from_paths:
667            return result
668        with self.lock_tree_write():
669            state = self.current_dirstate()
670            if isinstance(from_paths, (str, bytes)):
671                raise ValueError()
672            to_dir_utf8 = to_dir.encode('utf8')
673            to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
674            # check destination directory
675            # get the details for it
676            (to_entry_block_index, to_entry_entry_index, dir_present,
677             entry_present) = state._get_block_entry_index(
678                 to_entry_dirname, to_basename, 0)
679            if not entry_present:
680                raise errors.BzrMoveFailedError(
681                    '', to_dir, errors.NotVersionedError(to_dir))
682            to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
683            # get a handle on the block itself.
684            to_block_index = state._ensure_block(
685                to_entry_block_index, to_entry_entry_index, to_dir_utf8)
686            to_block = state._dirblocks[to_block_index]
687            to_abs = self.abspath(to_dir)
688            if not isdir(to_abs):
689                raise errors.BzrMoveFailedError('', to_dir,
690                                                errors.NotADirectory(to_abs))
691
692            if to_entry[1][0][0] != b'd':
693                raise errors.BzrMoveFailedError('', to_dir,
694                                                errors.NotADirectory(to_abs))
695
696            if self._inventory is not None:
697                update_inventory = True
698                inv = self.root_inventory
699                to_dir_id = to_entry[0][2]
700            else:
701                update_inventory = False
702
703            # GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
704            # missing those added here, but there's also no test coverage for this.
705            rollbacks = contextlib.ExitStack()
706
707            def move_one(old_entry, from_path_utf8, minikind, executable,
708                         fingerprint, packed_stat, size,
709                         to_block, to_key, to_path_utf8):
710                state._make_absent(old_entry)
711                from_key = old_entry[0]
712                rollbacks.callback(
713                    state.update_minimal,
714                    from_key,
715                    minikind,
716                    executable=executable,
717                    fingerprint=fingerprint,
718                    packed_stat=packed_stat,
719                    size=size,
720                    path_utf8=from_path_utf8)
721                state.update_minimal(to_key,
722                                     minikind,
723                                     executable=executable,
724                                     fingerprint=fingerprint,
725                                     packed_stat=packed_stat,
726                                     size=size,
727                                     path_utf8=to_path_utf8)
728                added_entry_index, _ = state._find_entry_index(
729                    to_key, to_block[1])
730                new_entry = to_block[1][added_entry_index]
731                rollbacks.callback(state._make_absent, new_entry)
732
733            for from_rel in from_paths:
734                # from_rel is 'pathinroot/foo/bar'
735                from_rel_utf8 = from_rel.encode('utf8')
736                from_dirname, from_tail = osutils.split(from_rel)
737                from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
738                from_entry = self._get_entry(path=from_rel)
739                if from_entry == (None, None):
740                    raise errors.BzrMoveFailedError(
741                        from_rel, to_dir,
742                        errors.NotVersionedError(path=from_rel))
743
744                from_id = from_entry[0][2]
745                to_rel = pathjoin(to_dir, from_tail)
746                to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
747                item_to_entry = self._get_entry(path=to_rel)
748                if item_to_entry != (None, None):
749                    raise errors.BzrMoveFailedError(
750                        from_rel, to_rel, "Target is already versioned.")
751
752                if from_rel == to_rel:
753                    raise errors.BzrMoveFailedError(
754                        from_rel, to_rel, "Source and target are identical.")
755
756                from_missing = not self.has_filename(from_rel)
757                to_missing = not self.has_filename(to_rel)
758                if after:
759                    move_file = False
760                else:
761                    move_file = True
762                if to_missing:
763                    if not move_file:
764                        raise errors.BzrMoveFailedError(
765                            from_rel, to_rel,
766                            errors.NoSuchFile(
767                                path=to_rel,
768                                extra="New file has not been created yet"))
769                    elif from_missing:
770                        # neither path exists
771                        raise errors.BzrRenameFailedError(
772                            from_rel, to_rel,
773                            errors.PathsDoNotExist(paths=(from_rel, to_rel)))
774                else:
775                    if from_missing:  # implicitly just update our path mapping
776                        move_file = False
777                    elif not after:
778                        raise errors.RenameFailedFilesExist(from_rel, to_rel)
779
780                # perform the disk move first - its the most likely failure point.
781                if move_file:
782                    from_rel_abs = self.abspath(from_rel)
783                    to_rel_abs = self.abspath(to_rel)
784                    try:
785                        osutils.rename(from_rel_abs, to_rel_abs)
786                    except OSError as e:
787                        raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
788                    rollbacks.callback(
789                        osutils.rename, to_rel_abs, from_rel_abs)
790                try:
791                    # perform the rename in the inventory next if needed: its easy
792                    # to rollback
793                    if update_inventory:
794                        # rename the entry
795                        from_entry = inv.get_entry(from_id)
796                        current_parent = from_entry.parent_id
797                        inv.rename(from_id, to_dir_id, from_tail)
798                        rollbacks.callback(
799                            inv.rename, from_id, current_parent, from_tail)
800                    # finally do the rename in the dirstate, which is a little
801                    # tricky to rollback, but least likely to need it.
802                    old_block_index, old_entry_index, dir_present, file_present = \
803                        state._get_block_entry_index(
804                            from_dirname, from_tail_utf8, 0)
805                    old_block = state._dirblocks[old_block_index][1]
806                    old_entry = old_block[old_entry_index]
807                    from_key, old_entry_details = old_entry
808                    cur_details = old_entry_details[0]
809                    # remove the old row
810                    to_key = ((to_block[0],) + from_key[1:3])
811                    minikind = cur_details[0]
812                    move_one(old_entry, from_path_utf8=from_rel_utf8,
813                             minikind=minikind,
814                             executable=cur_details[3],
815                             fingerprint=cur_details[1],
816                             packed_stat=cur_details[4],
817                             size=cur_details[2],
818                             to_block=to_block,
819                             to_key=to_key,
820                             to_path_utf8=to_rel_utf8)
821
822                    if minikind == b'd':
823                        def update_dirblock(from_dir, to_key, to_dir_utf8):
824                            """Recursively update all entries in this dirblock."""
825                            if from_dir == b'':
826                                raise AssertionError(
827                                    "renaming root not supported")
828                            from_key = (from_dir, '')
829                            from_block_idx, present = \
830                                state._find_block_index_from_key(from_key)
831                            if not present:
832                                # This is the old record, if it isn't present,
833                                # then there is theoretically nothing to
834                                # update.  (Unless it isn't present because of
835                                # lazy loading, but we don't do that yet)
836                                return
837                            from_block = state._dirblocks[from_block_idx]
838                            to_block_index, to_entry_index, _, _ = \
839                                state._get_block_entry_index(
840                                    to_key[0], to_key[1], 0)
841                            to_block_index = state._ensure_block(
842                                to_block_index, to_entry_index, to_dir_utf8)
843                            to_block = state._dirblocks[to_block_index]
844
845                            # Grab a copy since move_one may update the list.
846                            for entry in from_block[1][:]:
847                                if not (entry[0][0] == from_dir):
848                                    raise AssertionError()
849                                cur_details = entry[1][0]
850                                to_key = (
851                                    to_dir_utf8, entry[0][1], entry[0][2])
852                                from_path_utf8 = osutils.pathjoin(
853                                    entry[0][0], entry[0][1])
854                                to_path_utf8 = osutils.pathjoin(
855                                    to_dir_utf8, entry[0][1])
856                                minikind = cur_details[0]
857                                if minikind in (b'a', b'r'):
858                                    # Deleted children of a renamed directory
859                                    # Do not need to be updated.  Children that
860                                    # have been renamed out of this directory
861                                    # should also not be updated
862                                    continue
863                                move_one(entry, from_path_utf8=from_path_utf8,
864                                         minikind=minikind,
865                                         executable=cur_details[3],
866                                         fingerprint=cur_details[1],
867                                         packed_stat=cur_details[4],
868                                         size=cur_details[2],
869                                         to_block=to_block,
870                                         to_key=to_key,
871                                         to_path_utf8=to_path_utf8)
872                                if minikind == b'd':
873                                    # We need to move all the children of this
874                                    # entry
875                                    update_dirblock(from_path_utf8, to_key,
876                                                    to_path_utf8)
877                        update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
878                except BaseException:
879                    rollbacks.close()
880                    raise
881                result.append((from_rel, to_rel))
882                state._mark_modified()
883                self._make_dirty(reset_inventory=False)
884
885            return result
886
887    def _must_be_locked(self):
888        if not self._control_files._lock_count:
889            raise errors.ObjectNotLocked(self)
890
891    def _new_tree(self):
892        """Initialize the state in this tree to be a new tree."""
893        self._dirty = True
894
895    def path2id(self, path):
896        """Return the id for path in this tree."""
897        with self.lock_read():
898            if isinstance(path, list):
899                if path == []:
900                    path = [""]
901                path = osutils.pathjoin(*path)
902            path = path.strip('/')
903            entry = self._get_entry(path=path)
904            if entry == (None, None):
905                nested_tree, subpath = self.get_containing_nested_tree(path)
906                if nested_tree is not None:
907                    return nested_tree.path2id(subpath)
908                return None
909            return entry[0][2]
910
911    def paths2ids(self, paths, trees=[], require_versioned=True):
912        """See Tree.paths2ids().
913
914        This specialisation fast-paths the case where all the trees are in the
915        dirstate.
916        """
917        if paths is None:
918            return None
919        parents = self.get_parent_ids()
920        for tree in trees:
921            if not (isinstance(tree, DirStateRevisionTree) and
922                    tree._revision_id in parents):
923                return super(DirStateWorkingTree, self).paths2ids(
924                    paths, trees, require_versioned)
925        search_indexes = [
926            0] + [1 + parents.index(tree._revision_id) for tree in trees]
927        paths_utf8 = set()
928        for path in paths:
929            paths_utf8.add(path.encode('utf8'))
930        # -- get the state object and prepare it.
931        state = self.current_dirstate()
932        if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
933                      and b'' not in paths):
934            paths2ids = self._paths2ids_using_bisect
935        else:
936            paths2ids = self._paths2ids_in_memory
937        return paths2ids(paths_utf8, search_indexes,
938                         require_versioned=require_versioned)
939
940    def _paths2ids_in_memory(self, paths, search_indexes,
941                             require_versioned=True):
942        state = self.current_dirstate()
943        state._read_dirblocks_if_needed()
944
945        def _entries_for_path(path):
946            """Return a list with all the entries that match path for all ids.
947            """
948            dirname, basename = os.path.split(path)
949            key = (dirname, basename, b'')
950            block_index, present = state._find_block_index_from_key(key)
951            if not present:
952                # the block which should contain path is absent.
953                return []
954            result = []
955            block = state._dirblocks[block_index][1]
956            entry_index, _ = state._find_entry_index(key, block)
957            # we may need to look at multiple entries at this path: walk while
958            # the paths match.
959            while (entry_index < len(block) and
960                   block[entry_index][0][0:2] == key[0:2]):
961                result.append(block[entry_index])
962                entry_index += 1
963            return result
964        if require_versioned:
965            # -- check all supplied paths are versioned in a search tree. --
966            all_versioned = True
967            for path in paths:
968                path_entries = _entries_for_path(path)
969                if not path_entries:
970                    # this specified path is not present at all: error
971                    all_versioned = False
972                    break
973                found_versioned = False
974                # for each id at this path
975                for entry in path_entries:
976                    # for each tree.
977                    for index in search_indexes:
978                        if entry[1][index][0] != b'a':  # absent
979                            found_versioned = True
980                            # all good: found a versioned cell
981                            break
982                if not found_versioned:
983                    # none of the indexes was not 'absent' at all ids for this
984                    # path.
985                    all_versioned = False
986                    break
987            if not all_versioned:
988                raise errors.PathsNotVersionedError(
989                    [p.decode('utf-8') for p in paths])
990        # -- remove redundancy in supplied paths to prevent over-scanning --
991        search_paths = osutils.minimum_path_selection(paths)
992        # sketch:
993        # for all search_indexs in each path at or under each element of
994        # search_paths, if the detail is relocated: add the id, and add the
995        # relocated path as one to search if its not searched already. If the
996        # detail is not relocated, add the id.
997        searched_paths = set()
998        found_ids = set()
999
1000        def _process_entry(entry):
1001            """Look at search_indexes within entry.
1002
1003            If a specific tree's details are relocated, add the relocation
1004            target to search_paths if not searched already. If it is absent, do
1005            nothing. Otherwise add the id to found_ids.
1006            """
1007            for index in search_indexes:
1008                if entry[1][index][0] == b'r':  # relocated
1009                    if not osutils.is_inside_any(searched_paths,
1010                                                 entry[1][index][1]):
1011                        search_paths.add(entry[1][index][1])
1012                elif entry[1][index][0] != b'a':  # absent
1013                    found_ids.add(entry[0][2])
1014        while search_paths:
1015            current_root = search_paths.pop()
1016            searched_paths.add(current_root)
1017            # process the entries for this containing directory: the rest will
1018            # be found by their parents recursively.
1019            root_entries = _entries_for_path(current_root)
1020            if not root_entries:
1021                # this specified path is not present at all, skip it.
1022                continue
1023            for entry in root_entries:
1024                _process_entry(entry)
1025            initial_key = (current_root, b'', b'')
1026            block_index, _ = state._find_block_index_from_key(initial_key)
1027            while (block_index < len(state._dirblocks) and
1028                   osutils.is_inside(current_root, state._dirblocks[block_index][0])):
1029                for entry in state._dirblocks[block_index][1]:
1030                    _process_entry(entry)
1031                block_index += 1
1032        return found_ids
1033
1034    def _paths2ids_using_bisect(self, paths, search_indexes,
1035                                require_versioned=True):
1036        state = self.current_dirstate()
1037        found_ids = set()
1038
1039        split_paths = sorted(osutils.split(p) for p in paths)
1040        found = state._bisect_recursive(split_paths)
1041
1042        if require_versioned:
1043            found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
1044            for dir_name in split_paths:
1045                if dir_name not in found_dir_names:
1046                    raise errors.PathsNotVersionedError(
1047                        [p.decode('utf-8') for p in paths])
1048
1049        for dir_name_id, trees_info in found.items():
1050            for index in search_indexes:
1051                if trees_info[index][0] not in (b'r', b'a'):
1052                    found_ids.add(dir_name_id[2])
1053        return found_ids
1054
1055    def read_working_inventory(self):
1056        """Read the working inventory.
1057
1058        This is a meaningless operation for dirstate, but we obey it anyhow.
1059        """
1060        return self.root_inventory
1061
1062    def revision_tree(self, revision_id):
1063        """See Tree.revision_tree.
1064
1065        WorkingTree4 supplies revision_trees for any basis tree.
1066        """
1067        with self.lock_read():
1068            dirstate = self.current_dirstate()
1069            parent_ids = dirstate.get_parent_ids()
1070            if revision_id not in parent_ids:
1071                raise errors.NoSuchRevisionInTree(self, revision_id)
1072            if revision_id in dirstate.get_ghosts():
1073                raise errors.NoSuchRevisionInTree(self, revision_id)
1074            return DirStateRevisionTree(
1075                dirstate, revision_id, self.branch.repository,
1076                get_transport_from_path(self.basedir))
1077
1078    def set_last_revision(self, new_revision):
1079        """Change the last revision in the working tree."""
1080        with self.lock_tree_write():
1081            parents = self.get_parent_ids()
1082            if new_revision in (_mod_revision.NULL_REVISION, None):
1083                if len(parents) >= 2:
1084                    raise AssertionError(
1085                        "setting the last parent to none with a pending merge "
1086                        "is unsupported.")
1087                self.set_parent_ids([])
1088            else:
1089                self.set_parent_ids(
1090                    [new_revision] + parents[1:],
1091                    allow_leftmost_as_ghost=True)
1092
1093    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
1094        """Set the parent ids to revision_ids.
1095
1096        See also set_parent_trees. This api will try to retrieve the tree data
1097        for each element of revision_ids from the trees repository. If you have
1098        tree data already available, it is more efficient to use
1099        set_parent_trees rather than set_parent_ids. set_parent_ids is however
1100        an easier API to use.
1101
1102        :param revision_ids: The revision_ids to set as the parent ids of this
1103            working tree. Any of these may be ghosts.
1104        """
1105        with self.lock_tree_write():
1106            trees = []
1107            for revision_id in revision_ids:
1108                try:
1109                    revtree = self.branch.repository.revision_tree(revision_id)
1110                    # TODO: jam 20070213 KnitVersionedFile raises
1111                    # RevisionNotPresent rather than NoSuchRevision if a given
1112                    # revision_id is not present. Should Repository be catching
1113                    # it and re-raising NoSuchRevision?
1114                except (errors.NoSuchRevision, errors.RevisionNotPresent):
1115                    revtree = None
1116                trees.append((revision_id, revtree))
1117            self.set_parent_trees(
1118                trees, allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1119
1120    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1121        """Set the parents of the working tree.
1122
1123        :param parents_list: A list of (revision_id, tree) tuples.
1124            If tree is None, then that element is treated as an unreachable
1125            parent tree - i.e. a ghost.
1126        """
1127        with self.lock_tree_write():
1128            dirstate = self.current_dirstate()
1129            if len(parents_list) > 0:
1130                if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1131                    raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1132            real_trees = []
1133            ghosts = []
1134
1135            parent_ids = [rev_id for rev_id, tree in parents_list]
1136            graph = self.branch.repository.get_graph()
1137            heads = graph.heads(parent_ids)
1138            accepted_revisions = set()
1139
1140            # convert absent trees to the null tree, which we convert back to
1141            # missing on access.
1142            for rev_id, tree in parents_list:
1143                if len(accepted_revisions) > 0:
1144                    # we always accept the first tree
1145                    if rev_id in accepted_revisions or rev_id not in heads:
1146                        # We have already included either this tree, or its
1147                        # descendent, so we skip it.
1148                        continue
1149                _mod_revision.check_not_reserved_id(rev_id)
1150                if tree is not None:
1151                    real_trees.append((rev_id, tree))
1152                else:
1153                    real_trees.append((rev_id,
1154                                       self.branch.repository.revision_tree(
1155                                           _mod_revision.NULL_REVISION)))
1156                    ghosts.append(rev_id)
1157                accepted_revisions.add(rev_id)
1158            updated = False
1159            if (len(real_trees) == 1
1160                and not ghosts
1161                and self.branch.repository._format.fast_deltas
1162                and isinstance(real_trees[0][1], InventoryRevisionTree)
1163                    and self.get_parent_ids()):
1164                rev_id, rev_tree = real_trees[0]
1165                basis_id = self.get_parent_ids()[0]
1166                # There are times when basis_tree won't be in
1167                # self.branch.repository, (switch, for example)
1168                try:
1169                    basis_tree = self.branch.repository.revision_tree(basis_id)
1170                except errors.NoSuchRevision:
1171                    # Fall back to the set_parent_trees(), since we can't use
1172                    # _make_delta if we can't get the RevisionTree
1173                    pass
1174                else:
1175                    delta = rev_tree.root_inventory._make_delta(
1176                        basis_tree.root_inventory)
1177                    dirstate.update_basis_by_delta(delta, rev_id)
1178                    updated = True
1179            if not updated:
1180                dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1181            self._make_dirty(reset_inventory=False)
1182
1183    def _set_root_id(self, file_id):
1184        """See WorkingTree.set_root_id."""
1185        state = self.current_dirstate()
1186        state.set_path_id(b'', file_id)
1187        if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1188            self._make_dirty(reset_inventory=True)
1189
1190    def _sha_from_stat(self, path, stat_result):
1191        """Get a sha digest from the tree's stat cache.
1192
1193        The default implementation assumes no stat cache is present.
1194
1195        :param path: The path.
1196        :param stat_result: The stat result being looked up.
1197        """
1198        return self.current_dirstate().sha1_from_stat(path, stat_result)
1199
1200    def supports_tree_reference(self):
1201        return self._repo_supports_tree_reference
1202
1203    def unlock(self):
1204        """Unlock in format 4 trees needs to write the entire dirstate."""
1205        if self._control_files._lock_count == 1:
1206            # do non-implementation specific cleanup
1207            self._cleanup()
1208
1209            # eventually we should do signature checking during read locks for
1210            # dirstate updates.
1211            if self._control_files._lock_mode == 'w':
1212                if self._dirty:
1213                    self.flush()
1214            if self._dirstate is not None:
1215                # This is a no-op if there are no modifications.
1216                self._dirstate.save()
1217                self._dirstate.unlock()
1218            # TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
1219            #       point. Instead, it could check if the header has been
1220            #       modified when it is locked, and if not, it can hang on to
1221            #       the data it has in memory.
1222            self._dirstate = None
1223            self._inventory = None
1224        # reverse order of locking.
1225        try:
1226            return self._control_files.unlock()
1227        finally:
1228            self.branch.unlock()
1229
1230    def unversion(self, paths):
1231        """Remove the file ids in paths from the current versioned set.
1232
1233        When a directory is unversioned, all of its children are automatically
1234        unversioned.
1235
1236        :param paths: The file ids to stop versioning.
1237        :raises: NoSuchId if any fileid is not currently versioned.
1238        """
1239        with self.lock_tree_write():
1240            if not paths:
1241                return
1242            state = self.current_dirstate()
1243            state._read_dirblocks_if_needed()
1244            file_ids = set()
1245            for path in paths:
1246                file_id = self.path2id(path)
1247                if file_id is None:
1248                    raise errors.NoSuchFile(self, path)
1249                file_ids.add(file_id)
1250            ids_to_unversion = set(file_ids)
1251            paths_to_unversion = set()
1252            # sketch:
1253            # check if the root is to be unversioned, if so, assert for now.
1254            # walk the state marking unversioned things as absent.
1255            # if there are any un-unversioned ids at the end, raise
1256            for key, details in state._dirblocks[0][1]:
1257                if (details[0][0] not in (b'a', b'r') and  # absent or relocated
1258                        key[2] in ids_to_unversion):
1259                    # I haven't written the code to unversion / yet - it should
1260                    # be supported.
1261                    raise errors.BzrError(
1262                        'Unversioning the / is not currently supported')
1263            block_index = 0
1264            while block_index < len(state._dirblocks):
1265                # process one directory at a time.
1266                block = state._dirblocks[block_index]
1267                # first check: is the path one to remove - it or its children
1268                delete_block = False
1269                for path in paths_to_unversion:
1270                    if (block[0].startswith(path) and
1271                        (len(block[0]) == len(path) or
1272                         block[0][len(path)] == '/')):
1273                        # this entire block should be deleted - its the block for a
1274                        # path to unversion; or the child of one
1275                        delete_block = True
1276                        break
1277                # TODO: trim paths_to_unversion as we pass by paths
1278                if delete_block:
1279                    # this block is to be deleted: process it.
1280                    # TODO: we can special case the no-parents case and
1281                    # just forget the whole block.
1282                    entry_index = 0
1283                    while entry_index < len(block[1]):
1284                        entry = block[1][entry_index]
1285                        if entry[1][0][0] in (b'a', b'r'):
1286                            # don't remove absent or renamed entries
1287                            entry_index += 1
1288                        else:
1289                            # Mark this file id as having been removed
1290                            ids_to_unversion.discard(entry[0][2])
1291                            if not state._make_absent(entry):
1292                                # The block has not shrunk.
1293                                entry_index += 1
1294                    # go to the next block. (At the moment we dont delete empty
1295                    # dirblocks)
1296                    block_index += 1
1297                    continue
1298                entry_index = 0
1299                while entry_index < len(block[1]):
1300                    entry = block[1][entry_index]
1301                    if (entry[1][0][0] in (b'a', b'r') or  # absent, relocated
1302                        # ^ some parent row.
1303                            entry[0][2] not in ids_to_unversion):
1304                        # ^ not an id to unversion
1305                        entry_index += 1
1306                        continue
1307                    if entry[1][0][0] == b'd':
1308                        paths_to_unversion.add(
1309                            pathjoin(entry[0][0], entry[0][1]))
1310                    if not state._make_absent(entry):
1311                        entry_index += 1
1312                    # we have unversioned this id
1313                    ids_to_unversion.remove(entry[0][2])
1314                block_index += 1
1315            if ids_to_unversion:
1316                raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1317            self._make_dirty(reset_inventory=False)
1318            # have to change the legacy inventory too.
1319            if self._inventory is not None:
1320                for file_id in file_ids:
1321                    if self._inventory.has_id(file_id):
1322                        self._inventory.remove_recursive_id(file_id)
1323
1324    def rename_one(self, from_rel, to_rel, after=False):
1325        """See WorkingTree.rename_one"""
1326        with self.lock_tree_write():
1327            self.flush()
1328            super(DirStateWorkingTree, self).rename_one(
1329                from_rel, to_rel, after)
1330
1331    def apply_inventory_delta(self, changes):
1332        """See MutableTree.apply_inventory_delta"""
1333        with self.lock_tree_write():
1334            state = self.current_dirstate()
1335            state.update_by_delta(changes)
1336            self._make_dirty(reset_inventory=True)
1337
1338    def update_basis_by_delta(self, new_revid, delta):
1339        """See MutableTree.update_basis_by_delta."""
1340        if self.last_revision() == new_revid:
1341            raise AssertionError()
1342        self.current_dirstate().update_basis_by_delta(delta, new_revid)
1343
1344    def _validate(self):
1345        with self.lock_read():
1346            self._dirstate._validate()
1347
1348    def _write_inventory(self, inv):
1349        """Write inventory as the current inventory."""
1350        if self._dirty:
1351            raise AssertionError("attempting to write an inventory when the "
1352                                 "dirstate is dirty will lose pending changes")
1353        with self.lock_tree_write():
1354            had_inventory = self._inventory is not None
1355            # Setting self._inventory = None forces the dirstate to regenerate the
1356            # working inventory. We do this because self.inventory may be inv, or
1357            # may have been modified, and either case would prevent a clean delta
1358            # being created.
1359            self._inventory = None
1360            # generate a delta,
1361            delta = inv._make_delta(self.root_inventory)
1362            # and apply it.
1363            self.apply_inventory_delta(delta)
1364            if had_inventory:
1365                self._inventory = inv
1366            self.flush()
1367
1368    def reset_state(self, revision_ids=None):
1369        """Reset the state of the working tree.
1370
1371        This does a hard-reset to a last-known-good state. This is a way to
1372        fix if something got corrupted (like the .bzr/checkout/dirstate file)
1373        """
1374        with self.lock_tree_write():
1375            if revision_ids is None:
1376                revision_ids = self.get_parent_ids()
1377            if not revision_ids:
1378                base_tree = self.branch.repository.revision_tree(
1379                    _mod_revision.NULL_REVISION)
1380                trees = []
1381            else:
1382                trees = list(zip(revision_ids,
1383                                 self.branch.repository.revision_trees(revision_ids)))
1384                base_tree = trees[0][1]
1385            state = self.current_dirstate()
1386            # We don't support ghosts yet
1387            state.set_state_from_scratch(base_tree.root_inventory, trees, [])
1388
1389
1390class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1391
1392    def __init__(self, tree):
1393        self.tree = tree
1394
1395    def sha1(self, abspath):
1396        """See dirstate.SHA1Provider.sha1()."""
1397        filters = self.tree._content_filter_stack(
1398            self.tree.relpath(osutils.safe_unicode(abspath)))
1399        return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1400
1401    def stat_and_sha1(self, abspath):
1402        """See dirstate.SHA1Provider.stat_and_sha1()."""
1403        filters = self.tree._content_filter_stack(
1404            self.tree.relpath(osutils.safe_unicode(abspath)))
1405        with open(abspath, 'rb', 65000) as file_obj:
1406            statvalue = os.fstat(file_obj.fileno())
1407            if filters:
1408                file_obj, size = _mod_filters.filtered_input_file(file_obj, filters)
1409                statvalue = _mod_filters.FilteredStat(statvalue, size)
1410            sha1 = osutils.size_sha_file(file_obj)[1]
1411        return statvalue, sha1
1412
1413
1414class ContentFilteringDirStateWorkingTree(DirStateWorkingTree):
1415    """Dirstate working tree that supports content filtering.
1416
1417    The dirstate holds the hash and size of the canonical form of the file,
1418    and most methods must return that.
1419    """
1420
1421    def _file_content_summary(self, path, stat_result):
1422        # This is to support the somewhat obsolete path_content_summary method
1423        # with content filtering: see
1424        # <https://bugs.launchpad.net/bzr/+bug/415508>.
1425        #
1426        # If the dirstate cache is up to date and knows the hash and size,
1427        # return that.
1428        # Otherwise if there are no content filters, return the on-disk size
1429        # and leave the hash blank.
1430        # Otherwise, read and filter the on-disk file and use its size and
1431        # hash.
1432        #
1433        # The dirstate doesn't store the size of the canonical form so we
1434        # can't trust it for content-filtered trees.  We just return None.
1435        dirstate_sha1 = self._dirstate.sha1_from_stat(path, stat_result)
1436        executable = self._is_executable_from_path_and_stat(path, stat_result)
1437        return ('file', None, executable, dirstate_sha1)
1438
1439
1440class WorkingTree4(DirStateWorkingTree):
1441    """This is the Format 4 working tree.
1442
1443    This differs from WorkingTree by:
1444     - Having a consolidated internal dirstate, stored in a
1445       randomly-accessible sorted file on disk.
1446     - Not having a regular inventory attribute.  One can be synthesized
1447       on demand but this is expensive and should be avoided.
1448
1449    This is new in bzr 0.15.
1450    """
1451
1452
1453class WorkingTree5(ContentFilteringDirStateWorkingTree):
1454    """This is the Format 5 working tree.
1455
1456    This differs from WorkingTree4 by:
1457     - Supporting content filtering.
1458
1459    This is new in bzr 1.11.
1460    """
1461
1462
1463class WorkingTree6(ContentFilteringDirStateWorkingTree):
1464    """This is the Format 6 working tree.
1465
1466    This differs from WorkingTree5 by:
1467     - Supporting a current view that may mask the set of files in a tree
1468       impacted by most user operations.
1469
1470    This is new in bzr 1.14.
1471    """
1472
1473    def _make_views(self):
1474        return views.PathBasedViews(self)
1475
1476
1477class DirStateWorkingTreeFormat(WorkingTreeFormatMetaDir):
1478
1479    missing_parent_conflicts = True
1480
1481    supports_versioned_directories = True
1482
1483    _lock_class = LockDir
1484    _lock_file_name = 'lock'
1485
1486    def _open_control_files(self, a_controldir):
1487        transport = a_controldir.get_workingtree_transport(None)
1488        return LockableFiles(transport, self._lock_file_name,
1489                             self._lock_class)
1490
1491    def initialize(self, a_controldir, revision_id=None, from_branch=None,
1492                   accelerator_tree=None, hardlink=False):
1493        """See WorkingTreeFormat.initialize().
1494
1495        :param revision_id: allows creating a working tree at a different
1496            revision than the branch is at.
1497        :param accelerator_tree: A tree which can be used for retrieving file
1498            contents more quickly than the revision tree, i.e. a workingtree.
1499            The revision tree will be used for cases where accelerator_tree's
1500            content is different.
1501        :param hardlink: If true, hard-link files from accelerator_tree,
1502            where possible.
1503
1504        These trees get an initial random root id, if their repository supports
1505        rich root data, TREE_ROOT otherwise.
1506        """
1507        if not isinstance(a_controldir.transport, LocalTransport):
1508            raise errors.NotLocalUrl(a_controldir.transport.base)
1509        transport = a_controldir.get_workingtree_transport(self)
1510        control_files = self._open_control_files(a_controldir)
1511        control_files.create_lock()
1512        control_files.lock_write()
1513        transport.put_bytes('format', self.as_string(),
1514                            mode=a_controldir._get_file_mode())
1515        if from_branch is not None:
1516            branch = from_branch
1517        else:
1518            branch = a_controldir.open_branch()
1519        if revision_id is None:
1520            revision_id = branch.last_revision()
1521        local_path = transport.local_abspath('dirstate')
1522        # write out new dirstate (must exist when we create the tree)
1523        state = dirstate.DirState.initialize(local_path)
1524        state.unlock()
1525        del state
1526        wt = self._tree_class(a_controldir.root_transport.local_abspath('.'),
1527                              branch,
1528                              _format=self,
1529                              _controldir=a_controldir,
1530                              _control_files=control_files)
1531        wt._new_tree()
1532        wt.lock_tree_write()
1533        try:
1534            self._init_custom_control_files(wt)
1535            if revision_id in (None, _mod_revision.NULL_REVISION):
1536                if branch.repository.supports_rich_root():
1537                    wt._set_root_id(generate_ids.gen_root_id())
1538                else:
1539                    wt._set_root_id(ROOT_ID)
1540                wt.flush()
1541            basis = None
1542            # frequently, we will get here due to branching.  The accelerator
1543            # tree will be the tree from the branch, so the desired basis
1544            # tree will often be a parent of the accelerator tree.
1545            if accelerator_tree is not None:
1546                try:
1547                    basis = accelerator_tree.revision_tree(revision_id)
1548                except errors.NoSuchRevision:
1549                    pass
1550            if basis is None:
1551                basis = branch.repository.revision_tree(revision_id)
1552            if revision_id == _mod_revision.NULL_REVISION:
1553                parents_list = []
1554            else:
1555                parents_list = [(revision_id, basis)]
1556            with basis.lock_read():
1557                wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1558                wt.flush()
1559                # if the basis has a root id we have to use that; otherwise we
1560                # use a new random one
1561                basis_root_id = basis.path2id('')
1562                if basis_root_id is not None:
1563                    wt._set_root_id(basis_root_id)
1564                    wt.flush()
1565                if wt.supports_content_filtering():
1566                    # The original tree may not have the same content filters
1567                    # applied so we can't safely build the inventory delta from
1568                    # the source tree.
1569                    delta_from_tree = False
1570                else:
1571                    delta_from_tree = True
1572                # delta_from_tree is safe even for DirStateRevisionTrees,
1573                # because wt4.apply_inventory_delta does not mutate the input
1574                # inventory entries.
1575                bzr_transform.build_tree(
1576                    basis, wt, accelerator_tree,
1577                    hardlink=hardlink,
1578                    delta_from_tree=delta_from_tree)
1579                for hook in MutableTree.hooks['post_build_tree']:
1580                    hook(wt)
1581        finally:
1582            control_files.unlock()
1583            wt.unlock()
1584        return wt
1585
1586    def _init_custom_control_files(self, wt):
1587        """Subclasses with custom control files should override this method.
1588
1589        The working tree and control files are locked for writing when this
1590        method is called.
1591
1592        :param wt: the WorkingTree object
1593        """
1594
1595    def open(self, a_controldir, _found=False):
1596        """Return the WorkingTree object for a_controldir
1597
1598        _found is a private parameter, do not use it. It is used to indicate
1599               if format probing has already been done.
1600        """
1601        if not _found:
1602            # we are being called directly and must probe.
1603            raise NotImplementedError
1604        if not isinstance(a_controldir.transport, LocalTransport):
1605            raise errors.NotLocalUrl(a_controldir.transport.base)
1606        wt = self._open(a_controldir, self._open_control_files(a_controldir))
1607        return wt
1608
1609    def _open(self, a_controldir, control_files):
1610        """Open the tree itself.
1611
1612        :param a_controldir: the dir for the tree.
1613        :param control_files: the control files for the tree.
1614        """
1615        return self._tree_class(a_controldir.root_transport.local_abspath('.'),
1616                                branch=a_controldir.open_branch(),
1617                                _format=self,
1618                                _controldir=a_controldir,
1619                                _control_files=control_files)
1620
1621    def __get_matchingcontroldir(self):
1622        return self._get_matchingcontroldir()
1623
1624    def _get_matchingcontroldir(self):
1625        """Overrideable method to get a bzrdir for testing."""
1626        # please test against something that will let us do tree references
1627        return controldir.format_registry.make_controldir(
1628            'development-subtree')
1629
1630    _matchingcontroldir = property(__get_matchingcontroldir)
1631
1632
1633class WorkingTreeFormat4(DirStateWorkingTreeFormat):
1634    """The first consolidated dirstate working tree format.
1635
1636    This format:
1637        - exists within a metadir controlling .bzr
1638        - includes an explicit version marker for the workingtree control
1639          files, separate from the ControlDir format
1640        - modifies the hash cache format
1641        - is new in bzr 0.15
1642        - uses a LockDir to guard access to it.
1643    """
1644
1645    upgrade_recommended = False
1646
1647    _tree_class = WorkingTree4
1648
1649    @classmethod
1650    def get_format_string(cls):
1651        """See WorkingTreeFormat.get_format_string()."""
1652        return b"Bazaar Working Tree Format 4 (bzr 0.15)\n"
1653
1654    def get_format_description(self):
1655        """See WorkingTreeFormat.get_format_description()."""
1656        return "Working tree format 4"
1657
1658
1659class WorkingTreeFormat5(DirStateWorkingTreeFormat):
1660    """WorkingTree format supporting content filtering.
1661    """
1662
1663    upgrade_recommended = False
1664
1665    _tree_class = WorkingTree5
1666
1667    @classmethod
1668    def get_format_string(cls):
1669        """See WorkingTreeFormat.get_format_string()."""
1670        return b"Bazaar Working Tree Format 5 (bzr 1.11)\n"
1671
1672    def get_format_description(self):
1673        """See WorkingTreeFormat.get_format_description()."""
1674        return "Working tree format 5"
1675
1676    def supports_content_filtering(self):
1677        return True
1678
1679
1680class WorkingTreeFormat6(DirStateWorkingTreeFormat):
1681    """WorkingTree format supporting views.
1682    """
1683
1684    upgrade_recommended = False
1685
1686    _tree_class = WorkingTree6
1687
1688    @classmethod
1689    def get_format_string(cls):
1690        """See WorkingTreeFormat.get_format_string()."""
1691        return b"Bazaar Working Tree Format 6 (bzr 1.14)\n"
1692
1693    def get_format_description(self):
1694        """See WorkingTreeFormat.get_format_description()."""
1695        return "Working tree format 6"
1696
1697    def _init_custom_control_files(self, wt):
1698        """Subclasses with custom control files should override this method."""
1699        wt._transport.put_bytes('views', b'',
1700                                mode=wt.controldir._get_file_mode())
1701
1702    def supports_content_filtering(self):
1703        return True
1704
1705    def supports_views(self):
1706        return True
1707
1708    def _get_matchingcontroldir(self):
1709        """Overrideable method to get a bzrdir for testing."""
1710        # We use 'development-subtree' instead of '2a', because we have a
1711        # few tests that want to test tree references
1712        return controldir.format_registry.make_controldir('development-subtree')
1713
1714
1715class DirStateRevisionTree(InventoryTree):
1716    """A revision tree pulling the inventory from a dirstate.
1717
1718    Note that this is one of the historical (ie revision) trees cached in the
1719    dirstate for easy access, not the workingtree.
1720    """
1721
1722    def __init__(self, dirstate, revision_id, repository, nested_tree_transport):
1723        self._dirstate = dirstate
1724        self._revision_id = revision_id
1725        self._repository = repository
1726        self._inventory = None
1727        self._locked = 0
1728        self._dirstate_locked = False
1729        self._nested_tree_transport = nested_tree_transport
1730        self._repo_supports_tree_reference = getattr(
1731            repository._format, "supports_tree_reference",
1732            False)
1733
1734    def __repr__(self):
1735        return "<%s of %s in %s>" % \
1736            (self.__class__.__name__, self._revision_id, self._dirstate)
1737
1738    def annotate_iter(self, path,
1739                      default_revision=_mod_revision.CURRENT_REVISION):
1740        """See Tree.annotate_iter"""
1741        file_id = self.path2id(path)
1742        text_key = (file_id, self.get_file_revision(path))
1743        annotations = self._repository.texts.annotate(text_key)
1744        return [(key[-1], line) for (key, line) in annotations]
1745
1746    def _comparison_data(self, entry, path):
1747        """See Tree._comparison_data."""
1748        if entry is None:
1749            return None, False, None
1750        # trust the entry as RevisionTree does, but this may not be
1751        # sensible: the entry might not have come from us?
1752        return entry.kind, entry.executable, None
1753
1754    def _get_file_revision(self, path, file_id, vf, tree_revision):
1755        """Ensure that file_id, tree_revision is in vf to plan the merge."""
1756        last_revision = self.get_file_revision(path)
1757        base_vf = self._repository.texts
1758        if base_vf not in vf.fallback_versionedfiles:
1759            vf.fallback_versionedfiles.append(base_vf)
1760        return last_revision
1761
1762    def filter_unversioned_files(self, paths):
1763        """Filter out paths that are not versioned.
1764
1765        :return: set of paths.
1766        """
1767        pred = self.has_filename
1768        return set((p for p in paths if not pred(p)))
1769
1770    def id2path(self, file_id, recurse='down'):
1771        "Convert a file-id to a path."
1772        with self.lock_read():
1773            entry = self._get_entry(file_id=file_id)
1774            if entry == (None, None):
1775                if recurse == 'down':
1776                    if 'evil' in debug.debug_flags:
1777                        trace.mutter_callsite(
1778                            2, "Tree.id2path scans all nested trees.")
1779
1780                    for nested_path in self.iter_references():
1781                        nested_tree = self.get_nested_tree(nested_path)
1782                        try:
1783                            return osutils.pathjoin(nested_path, nested_tree.id2path(file_id))
1784                        except errors.NoSuchId:
1785                            pass
1786                raise errors.NoSuchId(tree=self, file_id=file_id)
1787            path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
1788            return path_utf8.decode('utf8')
1789
1790    def get_nested_tree(self, path):
1791        with self.lock_read():
1792            nested_revid = self.get_reference_revision(path)
1793            return self._get_nested_tree(path, None, nested_revid)
1794
1795    def _get_nested_tree(self, path, file_id, reference_revision):
1796        branch = _mod_branch.Branch.open_from_transport(
1797            self._nested_tree_transport.clone(path))
1798        try:
1799            revtree = branch.repository.revision_tree(reference_revision)
1800        except errors.NoSuchRevision:
1801            raise MissingNestedTree(path)
1802        if file_id is not None and revtree.path2id('') != file_id:
1803            raise AssertionError('mismatching file id: %r != %r' % (
1804                revtree.path2id(''), file_id))
1805        return revtree
1806
1807    def iter_references(self):
1808        if not self._repo_supports_tree_reference:
1809            # When the repo doesn't support references, we will have nothing to
1810            # return
1811            return iter([])
1812        # Otherwise, fall back to the default implementation
1813        return super(DirStateRevisionTree, self).iter_references()
1814
1815    def _get_parent_index(self):
1816        """Return the index in the dirstate referenced by this tree."""
1817        return self._dirstate.get_parent_ids().index(self._revision_id) + 1
1818
1819    def _get_entry(self, file_id=None, path=None):
1820        """Get the dirstate row for file_id or path.
1821
1822        If either file_id or path is supplied, it is used as the key to lookup.
1823        If both are supplied, the fastest lookup is used, and an error is
1824        raised if they do not both point at the same row.
1825
1826        :param file_id: An optional unicode file_id to be looked up.
1827        :param path: An optional unicode path to be looked up.
1828        :return: The dirstate row tuple for path/file_id, or (None, None)
1829        """
1830        if file_id is None and path is None:
1831            raise errors.BzrError('must supply file_id or path')
1832        if path is not None:
1833            path = path.encode('utf8')
1834        try:
1835            parent_index = self._get_parent_index()
1836        except ValueError:
1837            raise errors.NoSuchRevisionInTree(self._dirstate, self._revision_id)
1838        return self._dirstate._get_entry(parent_index, fileid_utf8=file_id,
1839                                         path_utf8=path)
1840
1841    def _generate_inventory(self):
1842        """Create and set self.inventory from the dirstate object.
1843
1844        (So this is only called the first time the inventory is requested for
1845        this tree; it then remains in memory until it's out of date.)
1846
1847        This is relatively expensive: we have to walk the entire dirstate.
1848        """
1849        if not self._locked:
1850            raise AssertionError(
1851                'cannot generate inventory of an unlocked '
1852                'dirstate revision tree')
1853        # separate call for profiling - makes it clear where the costs are.
1854        self._dirstate._read_dirblocks_if_needed()
1855        if self._revision_id not in self._dirstate.get_parent_ids():
1856            raise AssertionError(
1857                'parent %s has disappeared from %s' % (
1858                    self._revision_id, self._dirstate.get_parent_ids()))
1859        parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
1860        # This is identical now to the WorkingTree _generate_inventory except
1861        # for the tree index use.
1862        root_key, current_entry = self._dirstate._get_entry(
1863            parent_index, path_utf8=b'')
1864        current_id = root_key[2]
1865        if current_entry[parent_index][0] != b'd':
1866            raise AssertionError()
1867        inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1868        inv.root.revision = current_entry[parent_index][4]
1869        # Turn some things into local variables
1870        minikind_to_kind = dirstate.DirState._minikind_to_kind
1871        factory = entry_factory
1872        utf8_decode = cache_utf8._utf8_decode
1873        inv_byid = inv._byid
1874        # we could do this straight out of the dirstate; it might be fast
1875        # and should be profiled - RBC 20070216
1876        parent_ies = {b'': inv.root}
1877        for block in self._dirstate._dirblocks[1:]:  # skip root
1878            dirname = block[0]
1879            try:
1880                parent_ie = parent_ies[dirname]
1881            except KeyError:
1882                # all the paths in this block are not versioned in this tree
1883                continue
1884            for key, entry in block[1]:
1885                (minikind, fingerprint, size, executable,
1886                 revid) = entry[parent_index]
1887                if minikind in (b'a', b'r'):  # absent, relocated
1888                    # not this tree
1889                    continue
1890                name = key[1]
1891                name_unicode = utf8_decode(name)[0]
1892                file_id = key[2]
1893                kind = minikind_to_kind[minikind]
1894                inv_entry = factory[kind](file_id, name_unicode,
1895                                          parent_ie.file_id)
1896                inv_entry.revision = revid
1897                if kind == 'file':
1898                    inv_entry.executable = executable
1899                    inv_entry.text_size = size
1900                    inv_entry.text_sha1 = fingerprint
1901                elif kind == 'directory':
1902                    parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
1903                elif kind == 'symlink':
1904                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1905                elif kind == 'tree-reference':
1906                    inv_entry.reference_revision = fingerprint or None
1907                else:
1908                    raise AssertionError(
1909                        "cannot convert entry %r into an InventoryEntry"
1910                        % entry)
1911                # These checks cost us around 40ms on a 55k entry tree
1912                if file_id in inv_byid:
1913                    raise AssertionError(
1914                        'file_id %s already in'
1915                        ' inventory as %s' % (file_id, inv_byid[file_id]))
1916                if name_unicode in parent_ie.children:
1917                    raise AssertionError('name %r already in parent'
1918                                         % (name_unicode,))
1919                inv_byid[file_id] = inv_entry
1920                parent_ie.children[name_unicode] = inv_entry
1921        self._inventory = inv
1922
1923    def get_file_mtime(self, path):
1924        """Return the modification time for this record.
1925
1926        We return the timestamp of the last-changed revision.
1927        """
1928        # Make sure the file exists
1929        entry = self._get_entry(path=path)
1930        if entry == (None, None): # do we raise?
1931            raise errors.NoSuchFile(path)
1932        parent_index = self._get_parent_index()
1933        last_changed_revision = entry[1][parent_index][4]
1934        try:
1935            rev = self._repository.get_revision(last_changed_revision)
1936        except errors.NoSuchRevision:
1937            raise FileTimestampUnavailable(path)
1938        return rev.timestamp
1939
1940    def get_file_sha1(self, path, stat_value=None):
1941        entry = self._get_entry(path=path)
1942        parent_index = self._get_parent_index()
1943        parent_details = entry[1][parent_index]
1944        if parent_details[0] == b'f':
1945            return parent_details[1]
1946        return None
1947
1948    def get_file_revision(self, path):
1949        with self.lock_read():
1950            inv, inv_file_id = self._path2inv_file_id(path)
1951            return inv.get_entry(inv_file_id).revision
1952
1953    def get_file(self, path):
1954        return BytesIO(self.get_file_text(path))
1955
1956    def get_file_size(self, path):
1957        """See Tree.get_file_size"""
1958        inv, inv_file_id = self._path2inv_file_id(path)
1959        return inv.get_entry(inv_file_id).text_size
1960
1961    def get_file_text(self, path):
1962        content = None
1963        for _, content_iter in self.iter_files_bytes([(path, None)]):
1964            if content is not None:
1965                raise AssertionError('iter_files_bytes returned'
1966                                     ' too many entries')
1967            # For each entry returned by iter_files_bytes, we must consume the
1968            # content_iter before we step the files iterator.
1969            content = b''.join(content_iter)
1970        if content is None:
1971            raise AssertionError('iter_files_bytes did not return'
1972                                 ' the requested data')
1973        return content
1974
1975    def get_reference_revision(self, path):
1976        inv, inv_file_id = self._path2inv_file_id(path)
1977        return inv.get_entry(inv_file_id).reference_revision
1978
1979    def iter_files_bytes(self, desired_files):
1980        """See Tree.iter_files_bytes.
1981
1982        This version is implemented on top of Repository.iter_files_bytes"""
1983        parent_index = self._get_parent_index()
1984        repo_desired_files = []
1985        for path, identifier in desired_files:
1986            entry = self._get_entry(path=path)
1987            if entry == (None, None):
1988                raise errors.NoSuchFile(path)
1989            repo_desired_files.append((entry[0][2], entry[1][parent_index][4],
1990                                       identifier))
1991        return self._repository.iter_files_bytes(repo_desired_files)
1992
1993    def get_symlink_target(self, path):
1994        entry = self._get_entry(path=path)
1995        if entry is None:
1996            raise errors.NoSuchFile(tree=self, path=path)
1997        parent_index = self._get_parent_index()
1998        if entry[1][parent_index][0] != b'l':
1999            return None
2000        else:
2001            target = entry[1][parent_index][1]
2002            target = target.decode('utf8')
2003            return target
2004
2005    def get_revision_id(self):
2006        """Return the revision id for this tree."""
2007        return self._revision_id
2008
2009    def _get_root_inventory(self):
2010        if self._inventory is not None:
2011            return self._inventory
2012        self._must_be_locked()
2013        self._generate_inventory()
2014        return self._inventory
2015
2016    root_inventory = property(_get_root_inventory,
2017                              doc="Inventory of this Tree")
2018
2019    def get_parent_ids(self):
2020        """The parents of a tree in the dirstate are not cached."""
2021        return self._repository.get_revision(self._revision_id).parent_ids
2022
2023    def has_filename(self, filename):
2024        return bool(self.path2id(filename))
2025
2026    def kind(self, path):
2027        entry = self._get_entry(path=path)[1]
2028        if entry is None:
2029            raise errors.NoSuchFile(path)
2030        parent_index = self._get_parent_index()
2031        return dirstate.DirState._minikind_to_kind[entry[parent_index][0]]
2032
2033    def stored_kind(self, path):
2034        """See Tree.stored_kind"""
2035        return self.kind(path)
2036
2037    def path_content_summary(self, path):
2038        """See Tree.path_content_summary."""
2039        inv, inv_file_id = self._path2inv_file_id(path)
2040        if inv_file_id is None:
2041            return ('missing', None, None, None)
2042        entry = inv.get_entry(inv_file_id)
2043        kind = entry.kind
2044        if kind == 'file':
2045            return (kind, entry.text_size, entry.executable, entry.text_sha1)
2046        elif kind == 'symlink':
2047            return (kind, None, None, entry.symlink_target)
2048        else:
2049            return (kind, None, None, None)
2050
2051    def is_executable(self, path):
2052        inv, inv_file_id = self._path2inv_file_id(path)
2053        if inv_file_id is None:
2054            raise errors.NoSuchFile(path)
2055        ie = inv.get_entry(inv_file_id)
2056        if ie.kind != "file":
2057            return False
2058        return ie.executable
2059
2060    def is_locked(self):
2061        return self._locked
2062
2063    def list_files(self, include_root=False, from_dir=None, recursive=True,
2064                   recurse_nested=False):
2065        # The only files returned by this are those from the version
2066        if from_dir is None:
2067            from_dir_id = None
2068            inv = self.root_inventory
2069        else:
2070            inv, from_dir_id = self._path2inv_file_id(from_dir)
2071            if from_dir_id is None:
2072                # Directory not versioned
2073                return iter([])
2074        def iter_entries(inv):
2075            entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
2076            if inv.root is not None and not include_root and from_dir is None:
2077                # skip the root for compatibility with the current apis.
2078                next(entries)
2079            for path, entry in entries:
2080                if entry.kind == 'tree-reference' and recurse_nested:
2081                    subtree = self._get_nested_tree(
2082                        path, entry.file_id, entry.reference_revision)
2083                    for subpath, status, kind, entry in subtree.list_files(
2084                            include_root=True, recursive=recursive,
2085                            recurse_nested=recurse_nested):
2086                        if subpath:
2087                            full_subpath = osutils.pathjoin(path, subpath)
2088                        else:
2089                            full_subpath = path
2090                        yield full_subpath, status, kind, entry
2091                else:
2092                    yield path, 'V', entry.kind, entry
2093        return iter_entries(inv)
2094
2095    def lock_read(self):
2096        """Lock the tree for a set of operations.
2097
2098        :return: A breezy.lock.LogicalLockResult.
2099        """
2100        if not self._locked:
2101            self._repository.lock_read()
2102            if self._dirstate._lock_token is None:
2103                self._dirstate.lock_read()
2104                self._dirstate_locked = True
2105        self._locked += 1
2106        return LogicalLockResult(self.unlock)
2107
2108    def _must_be_locked(self):
2109        if not self._locked:
2110            raise errors.ObjectNotLocked(self)
2111
2112    def path2id(self, path):
2113        """Return the id for path in this tree."""
2114        # lookup by path: faster than splitting and walking the ivnentory.
2115        if isinstance(path, list):
2116            if path == []:
2117                path = [""]
2118            path = osutils.pathjoin(*path)
2119        with self.lock_read():
2120            entry = self._get_entry(path=path)
2121            if entry == (None, None):
2122                nested_tree, subpath = self.get_containing_nested_tree(path)
2123                if nested_tree is not None:
2124                    return nested_tree.path2id(subpath)
2125                return None
2126            return entry[0][2]
2127
2128    def unlock(self):
2129        """Unlock, freeing any cache memory used during the lock."""
2130        # outside of a lock, the inventory is suspect: release it.
2131        self._locked -= 1
2132        if not self._locked:
2133            self._inventory = None
2134            self._locked = 0
2135            if self._dirstate_locked:
2136                self._dirstate.unlock()
2137                self._dirstate_locked = False
2138            self._repository.unlock()
2139
2140    def supports_tree_reference(self):
2141        with self.lock_read():
2142            return self._repo_supports_tree_reference
2143
2144    def walkdirs(self, prefix=""):
2145        # TODO: jam 20070215 This is the lazy way by using the RevisionTree
2146        # implementation based on an inventory.
2147        # This should be cleaned up to use the much faster Dirstate code
2148        # So for now, we just build up the parent inventory, and extract
2149        # it the same way RevisionTree does.
2150        _directory = 'directory'
2151        inv = self._get_root_inventory()
2152        top_id = inv.path2id(prefix)
2153        if top_id is None:
2154            pending = []
2155        else:
2156            pending = [(prefix, top_id)]
2157        while pending:
2158            dirblock = []
2159            relpath, file_id = pending.pop()
2160            # 0 - relpath, 1- file-id
2161            if relpath:
2162                relroot = relpath + '/'
2163            else:
2164                relroot = ""
2165            # FIXME: stash the node in pending
2166            entry = inv.get_entry(file_id)
2167            subdirs = []
2168            for name, child in entry.sorted_children():
2169                toppath = relroot + name
2170                dirblock.append((toppath, name, child.kind, None, child.kind))
2171                if child.kind == _directory:
2172                    subdirs.append((toppath, child.file_id))
2173            yield relpath, dirblock
2174            # push the user specified dirs from dirblock
2175            pending.extend(reversed(subdirs))
2176
2177
2178class InterDirStateTree(InterInventoryTree):
2179    """Fast path optimiser for changes_from with dirstate trees.
2180
2181    This is used only when both trees are in the dirstate working file, and
2182    the source is any parent within the dirstate, and the destination is
2183    the current working tree of the same dirstate.
2184    """
2185    # this could be generalized to allow comparisons between any trees in the
2186    # dirstate, and possibly between trees stored in different dirstates.
2187
2188    def __init__(self, source, target):
2189        super(InterDirStateTree, self).__init__(source, target)
2190        if not InterDirStateTree.is_compatible(source, target):
2191            raise Exception("invalid source %r and target %r" %
2192                            (source, target))
2193
2194    @staticmethod
2195    def make_source_parent_tree(source, target):
2196        """Change the source tree into a parent of the target."""
2197        revid = source.commit('record tree')
2198        target.branch.fetch(source.branch, revid)
2199        target.set_parent_ids([revid])
2200        return target.basis_tree(), target
2201
2202    @classmethod
2203    def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
2204        result = klass.make_source_parent_tree(source, target)
2205        result[1]._iter_changes = dirstate.ProcessEntryPython
2206        return result
2207
2208    @classmethod
2209    def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
2210                                                  target):
2211        from .tests.test__dirstate_helpers import \
2212            compiled_dirstate_helpers_feature
2213        test_case.requireFeature(compiled_dirstate_helpers_feature)
2214        from ._dirstate_helpers_pyx import ProcessEntryC
2215        result = klass.make_source_parent_tree(source, target)
2216        result[1]._iter_changes = ProcessEntryC
2217        return result
2218
2219    _matching_from_tree_format = WorkingTreeFormat4()
2220    _matching_to_tree_format = WorkingTreeFormat4()
2221
2222    @classmethod
2223    def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
2224        # This method shouldn't be called, because we have python and C
2225        # specific flavours.
2226        raise NotImplementedError
2227
2228    def iter_changes(self, include_unchanged=False,
2229                     specific_files=None, pb=None, extra_trees=[],
2230                     require_versioned=True, want_unversioned=False):
2231        """Return the changes from source to target.
2232
2233        :return: An iterator that yields tuples. See InterTree.iter_changes
2234            for details.
2235        :param specific_files: An optional list of file paths to restrict the
2236            comparison to. When mapping filenames to ids, all matches in all
2237            trees (including optional extra_trees) are used, and all children of
2238            matched directories are included.
2239        :param include_unchanged: An optional boolean requesting the inclusion of
2240            unchanged entries in the result.
2241        :param extra_trees: An optional list of additional trees to use when
2242            mapping the contents of specific_files (paths) to file_ids.
2243        :param require_versioned: If True, all files in specific_files must be
2244            versioned in one of source, target, extra_trees or
2245            PathsNotVersionedError is raised.
2246        :param want_unversioned: Should unversioned files be returned in the
2247            output. An unversioned file is defined as one with (False, False)
2248            for the versioned pair.
2249        """
2250        # TODO: handle extra trees in the dirstate.
2251        if (extra_trees or specific_files == []):
2252            # we can't fast-path these cases (yet)
2253            return super(InterDirStateTree, self).iter_changes(
2254                include_unchanged, specific_files, pb, extra_trees,
2255                require_versioned, want_unversioned=want_unversioned)
2256        parent_ids = self.target.get_parent_ids()
2257        if not (self.source._revision_id in parent_ids
2258                or self.source._revision_id == _mod_revision.NULL_REVISION):
2259            raise AssertionError(
2260                "revision {%s} is not stored in {%s}, but %s "
2261                "can only be used for trees stored in the dirstate"
2262                % (self.source._revision_id, self.target, self.iter_changes))
2263        target_index = 0
2264        if self.source._revision_id == _mod_revision.NULL_REVISION:
2265            source_index = None
2266            indices = (target_index,)
2267        else:
2268            if not (self.source._revision_id in parent_ids):
2269                raise AssertionError(
2270                    "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
2271                        self.source._revision_id, parent_ids))
2272            source_index = 1 + parent_ids.index(self.source._revision_id)
2273            indices = (source_index, target_index)
2274
2275        if specific_files is None:
2276            specific_files = {''}
2277
2278        # -- get the state object and prepare it.
2279        state = self.target.current_dirstate()
2280        state._read_dirblocks_if_needed()
2281        if require_versioned:
2282            # -- check all supplied paths are versioned in a search tree. --
2283            not_versioned = []
2284            for path in specific_files:
2285                path_entries = state._entries_for_path(path.encode('utf-8'))
2286                if not path_entries:
2287                    # this specified path is not present at all: error
2288                    not_versioned.append(path)
2289                    continue
2290                found_versioned = False
2291                # for each id at this path
2292                for entry in path_entries:
2293                    # for each tree.
2294                    for index in indices:
2295                        if entry[1][index][0] != b'a':  # absent
2296                            found_versioned = True
2297                            # all good: found a versioned cell
2298                            break
2299                if not found_versioned:
2300                    # none of the indexes was not 'absent' at all ids for this
2301                    # path.
2302                    not_versioned.append(path)
2303            if len(not_versioned) > 0:
2304                raise errors.PathsNotVersionedError(not_versioned)
2305
2306        # remove redundancy in supplied specific_files to prevent over-scanning
2307        # make all specific_files utf8
2308        search_specific_files_utf8 = set()
2309        for path in osutils.minimum_path_selection(specific_files):
2310            # Note, if there are many specific files, using cache_utf8
2311            # would be good here.
2312            search_specific_files_utf8.add(path.encode('utf8'))
2313
2314        iter_changes = self.target._iter_changes(
2315            include_unchanged, self.target._supports_executable(),
2316            search_specific_files_utf8, state, source_index, target_index,
2317            want_unversioned, self.target)
2318        return iter_changes.iter_changes()
2319
2320    @staticmethod
2321    def is_compatible(source, target):
2322        # the target must be a dirstate working tree
2323        if not isinstance(target, DirStateWorkingTree):
2324            return False
2325        # the source must be a revtree or dirstate rev tree.
2326        if not isinstance(source,
2327                          (revisiontree.RevisionTree, DirStateRevisionTree)):
2328            return False
2329        # the source revid must be in the target dirstate
2330        if not (source._revision_id == _mod_revision.NULL_REVISION or
2331                source._revision_id in target.get_parent_ids()):
2332            # TODO: what about ghosts? it may well need to
2333            # check for them explicitly.
2334            return False
2335        return True
2336
2337
2338InterTree.register_optimiser(InterDirStateTree)
2339
2340
2341class Converter3to4(object):
2342    """Perform an in-place upgrade of format 3 to format 4 trees."""
2343
2344    def __init__(self):
2345        self.target_format = WorkingTreeFormat4()
2346
2347    def convert(self, tree):
2348        # lock the control files not the tree, so that we dont get tree
2349        # on-unlock behaviours, and so that noone else diddles with the
2350        # tree during upgrade.
2351        tree._control_files.lock_write()
2352        try:
2353            tree.read_working_inventory()
2354            self.create_dirstate_data(tree)
2355            self.update_format(tree)
2356            self.remove_xml_files(tree)
2357        finally:
2358            tree._control_files.unlock()
2359
2360    def create_dirstate_data(self, tree):
2361        """Create the dirstate based data for tree."""
2362        local_path = tree.controldir.get_workingtree_transport(
2363            None).local_abspath('dirstate')
2364        state = dirstate.DirState.from_tree(tree, local_path)
2365        state.save()
2366        state.unlock()
2367
2368    def remove_xml_files(self, tree):
2369        """Remove the oldformat 3 data."""
2370        transport = tree.controldir.get_workingtree_transport(None)
2371        for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2372                     'pending-merges', 'stat-cache']:
2373            try:
2374                transport.delete(path)
2375            except errors.NoSuchFile:
2376                # some files are optional - just deal.
2377                pass
2378
2379    def update_format(self, tree):
2380        """Change the format marker."""
2381        tree._transport.put_bytes('format',
2382                                  self.target_format.as_string(),
2383                                  mode=tree.controldir._get_file_mode())
2384
2385
2386class Converter4to5(object):
2387    """Perform an in-place upgrade of format 4 to format 5 trees."""
2388
2389    def __init__(self):
2390        self.target_format = WorkingTreeFormat5()
2391
2392    def convert(self, tree):
2393        # lock the control files not the tree, so that we don't get tree
2394        # on-unlock behaviours, and so that no-one else diddles with the
2395        # tree during upgrade.
2396        tree._control_files.lock_write()
2397        try:
2398            self.update_format(tree)
2399        finally:
2400            tree._control_files.unlock()
2401
2402    def update_format(self, tree):
2403        """Change the format marker."""
2404        tree._transport.put_bytes('format',
2405                                  self.target_format.as_string(),
2406                                  mode=tree.controldir._get_file_mode())
2407
2408
2409class Converter4or5to6(object):
2410    """Perform an in-place upgrade of format 4 or 5 to format 6 trees."""
2411
2412    def __init__(self):
2413        self.target_format = WorkingTreeFormat6()
2414
2415    def convert(self, tree):
2416        # lock the control files not the tree, so that we don't get tree
2417        # on-unlock behaviours, and so that no-one else diddles with the
2418        # tree during upgrade.
2419        tree._control_files.lock_write()
2420        try:
2421            self.init_custom_control_files(tree)
2422            self.update_format(tree)
2423        finally:
2424            tree._control_files.unlock()
2425
2426    def init_custom_control_files(self, tree):
2427        """Initialize custom control files."""
2428        tree._transport.put_bytes('views', b'',
2429                                  mode=tree.controldir._get_file_mode())
2430
2431    def update_format(self, tree):
2432        """Change the format marker."""
2433        tree._transport.put_bytes('format',
2434                                  self.target_format.as_string(),
2435                                  mode=tree.controldir._get_file_mode())
2436