~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Patch Queue Manager
  • Date: 2011-12-21 21:56:31 UTC
  • mfrom: (6393.2.4 907268-bazaar-DEFAULT)
  • Revision ID: pqm@pqm.ubuntu.com-20111221215631-coxvalt3phw8jaza
(vila) bzr config displays [DEFAULT] for bazaar.conf (Vincent Ladeuil)

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