/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-22 14:12:22 UTC
  • mfrom: (5809 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110422141222-nx2j0hbkihcb8j16
Merge newer bzr.dev and resolve conflicts.
Try to write some documentation about how the _dirblock_state works.
Fix up the tests so that they pass again.

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