~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-20 14:15:25 UTC
  • mto: (6471.1.4 iter-child-entries)
  • mto: This revision was merged to the branch mainline in revision 6472.
  • Revision ID: jelmer@samba.org-20120220141525-9azkfei62st8yc7w
Use inventories directly in fewer places.

Show diffs side-by-side

added added

removed removed

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