~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-10-31 04:39:04 UTC
  • mfrom: (3565.6.16 switch_nick)
  • Revision ID: pqm@pqm.ubuntu.com-20081031043904-52fnbfrloojemvcc
(mbp) branch nickname documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
from bisect import bisect_left
 
32
import collections
 
33
from copy import deepcopy
 
34
import errno
 
35
import itertools
 
36
import operator
 
37
import stat
 
38
from time import time
 
39
import warnings
 
40
 
 
41
import bzrlib
 
42
from bzrlib import (
 
43
    bzrdir,
 
44
    cache_utf8,
 
45
    conflicts as _mod_conflicts,
 
46
    debug,
 
47
    delta,
 
48
    dirstate,
 
49
    errors,
 
50
    generate_ids,
 
51
    globbing,
 
52
    ignores,
 
53
    merge,
 
54
    osutils,
 
55
    revision as _mod_revision,
 
56
    revisiontree,
 
57
    textui,
 
58
    trace,
 
59
    transform,
 
60
    urlutils,
 
61
    xml5,
 
62
    xml6,
 
63
    )
 
64
import bzrlib.branch
 
65
from bzrlib.transport import get_transport
 
66
import bzrlib.ui
 
67
""")
 
68
 
 
69
from bzrlib import symbol_versioning
 
70
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
71
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
 
72
from bzrlib.lockable_files import LockableFiles, TransportLock
 
73
from bzrlib.lockdir import LockDir
 
74
import bzrlib.mutabletree
 
75
from bzrlib.mutabletree import needs_tree_write_lock
 
76
from bzrlib.osutils import (
 
77
    file_kind,
 
78
    isdir,
 
79
    normpath,
 
80
    pathjoin,
 
81
    rand_chars,
 
82
    realpath,
 
83
    safe_unicode,
 
84
    splitpath,
 
85
    )
 
86
from bzrlib.trace import mutter, note
 
87
from bzrlib.transport.local import LocalTransport
 
88
from bzrlib.tree import InterTree
 
89
from bzrlib.progress import DummyProgress, ProgressPhase
 
90
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
 
91
from bzrlib.rio import RioReader, rio_file, Stanza
 
92
from bzrlib.symbol_versioning import (deprecated_passed,
 
93
        deprecated_method,
 
94
        deprecated_function,
 
95
        DEPRECATED_PARAMETER,
 
96
        )
 
97
from bzrlib.tree import Tree
 
98
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
 
99
 
 
100
 
 
101
class WorkingTree4(WorkingTree3):
 
102
    """This is the Format 4 working tree.
 
103
 
 
104
    This differs from WorkingTree3 by:
 
105
     - Having a consolidated internal dirstate, stored in a
 
106
       randomly-accessible sorted file on disk.
 
107
     - Not having a regular inventory attribute.  One can be synthesized 
 
108
       on demand but this is expensive and should be avoided.
 
109
 
 
110
    This is new in bzr 0.15.
 
111
    """
 
112
 
 
113
    def __init__(self, basedir,
 
114
                 branch,
 
115
                 _control_files=None,
 
116
                 _format=None,
 
117
                 _bzrdir=None):
 
118
        """Construct a WorkingTree for basedir.
 
119
 
 
120
        If the branch is not supplied, it is opened automatically.
 
121
        If the branch is supplied, it must be the branch for this basedir.
 
122
        (branch.base is not cross checked, because for remote branches that
 
123
        would be meaningless).
 
124
        """
 
125
        self._format = _format
 
126
        self.bzrdir = _bzrdir
 
127
        basedir = safe_unicode(basedir)
 
128
        mutter("opening working tree %r", basedir)
 
129
        self._branch = branch
 
130
        self.basedir = realpath(basedir)
 
131
        # if branch is at our basedir and is a format 6 or less
 
132
        # assume all other formats have their own control files.
 
133
        self._control_files = _control_files
 
134
        self._transport = self._control_files._transport
 
135
        self._dirty = None
 
136
        #-------------
 
137
        # during a read or write lock these objects are set, and are
 
138
        # None the rest of the time.
 
139
        self._dirstate = None
 
140
        self._inventory = None
 
141
        #-------------
 
142
        self._setup_directory_is_tree_reference()
 
143
        self._detect_case_handling()
 
144
        self._rules_searcher = None
 
145
        #--- allow tests to select the dirstate iter_changes implementation
 
146
        self._iter_changes = dirstate._process_entry
 
147
 
 
148
    @needs_tree_write_lock
 
149
    def _add(self, files, ids, kinds):
 
150
        """See MutableTree._add."""
 
151
        state = self.current_dirstate()
 
152
        for f, file_id, kind in zip(files, ids, kinds):
 
153
            f = f.strip('/')
 
154
            if self.path2id(f):
 
155
                # special case tree root handling.
 
156
                if f == '' and self.path2id(f) == ROOT_ID:
 
157
                    state.set_path_id('', generate_ids.gen_file_id(f))
 
158
                continue
 
159
            if file_id is None:
 
160
                file_id = generate_ids.gen_file_id(f)
 
161
            # deliberately add the file with no cached stat or sha1
 
162
            # - on the first access it will be gathered, and we can
 
163
            # always change this once tests are all passing.
 
164
            state.add(f, file_id, kind, None, '')
 
165
        self._make_dirty(reset_inventory=True)
 
166
 
 
167
    def _make_dirty(self, reset_inventory):
 
168
        """Make the tree state dirty.
 
169
 
 
170
        :param reset_inventory: True if the cached inventory should be removed
 
171
            (presuming there is one).
 
172
        """
 
173
        self._dirty = True
 
174
        if reset_inventory and self._inventory is not None:
 
175
            self._inventory = None
 
176
 
 
177
    @needs_tree_write_lock
 
178
    def add_reference(self, sub_tree):
 
179
        # use standard implementation, which calls back to self._add
 
180
        # 
 
181
        # So we don't store the reference_revision in the working dirstate,
 
182
        # it's just recorded at the moment of commit. 
 
183
        self._add_reference(sub_tree)
 
184
 
 
185
    def break_lock(self):
 
186
        """Break a lock if one is present from another instance.
 
187
 
 
188
        Uses the ui factory to ask for confirmation if the lock may be from
 
189
        an active process.
 
190
 
 
191
        This will probe the repository for its lock as well.
 
192
        """
 
193
        # if the dirstate is locked by an active process, reject the break lock
 
194
        # call.
 
195
        try:
 
196
            if self._dirstate is None:
 
197
                clear = True
 
198
            else:
 
199
                clear = False
 
200
            state = self._current_dirstate()
 
201
            if state._lock_token is not None:
 
202
                # we already have it locked. sheese, cant break our own lock.
 
203
                raise errors.LockActive(self.basedir)
 
204
            else:
 
205
                try:
 
206
                    # try for a write lock - need permission to get one anyhow
 
207
                    # to break locks.
 
208
                    state.lock_write()
 
209
                except errors.LockContention:
 
210
                    # oslocks fail when a process is still live: fail.
 
211
                    # TODO: get the locked lockdir info and give to the user to
 
212
                    # assist in debugging.
 
213
                    raise errors.LockActive(self.basedir)
 
214
                else:
 
215
                    state.unlock()
 
216
        finally:
 
217
            if clear:
 
218
                self._dirstate = None
 
219
        self._control_files.break_lock()
 
220
        self.branch.break_lock()
 
221
 
 
222
    def _comparison_data(self, entry, path):
 
223
        kind, executable, stat_value = \
 
224
            WorkingTree3._comparison_data(self, entry, path)
 
225
        # it looks like a plain directory, but it's really a reference -- see
 
226
        # also kind()
 
227
        if (self._repo_supports_tree_reference and
 
228
            kind == 'directory' and
 
229
            self._directory_is_tree_reference(path)):
 
230
            kind = 'tree-reference'
 
231
        return kind, executable, stat_value
 
232
 
 
233
    @needs_write_lock
 
234
    def commit(self, message=None, revprops=None, *args, **kwargs):
 
235
        # mark the tree as dirty post commit - commit
 
236
        # can change the current versioned list by doing deletes.
 
237
        result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
 
238
        self._make_dirty(reset_inventory=True)
 
239
        return result
 
240
 
 
241
    def current_dirstate(self):
 
242
        """Return the current dirstate object.
 
243
 
 
244
        This is not part of the tree interface and only exposed for ease of
 
245
        testing.
 
246
 
 
247
        :raises errors.NotWriteLocked: when not in a lock.
 
248
        """
 
249
        self._must_be_locked()
 
250
        return self._current_dirstate()
 
251
 
 
252
    def _current_dirstate(self):
 
253
        """Internal function that does not check lock status.
 
254
 
 
255
        This is needed for break_lock which also needs the dirstate.
 
256
        """
 
257
        if self._dirstate is not None:
 
258
            return self._dirstate
 
259
        local_path = self.bzrdir.get_workingtree_transport(None
 
260
            ).local_abspath('dirstate')
 
261
        self._dirstate = dirstate.DirState.on_file(local_path)
 
262
        return self._dirstate
 
263
 
 
264
    def filter_unversioned_files(self, paths):
 
265
        """Filter out paths that are versioned.
 
266
 
 
267
        :return: set of paths.
 
268
        """
 
269
        # TODO: make a generic multi-bisect routine roughly that should list
 
270
        # the paths, then process one half at a time recursively, and feed the
 
271
        # results of each bisect in further still
 
272
        paths = sorted(paths)
 
273
        result = set()
 
274
        state = self.current_dirstate()
 
275
        # TODO we want a paths_to_dirblocks helper I think
 
276
        for path in paths:
 
277
            dirname, basename = os.path.split(path.encode('utf8'))
 
278
            _, _, _, path_is_versioned = state._get_block_entry_index(
 
279
                dirname, basename, 0)
 
280
            if not path_is_versioned:
 
281
                result.add(path)
 
282
        return result
 
283
 
 
284
    def flush(self):
 
285
        """Write all cached data to disk."""
 
286
        if self._control_files._lock_mode != 'w':
 
287
            raise errors.NotWriteLocked(self)
 
288
        self.current_dirstate().save()
 
289
        self._inventory = None
 
290
        self._dirty = False
 
291
 
 
292
    @needs_tree_write_lock
 
293
    def _gather_kinds(self, files, kinds):
 
294
        """See MutableTree._gather_kinds."""
 
295
        for pos, f in enumerate(files):
 
296
            if kinds[pos] is None:
 
297
                kinds[pos] = self._kind(f)
 
298
 
 
299
    def _generate_inventory(self):
 
300
        """Create and set self.inventory from the dirstate object.
 
301
        
 
302
        This is relatively expensive: we have to walk the entire dirstate.
 
303
        Ideally we would not, and can deprecate this function.
 
304
        """
 
305
        #: uncomment to trap on inventory requests.
 
306
        # import pdb;pdb.set_trace()
 
307
        state = self.current_dirstate()
 
308
        state._read_dirblocks_if_needed()
 
309
        root_key, current_entry = self._get_entry(path='')
 
310
        current_id = root_key[2]
 
311
        if not (current_entry[0][0] == 'd'): # directory
 
312
            raise AssertionError(current_entry)
 
313
        inv = Inventory(root_id=current_id)
 
314
        # Turn some things into local variables
 
315
        minikind_to_kind = dirstate.DirState._minikind_to_kind
 
316
        factory = entry_factory
 
317
        utf8_decode = cache_utf8._utf8_decode
 
318
        inv_byid = inv._byid
 
319
        # we could do this straight out of the dirstate; it might be fast
 
320
        # and should be profiled - RBC 20070216
 
321
        parent_ies = {'' : inv.root}
 
322
        for block in state._dirblocks[1:]: # skip the root
 
323
            dirname = block[0]
 
324
            try:
 
325
                parent_ie = parent_ies[dirname]
 
326
            except KeyError:
 
327
                # all the paths in this block are not versioned in this tree
 
328
                continue
 
329
            for key, entry in block[1]:
 
330
                minikind, link_or_sha1, size, executable, stat = entry[0]
 
331
                if minikind in ('a', 'r'): # absent, relocated
 
332
                    # a parent tree only entry
 
333
                    continue
 
334
                name = key[1]
 
335
                name_unicode = utf8_decode(name)[0]
 
336
                file_id = key[2]
 
337
                kind = minikind_to_kind[minikind]
 
338
                inv_entry = factory[kind](file_id, name_unicode,
 
339
                                          parent_ie.file_id)
 
340
                if kind == 'file':
 
341
                    # This is only needed on win32, where this is the only way
 
342
                    # we know the executable bit.
 
343
                    inv_entry.executable = executable
 
344
                    # not strictly needed: working tree
 
345
                    #inv_entry.text_size = size
 
346
                    #inv_entry.text_sha1 = sha1
 
347
                elif kind == 'directory':
 
348
                    # add this entry to the parent map.
 
349
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
 
350
                elif kind == 'tree-reference':
 
351
                    if not self._repo_supports_tree_reference:
 
352
                        raise AssertionError(
 
353
                            "repository of %r "
 
354
                            "doesn't support tree references "
 
355
                            "required by entry %r"
 
356
                            % (self, name))
 
357
                    inv_entry.reference_revision = link_or_sha1 or None
 
358
                elif kind != 'symlink':
 
359
                    raise AssertionError("unknown kind %r" % kind)
 
360
                # These checks cost us around 40ms on a 55k entry tree
 
361
                if file_id in inv_byid:
 
362
                    raise AssertionError('file_id %s already in'
 
363
                        ' inventory as %s' % (file_id, inv_byid[file_id]))
 
364
                if name_unicode in parent_ie.children:
 
365
                    raise AssertionError('name %r already in parent'
 
366
                        % (name_unicode,))
 
367
                inv_byid[file_id] = inv_entry
 
368
                parent_ie.children[name_unicode] = inv_entry
 
369
        self._inventory = inv
 
370
 
 
371
    def _get_entry(self, file_id=None, path=None):
 
372
        """Get the dirstate row for file_id or path.
 
373
 
 
374
        If either file_id or path is supplied, it is used as the key to lookup.
 
375
        If both are supplied, the fastest lookup is used, and an error is
 
376
        raised if they do not both point at the same row.
 
377
        
 
378
        :param file_id: An optional unicode file_id to be looked up.
 
379
        :param path: An optional unicode path to be looked up.
 
380
        :return: The dirstate row tuple for path/file_id, or (None, None)
 
381
        """
 
382
        if file_id is None and path is None:
 
383
            raise errors.BzrError('must supply file_id or path')
 
384
        state = self.current_dirstate()
 
385
        if path is not None:
 
386
            path = path.encode('utf8')
 
387
        return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
 
388
 
 
389
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
390
        # check file id is valid unconditionally.
 
391
        entry = self._get_entry(file_id=file_id, path=path)
 
392
        if entry[0] is None:
 
393
            raise errors.NoSuchId(self, file_id)
 
394
        if path is None:
 
395
            path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
 
396
 
 
397
        file_abspath = self.abspath(path)
 
398
        state = self.current_dirstate()
 
399
        if stat_value is None:
 
400
            try:
 
401
                stat_value = os.lstat(file_abspath)
 
402
            except OSError, e:
 
403
                if e.errno == errno.ENOENT:
 
404
                    return None
 
405
                else:
 
406
                    raise
 
407
        link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
 
408
            stat_value=stat_value)
 
409
        if entry[1][0][0] == 'f':
 
410
            if link_or_sha1 is None:
 
411
                file_obj, statvalue = self.get_file_with_stat(file_id, path)
 
412
                try:
 
413
                    sha1 = osutils.sha_file(file_obj)
 
414
                finally:
 
415
                    file_obj.close()
 
416
                self._observed_sha1(file_id, path, (sha1, statvalue))
 
417
                return sha1
 
418
            else:
 
419
                return link_or_sha1
 
420
        return None
 
421
 
 
422
    def _get_inventory(self):
 
423
        """Get the inventory for the tree. This is only valid within a lock."""
 
424
        if 'evil' in debug.debug_flags:
 
425
            trace.mutter_callsite(2,
 
426
                "accessing .inventory forces a size of tree translation.")
 
427
        if self._inventory is not None:
 
428
            return self._inventory
 
429
        self._must_be_locked()
 
430
        self._generate_inventory()
 
431
        return self._inventory
 
432
 
 
433
    inventory = property(_get_inventory,
 
434
                         doc="Inventory of this Tree")
 
435
 
 
436
    @needs_read_lock
 
437
    def get_parent_ids(self):
 
438
        """See Tree.get_parent_ids.
 
439
        
 
440
        This implementation requests the ids list from the dirstate file.
 
441
        """
 
442
        return self.current_dirstate().get_parent_ids()
 
443
 
 
444
    def get_reference_revision(self, file_id, path=None):
 
445
        # referenced tree's revision is whatever's currently there
 
446
        return self.get_nested_tree(file_id, path).last_revision()
 
447
 
 
448
    def get_nested_tree(self, file_id, path=None):
 
449
        if path is None:
 
450
            path = self.id2path(file_id)
 
451
        # else: check file_id is at path?
 
452
        return WorkingTree.open(self.abspath(path))
 
453
 
 
454
    @needs_read_lock
 
455
    def get_root_id(self):
 
456
        """Return the id of this trees root"""
 
457
        return self._get_entry(path='')[0][2]
 
458
 
 
459
    def has_id(self, file_id):
 
460
        state = self.current_dirstate()
 
461
        row, parents = self._get_entry(file_id=file_id)
 
462
        if row is None:
 
463
            return False
 
464
        return osutils.lexists(pathjoin(
 
465
                    self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
 
466
 
 
467
    @needs_read_lock
 
468
    def id2path(self, file_id):
 
469
        "Convert a file-id to a path."
 
470
        state = self.current_dirstate()
 
471
        entry = self._get_entry(file_id=file_id)
 
472
        if entry == (None, None):
 
473
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
474
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
 
475
        return path_utf8.decode('utf8')
 
476
 
 
477
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
 
478
        entry = self._get_entry(path=path)
 
479
        if entry == (None, None):
 
480
            return False # Missing entries are not executable
 
481
        return entry[1][0][3] # Executable?
 
482
 
 
483
    if not osutils.supports_executable():
 
484
        def is_executable(self, file_id, path=None):
 
485
            """Test if a file is executable or not.
 
486
 
 
487
            Note: The caller is expected to take a read-lock before calling this.
 
488
            """
 
489
            entry = self._get_entry(file_id=file_id, path=path)
 
490
            if entry == (None, None):
 
491
                return False
 
492
            return entry[1][0][3]
 
493
 
 
494
        _is_executable_from_path_and_stat = \
 
495
            _is_executable_from_path_and_stat_from_basis
 
496
    else:
 
497
        def is_executable(self, file_id, path=None):
 
498
            """Test if a file is executable or not.
 
499
 
 
500
            Note: The caller is expected to take a read-lock before calling this.
 
501
            """
 
502
            self._must_be_locked()
 
503
            if not path:
 
504
                path = self.id2path(file_id)
 
505
            mode = os.lstat(self.abspath(path)).st_mode
 
506
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
507
 
 
508
    def all_file_ids(self):
 
509
        """See Tree.iter_all_file_ids"""
 
510
        self._must_be_locked()
 
511
        result = set()
 
512
        for key, tree_details in self.current_dirstate()._iter_entries():
 
513
            if tree_details[0][0] in ('a', 'r'): # relocated
 
514
                continue
 
515
            result.add(key[2])
 
516
        return result
 
517
 
 
518
    @needs_read_lock
 
519
    def __iter__(self):
 
520
        """Iterate through file_ids for this tree.
 
521
 
 
522
        file_ids are in a WorkingTree if they are in the working inventory
 
523
        and the working file exists.
 
524
        """
 
525
        result = []
 
526
        for key, tree_details in self.current_dirstate()._iter_entries():
 
527
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
 
528
                # not relevant to the working tree
 
529
                continue
 
530
            path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
 
531
            if osutils.lexists(path):
 
532
                result.append(key[2])
 
533
        return iter(result)
 
534
 
 
535
    def iter_references(self):
 
536
        if not self._repo_supports_tree_reference:
 
537
            # When the repo doesn't support references, we will have nothing to
 
538
            # return
 
539
            return
 
540
        for key, tree_details in self.current_dirstate()._iter_entries():
 
541
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
 
542
                # not relevant to the working tree
 
543
                continue
 
544
            if not key[1]:
 
545
                # the root is not a reference.
 
546
                continue
 
547
            relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
 
548
            try:
 
549
                if self._kind(relpath) == 'tree-reference':
 
550
                    yield relpath, key[2]
 
551
            except errors.NoSuchFile:
 
552
                # path is missing on disk.
 
553
                continue
 
554
 
 
555
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
 
556
        """See MutableTree._observed_sha1."""
 
557
        state = self.current_dirstate()
 
558
        entry = self._get_entry(file_id=file_id, path=path)
 
559
        state._observed_sha1(entry, sha1, statvalue)
 
560
 
 
561
    def kind(self, file_id):
 
562
        """Return the kind of a file.
 
563
 
 
564
        This is always the actual kind that's on disk, regardless of what it
 
565
        was added as.
 
566
 
 
567
        Note: The caller is expected to take a read-lock before calling this.
 
568
        """
 
569
        relpath = self.id2path(file_id)
 
570
        if relpath is None:
 
571
            raise AssertionError(
 
572
                "path for id {%s} is None!" % file_id)
 
573
        return self._kind(relpath)
 
574
 
 
575
    def _kind(self, relpath):
 
576
        abspath = self.abspath(relpath)
 
577
        kind = file_kind(abspath)
 
578
        if (self._repo_supports_tree_reference and
 
579
            kind == 'directory' and
 
580
            self._directory_is_tree_reference(relpath)):
 
581
            kind = 'tree-reference'
 
582
        return kind
 
583
 
 
584
    @needs_read_lock
 
585
    def _last_revision(self):
 
586
        """See Mutable.last_revision."""
 
587
        parent_ids = self.current_dirstate().get_parent_ids()
 
588
        if parent_ids:
 
589
            return parent_ids[0]
 
590
        else:
 
591
            return _mod_revision.NULL_REVISION
 
592
 
 
593
    def lock_read(self):
 
594
        """See Branch.lock_read, and WorkingTree.unlock."""
 
595
        self.branch.lock_read()
 
596
        try:
 
597
            self._control_files.lock_read()
 
598
            try:
 
599
                state = self.current_dirstate()
 
600
                if not state._lock_token:
 
601
                    state.lock_read()
 
602
                # set our support for tree references from the repository in
 
603
                # use.
 
604
                self._repo_supports_tree_reference = getattr(
 
605
                    self.branch.repository._format, "supports_tree_reference",
 
606
                    False)
 
607
            except:
 
608
                self._control_files.unlock()
 
609
                raise
 
610
        except:
 
611
            self.branch.unlock()
 
612
            raise
 
613
 
 
614
    def _lock_self_write(self):
 
615
        """This should be called after the branch is locked."""
 
616
        try:
 
617
            self._control_files.lock_write()
 
618
            try:
 
619
                state = self.current_dirstate()
 
620
                if not state._lock_token:
 
621
                    state.lock_write()
 
622
                # set our support for tree references from the repository in
 
623
                # use.
 
624
                self._repo_supports_tree_reference = getattr(
 
625
                    self.branch.repository._format, "supports_tree_reference",
 
626
                    False)
 
627
            except:
 
628
                self._control_files.unlock()
 
629
                raise
 
630
        except:
 
631
            self.branch.unlock()
 
632
            raise
 
633
 
 
634
    def lock_tree_write(self):
 
635
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
636
        self.branch.lock_read()
 
637
        self._lock_self_write()
 
638
 
 
639
    def lock_write(self):
 
640
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
641
        self.branch.lock_write()
 
642
        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=str(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._dirblock_state = dirstate.DirState.IN_MEMORY_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(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
 
900
        search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
 
901
        # -- make all paths utf8 --
 
902
        paths_utf8 = set()
 
903
        for path in paths:
 
904
            paths_utf8.add(path.encode('utf8'))
 
905
        paths = paths_utf8
 
906
        # -- paths is now a utf8 path set --
 
907
        # -- get the state object and prepare it.
 
908
        state = self.current_dirstate()
 
909
        if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
 
910
            and '' not in paths):
 
911
            paths2ids = self._paths2ids_using_bisect
 
912
        else:
 
913
            paths2ids = self._paths2ids_in_memory
 
914
        return paths2ids(paths, search_indexes,
 
915
                         require_versioned=require_versioned)
 
916
 
 
917
    def _paths2ids_in_memory(self, paths, search_indexes,
 
918
                             require_versioned=True):
 
919
        state = self.current_dirstate()
 
920
        state._read_dirblocks_if_needed()
 
921
        def _entries_for_path(path):
 
922
            """Return a list with all the entries that match path for all ids.
 
923
            """
 
924
            dirname, basename = os.path.split(path)
 
925
            key = (dirname, basename, '')
 
926
            block_index, present = state._find_block_index_from_key(key)
 
927
            if not present:
 
928
                # the block which should contain path is absent.
 
929
                return []
 
930
            result = []
 
931
            block = state._dirblocks[block_index][1]
 
932
            entry_index, _ = state._find_entry_index(key, block)
 
933
            # we may need to look at multiple entries at this path: walk while the paths match.
 
934
            while (entry_index < len(block) and
 
935
                block[entry_index][0][0:2] == key[0:2]):
 
936
                result.append(block[entry_index])
 
937
                entry_index += 1
 
938
            return result
 
939
        if require_versioned:
 
940
            # -- check all supplied paths are versioned in a search tree. --
 
941
            all_versioned = True
 
942
            for path in paths:
 
943
                path_entries = _entries_for_path(path)
 
944
                if not path_entries:
 
945
                    # this specified path is not present at all: error
 
946
                    all_versioned = False
 
947
                    break
 
948
                found_versioned = False
 
949
                # for each id at this path
 
950
                for entry in path_entries:
 
951
                    # for each tree.
 
952
                    for index in search_indexes:
 
953
                        if entry[1][index][0] != 'a': # absent
 
954
                            found_versioned = True
 
955
                            # all good: found a versioned cell
 
956
                            break
 
957
                if not found_versioned:
 
958
                    # none of the indexes was not 'absent' at all ids for this
 
959
                    # path.
 
960
                    all_versioned = False
 
961
                    break
 
962
            if not all_versioned:
 
963
                raise errors.PathsNotVersionedError(paths)
 
964
        # -- remove redundancy in supplied paths to prevent over-scanning --
 
965
        search_paths = osutils.minimum_path_selection(paths)
 
966
        # sketch: 
 
967
        # for all search_indexs in each path at or under each element of
 
968
        # search_paths, if the detail is relocated: add the id, and add the
 
969
        # relocated path as one to search if its not searched already. If the
 
970
        # detail is not relocated, add the id.
 
971
        searched_paths = set()
 
972
        found_ids = set()
 
973
        def _process_entry(entry):
 
974
            """Look at search_indexes within entry.
 
975
 
 
976
            If a specific tree's details are relocated, add the relocation
 
977
            target to search_paths if not searched already. If it is absent, do
 
978
            nothing. Otherwise add the id to found_ids.
 
979
            """
 
980
            for index in search_indexes:
 
981
                if entry[1][index][0] == 'r': # relocated
 
982
                    if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
 
983
                        search_paths.add(entry[1][index][1])
 
984
                elif entry[1][index][0] != 'a': # absent
 
985
                    found_ids.add(entry[0][2])
 
986
        while search_paths:
 
987
            current_root = search_paths.pop()
 
988
            searched_paths.add(current_root)
 
989
            # process the entries for this containing directory: the rest will be
 
990
            # found by their parents recursively.
 
991
            root_entries = _entries_for_path(current_root)
 
992
            if not root_entries:
 
993
                # this specified path is not present at all, skip it.
 
994
                continue
 
995
            for entry in root_entries:
 
996
                _process_entry(entry)
 
997
            initial_key = (current_root, '', '')
 
998
            block_index, _ = state._find_block_index_from_key(initial_key)
 
999
            while (block_index < len(state._dirblocks) and
 
1000
                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
 
1001
                for entry in state._dirblocks[block_index][1]:
 
1002
                    _process_entry(entry)
 
1003
                block_index += 1
 
1004
        return found_ids
 
1005
 
 
1006
    def _paths2ids_using_bisect(self, paths, search_indexes,
 
1007
                                require_versioned=True):
 
1008
        state = self.current_dirstate()
 
1009
        found_ids = set()
 
1010
 
 
1011
        split_paths = sorted(osutils.split(p) for p in paths)
 
1012
        found = state._bisect_recursive(split_paths)
 
1013
 
 
1014
        if require_versioned:
 
1015
            found_dir_names = set(dir_name_id[:2] for dir_name_id in found)
 
1016
            for dir_name in split_paths:
 
1017
                if dir_name not in found_dir_names:
 
1018
                    raise errors.PathsNotVersionedError(paths)
 
1019
 
 
1020
        for dir_name_id, trees_info in found.iteritems():
 
1021
            for index in search_indexes:
 
1022
                if trees_info[index][0] not in ('r', 'a'):
 
1023
                    found_ids.add(dir_name_id[2])
 
1024
        return found_ids
 
1025
 
 
1026
    def read_working_inventory(self):
 
1027
        """Read the working inventory.
 
1028
        
 
1029
        This is a meaningless operation for dirstate, but we obey it anyhow.
 
1030
        """
 
1031
        return self.inventory
 
1032
 
 
1033
    @needs_read_lock
 
1034
    def revision_tree(self, revision_id):
 
1035
        """See Tree.revision_tree.
 
1036
 
 
1037
        WorkingTree4 supplies revision_trees for any basis tree.
 
1038
        """
 
1039
        dirstate = self.current_dirstate()
 
1040
        parent_ids = dirstate.get_parent_ids()
 
1041
        if revision_id not in parent_ids:
 
1042
            raise errors.NoSuchRevisionInTree(self, revision_id)
 
1043
        if revision_id in dirstate.get_ghosts():
 
1044
            raise errors.NoSuchRevisionInTree(self, revision_id)
 
1045
        return DirStateRevisionTree(dirstate, revision_id,
 
1046
            self.branch.repository)
 
1047
 
 
1048
    @needs_tree_write_lock
 
1049
    def set_last_revision(self, new_revision):
 
1050
        """Change the last revision in the working tree."""
 
1051
        parents = self.get_parent_ids()
 
1052
        if new_revision in (NULL_REVISION, None):
 
1053
            if len(parents) >= 2:
 
1054
                raise AssertionError(
 
1055
                    "setting the last parent to none with a pending merge is "
 
1056
                    "unsupported.")
 
1057
            self.set_parent_ids([])
 
1058
        else:
 
1059
            self.set_parent_ids([new_revision] + parents[1:],
 
1060
                allow_leftmost_as_ghost=True)
 
1061
 
 
1062
    @needs_tree_write_lock
 
1063
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
 
1064
        """Set the parent ids to revision_ids.
 
1065
        
 
1066
        See also set_parent_trees. This api will try to retrieve the tree data
 
1067
        for each element of revision_ids from the trees repository. If you have
 
1068
        tree data already available, it is more efficient to use
 
1069
        set_parent_trees rather than set_parent_ids. set_parent_ids is however
 
1070
        an easier API to use.
 
1071
 
 
1072
        :param revision_ids: The revision_ids to set as the parent ids of this
 
1073
            working tree. Any of these may be ghosts.
 
1074
        """
 
1075
        trees = []
 
1076
        for revision_id in revision_ids:
 
1077
            try:
 
1078
                revtree = self.branch.repository.revision_tree(revision_id)
 
1079
                # TODO: jam 20070213 KnitVersionedFile raises
 
1080
                #       RevisionNotPresent rather than NoSuchRevision if a
 
1081
                #       given revision_id is not present. Should Repository be
 
1082
                #       catching it and re-raising NoSuchRevision?
 
1083
            except (errors.NoSuchRevision, errors.RevisionNotPresent):
 
1084
                revtree = None
 
1085
            trees.append((revision_id, revtree))
 
1086
        self.set_parent_trees(trees,
 
1087
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
1088
 
 
1089
    @needs_tree_write_lock
 
1090
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
 
1091
        """Set the parents of the working tree.
 
1092
 
 
1093
        :param parents_list: A list of (revision_id, tree) tuples.
 
1094
            If tree is None, then that element is treated as an unreachable
 
1095
            parent tree - i.e. a ghost.
 
1096
        """
 
1097
        dirstate = self.current_dirstate()
 
1098
        if len(parents_list) > 0:
 
1099
            if not allow_leftmost_as_ghost and parents_list[0][1] is None:
 
1100
                raise errors.GhostRevisionUnusableHere(parents_list[0][0])
 
1101
        real_trees = []
 
1102
        ghosts = []
 
1103
 
 
1104
        parent_ids = [rev_id for rev_id, tree in parents_list]
 
1105
        graph = self.branch.repository.get_graph()
 
1106
        heads = graph.heads(parent_ids)
 
1107
        accepted_revisions = set()
 
1108
 
 
1109
        # convert absent trees to the null tree, which we convert back to
 
1110
        # missing on access.
 
1111
        for rev_id, tree in parents_list:
 
1112
            if len(accepted_revisions) > 0:
 
1113
                # we always accept the first tree
 
1114
                if rev_id in accepted_revisions or rev_id not in heads:
 
1115
                    # We have already included either this tree, or its
 
1116
                    # descendent, so we skip it.
 
1117
                    continue
 
1118
            _mod_revision.check_not_reserved_id(rev_id)
 
1119
            if tree is not None:
 
1120
                real_trees.append((rev_id, tree))
 
1121
            else:
 
1122
                real_trees.append((rev_id,
 
1123
                    self.branch.repository.revision_tree(
 
1124
                        _mod_revision.NULL_REVISION)))
 
1125
                ghosts.append(rev_id)
 
1126
            accepted_revisions.add(rev_id)
 
1127
        dirstate.set_parent_trees(real_trees, ghosts=ghosts)
 
1128
        self._make_dirty(reset_inventory=False)
 
1129
 
 
1130
    def _set_root_id(self, file_id):
 
1131
        """See WorkingTree.set_root_id."""
 
1132
        state = self.current_dirstate()
 
1133
        state.set_path_id('', file_id)
 
1134
        if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
 
1135
            self._make_dirty(reset_inventory=True)
 
1136
 
 
1137
    def _sha_from_stat(self, path, stat_result):
 
1138
        """Get a sha digest from the tree's stat cache.
 
1139
 
 
1140
        The default implementation assumes no stat cache is present.
 
1141
 
 
1142
        :param path: The path.
 
1143
        :param stat_result: The stat result being looked up.
 
1144
        """
 
1145
        return self.current_dirstate().sha1_from_stat(path, stat_result)
 
1146
 
 
1147
    @needs_read_lock
 
1148
    def supports_tree_reference(self):
 
1149
        return self._repo_supports_tree_reference
 
1150
 
 
1151
    def unlock(self):
 
1152
        """Unlock in format 4 trees needs to write the entire dirstate."""
 
1153
        # do non-implementation specific cleanup
 
1154
        self._cleanup()
 
1155
 
 
1156
        if self._control_files._lock_count == 1:
 
1157
            # eventually we should do signature checking during read locks for
 
1158
            # dirstate updates.
 
1159
            if self._control_files._lock_mode == 'w':
 
1160
                if self._dirty:
 
1161
                    self.flush()
 
1162
            if self._dirstate is not None:
 
1163
                # This is a no-op if there are no modifications.
 
1164
                self._dirstate.save()
 
1165
                self._dirstate.unlock()
 
1166
            # TODO: jam 20070301 We shouldn't have to wipe the dirstate at this
 
1167
            #       point. Instead, it could check if the header has been
 
1168
            #       modified when it is locked, and if not, it can hang on to
 
1169
            #       the data it has in memory.
 
1170
            self._dirstate = None
 
1171
            self._inventory = None
 
1172
        # reverse order of locking.
 
1173
        try:
 
1174
            return self._control_files.unlock()
 
1175
        finally:
 
1176
            self.branch.unlock()
 
1177
 
 
1178
    @needs_tree_write_lock
 
1179
    def unversion(self, file_ids):
 
1180
        """Remove the file ids in file_ids from the current versioned set.
 
1181
 
 
1182
        When a file_id is unversioned, all of its children are automatically
 
1183
        unversioned.
 
1184
 
 
1185
        :param file_ids: The file ids to stop versioning.
 
1186
        :raises: NoSuchId if any fileid is not currently versioned.
 
1187
        """
 
1188
        if not file_ids:
 
1189
            return
 
1190
        state = self.current_dirstate()
 
1191
        state._read_dirblocks_if_needed()
 
1192
        ids_to_unversion = set(file_ids)
 
1193
        paths_to_unversion = set()
 
1194
        # sketch:
 
1195
        # check if the root is to be unversioned, if so, assert for now.
 
1196
        # walk the state marking unversioned things as absent.
 
1197
        # if there are any un-unversioned ids at the end, raise
 
1198
        for key, details in state._dirblocks[0][1]:
 
1199
            if (details[0][0] not in ('a', 'r') and # absent or relocated
 
1200
                key[2] in ids_to_unversion):
 
1201
                # I haven't written the code to unversion / yet - it should be
 
1202
                # supported.
 
1203
                raise errors.BzrError('Unversioning the / is not currently supported')
 
1204
        block_index = 0
 
1205
        while block_index < len(state._dirblocks):
 
1206
            # process one directory at a time.
 
1207
            block = state._dirblocks[block_index]
 
1208
            # first check: is the path one to remove - it or its children
 
1209
            delete_block = False
 
1210
            for path in paths_to_unversion:
 
1211
                if (block[0].startswith(path) and
 
1212
                    (len(block[0]) == len(path) or
 
1213
                     block[0][len(path)] == '/')):
 
1214
                    # this entire block should be deleted - its the block for a
 
1215
                    # path to unversion; or the child of one
 
1216
                    delete_block = True
 
1217
                    break
 
1218
            # TODO: trim paths_to_unversion as we pass by paths
 
1219
            if delete_block:
 
1220
                # this block is to be deleted: process it.
 
1221
                # TODO: we can special case the no-parents case and
 
1222
                # just forget the whole block.
 
1223
                entry_index = 0
 
1224
                while entry_index < len(block[1]):
 
1225
                    # Mark this file id as having been removed
 
1226
                    entry = block[1][entry_index]
 
1227
                    ids_to_unversion.discard(entry[0][2])
 
1228
                    if (entry[1][0][0] in 'ar' # don't remove absent or renamed
 
1229
                                               # entries
 
1230
                        or not state._make_absent(entry)):
 
1231
                        entry_index += 1
 
1232
                # go to the next block. (At the moment we dont delete empty
 
1233
                # dirblocks)
 
1234
                block_index += 1
 
1235
                continue
 
1236
            entry_index = 0
 
1237
            while entry_index < len(block[1]):
 
1238
                entry = block[1][entry_index]
 
1239
                if (entry[1][0][0] in ('a', 'r') or # absent, relocated
 
1240
                    # ^ some parent row.
 
1241
                    entry[0][2] not in ids_to_unversion):
 
1242
                    # ^ not an id to unversion
 
1243
                    entry_index += 1
 
1244
                    continue
 
1245
                if entry[1][0][0] == 'd':
 
1246
                    paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
 
1247
                if not state._make_absent(entry):
 
1248
                    entry_index += 1
 
1249
                # we have unversioned this id
 
1250
                ids_to_unversion.remove(entry[0][2])
 
1251
            block_index += 1
 
1252
        if ids_to_unversion:
 
1253
            raise errors.NoSuchId(self, iter(ids_to_unversion).next())
 
1254
        self._make_dirty(reset_inventory=False)
 
1255
        # have to change the legacy inventory too.
 
1256
        if self._inventory is not None:
 
1257
            for file_id in file_ids:
 
1258
                self._inventory.remove_recursive_id(file_id)
 
1259
 
 
1260
    @needs_tree_write_lock
 
1261
    def rename_one(self, from_rel, to_rel, after=False):
 
1262
        """See WorkingTree.rename_one"""
 
1263
        self.flush()
 
1264
        WorkingTree.rename_one(self, from_rel, to_rel, after)
 
1265
 
 
1266
    @needs_tree_write_lock
 
1267
    def apply_inventory_delta(self, changes):
 
1268
        """See MutableTree.apply_inventory_delta"""
 
1269
        state = self.current_dirstate()
 
1270
        state.update_by_delta(changes)
 
1271
        self._make_dirty(reset_inventory=True)
 
1272
 
 
1273
    def update_basis_by_delta(self, new_revid, delta):
 
1274
        """See MutableTree.update_basis_by_delta."""
 
1275
        if self.last_revision() == new_revid:
 
1276
            raise AssertionError()
 
1277
        self.current_dirstate().update_basis_by_delta(delta, new_revid)
 
1278
 
 
1279
    @needs_read_lock
 
1280
    def _validate(self):
 
1281
        self._dirstate._validate()
 
1282
 
 
1283
    @needs_tree_write_lock
 
1284
    def _write_inventory(self, inv):
 
1285
        """Write inventory as the current inventory."""
 
1286
        if self._dirty:
 
1287
            raise AssertionError("attempting to write an inventory when the "
 
1288
                "dirstate is dirty will lose pending changes")
 
1289
        self.current_dirstate().set_state_from_inventory(inv)
 
1290
        self._make_dirty(reset_inventory=False)
 
1291
        if self._inventory is not None:
 
1292
            self._inventory = inv
 
1293
        self.flush()
 
1294
 
 
1295
 
 
1296
class WorkingTreeFormat4(WorkingTreeFormat3):
 
1297
    """The first consolidated dirstate working tree format.
 
1298
 
 
1299
    This format:
 
1300
        - exists within a metadir controlling .bzr
 
1301
        - includes an explicit version marker for the workingtree control
 
1302
          files, separate from the BzrDir format
 
1303
        - modifies the hash cache format
 
1304
        - is new in bzr 0.15
 
1305
        - uses a LockDir to guard access to it.
 
1306
    """
 
1307
 
 
1308
    upgrade_recommended = False
 
1309
 
 
1310
    _tree_class = WorkingTree4
 
1311
 
 
1312
    def get_format_string(self):
 
1313
        """See WorkingTreeFormat.get_format_string()."""
 
1314
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
 
1315
 
 
1316
    def get_format_description(self):
 
1317
        """See WorkingTreeFormat.get_format_description()."""
 
1318
        return "Working tree format 4"
 
1319
 
 
1320
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
 
1321
                   accelerator_tree=None, hardlink=False):
 
1322
        """See WorkingTreeFormat.initialize().
 
1323
 
 
1324
        :param revision_id: allows creating a working tree at a different
 
1325
        revision than the branch is at.
 
1326
        :param accelerator_tree: A tree which can be used for retrieving file
 
1327
            contents more quickly than the revision tree, i.e. a workingtree.
 
1328
            The revision tree will be used for cases where accelerator_tree's
 
1329
            content is different.
 
1330
        :param hardlink: If true, hard-link files from accelerator_tree,
 
1331
            where possible.
 
1332
 
 
1333
        These trees get an initial random root id, if their repository supports
 
1334
        rich root data, TREE_ROOT otherwise.
 
1335
        """
 
1336
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1337
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1338
        transport = a_bzrdir.get_workingtree_transport(self)
 
1339
        control_files = self._open_control_files(a_bzrdir)
 
1340
        control_files.create_lock()
 
1341
        control_files.lock_write()
 
1342
        transport.put_bytes('format', self.get_format_string(),
 
1343
            mode=a_bzrdir._get_file_mode())
 
1344
        if from_branch is not None:
 
1345
            branch = from_branch
 
1346
        else:
 
1347
            branch = a_bzrdir.open_branch()
 
1348
        if revision_id is None:
 
1349
            revision_id = branch.last_revision()
 
1350
        local_path = transport.local_abspath('dirstate')
 
1351
        # write out new dirstate (must exist when we create the tree)
 
1352
        state = dirstate.DirState.initialize(local_path)
 
1353
        state.unlock()
 
1354
        del state
 
1355
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1356
                         branch,
 
1357
                         _format=self,
 
1358
                         _bzrdir=a_bzrdir,
 
1359
                         _control_files=control_files)
 
1360
        wt._new_tree()
 
1361
        wt.lock_tree_write()
 
1362
        try:
 
1363
            self._init_custom_control_files(wt)
 
1364
            if revision_id in (None, NULL_REVISION):
 
1365
                if branch.repository.supports_rich_root():
 
1366
                    wt._set_root_id(generate_ids.gen_root_id())
 
1367
                else:
 
1368
                    wt._set_root_id(ROOT_ID)
 
1369
                wt.flush()
 
1370
            basis = None
 
1371
            # frequently, we will get here due to branching.  The accelerator
 
1372
            # tree will be the tree from the branch, so the desired basis
 
1373
            # tree will often be a parent of the accelerator tree.
 
1374
            if accelerator_tree is not None:
 
1375
                try:
 
1376
                    basis = accelerator_tree.revision_tree(revision_id)
 
1377
                except errors.NoSuchRevision:
 
1378
                    pass
 
1379
            if basis is None:
 
1380
                basis = branch.repository.revision_tree(revision_id)
 
1381
            if revision_id == NULL_REVISION:
 
1382
                parents_list = []
 
1383
            else:
 
1384
                parents_list = [(revision_id, basis)]
 
1385
            basis.lock_read()
 
1386
            try:
 
1387
                wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
 
1388
                wt.flush()
 
1389
                # if the basis has a root id we have to use that; otherwise we
 
1390
                # use a new random one
 
1391
                basis_root_id = basis.get_root_id()
 
1392
                if basis_root_id is not None:
 
1393
                    wt._set_root_id(basis_root_id)
 
1394
                    wt.flush()
 
1395
                # delta_from_tree is safe even for DirStateRevisionTrees,
 
1396
                # because wt4.apply_inventory_delta does not mutate the input
 
1397
                # inventory entries.
 
1398
                transform.build_tree(basis, wt, accelerator_tree,
 
1399
                                     hardlink=hardlink, delta_from_tree=True)
 
1400
            finally:
 
1401
                basis.unlock()
 
1402
        finally:
 
1403
            control_files.unlock()
 
1404
            wt.unlock()
 
1405
        return wt
 
1406
 
 
1407
    def _init_custom_control_files(self, wt):
 
1408
        """Subclasses with custom control files should override this method.
 
1409
        
 
1410
        The working tree and control files are locked for writing when this
 
1411
        method is called.
 
1412
        
 
1413
        :param wt: the WorkingTree object
 
1414
        """
 
1415
 
 
1416
    def _open(self, a_bzrdir, control_files):
 
1417
        """Open the tree itself.
 
1418
 
 
1419
        :param a_bzrdir: the dir for the tree.
 
1420
        :param control_files: the control files for the tree.
 
1421
        """
 
1422
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1423
                           branch=a_bzrdir.open_branch(),
 
1424
                           _format=self,
 
1425
                           _bzrdir=a_bzrdir,
 
1426
                           _control_files=control_files)
 
1427
 
 
1428
    def __get_matchingbzrdir(self):
 
1429
        # please test against something that will let us do tree references
 
1430
        return bzrdir.format_registry.make_bzrdir(
 
1431
            'dirstate-with-subtree')
 
1432
 
 
1433
    _matchingbzrdir = property(__get_matchingbzrdir)
 
1434
 
 
1435
 
 
1436
class DirStateRevisionTree(Tree):
 
1437
    """A revision tree pulling the inventory from a dirstate."""
 
1438
 
 
1439
    def __init__(self, dirstate, revision_id, repository):
 
1440
        self._dirstate = dirstate
 
1441
        self._revision_id = revision_id
 
1442
        self._repository = repository
 
1443
        self._inventory = None
 
1444
        self._locked = 0
 
1445
        self._dirstate_locked = False
 
1446
        self._repo_supports_tree_reference = getattr(
 
1447
            repository._format, "supports_tree_reference",
 
1448
            False)
 
1449
 
 
1450
    def __repr__(self):
 
1451
        return "<%s of %s in %s>" % \
 
1452
            (self.__class__.__name__, self._revision_id, self._dirstate)
 
1453
 
 
1454
    def annotate_iter(self, file_id,
 
1455
                      default_revision=_mod_revision.CURRENT_REVISION):
 
1456
        """See Tree.annotate_iter"""
 
1457
        text_key = (file_id, self.inventory[file_id].revision)
 
1458
        annotations = self._repository.texts.annotate(text_key)
 
1459
        return [(key[-1], line) for (key, line) in annotations]
 
1460
 
 
1461
    def _get_ancestors(self, default_revision):
 
1462
        return set(self._repository.get_ancestry(self._revision_id,
 
1463
                                                 topo_sorted=False))
 
1464
    def _comparison_data(self, entry, path):
 
1465
        """See Tree._comparison_data."""
 
1466
        if entry is None:
 
1467
            return None, False, None
 
1468
        # trust the entry as RevisionTree does, but this may not be
 
1469
        # sensible: the entry might not have come from us?
 
1470
        return entry.kind, entry.executable, None
 
1471
 
 
1472
    def _file_size(self, entry, stat_value):
 
1473
        return entry.text_size
 
1474
 
 
1475
    def filter_unversioned_files(self, paths):
 
1476
        """Filter out paths that are not versioned.
 
1477
 
 
1478
        :return: set of paths.
 
1479
        """
 
1480
        pred = self.has_filename
 
1481
        return set((p for p in paths if not pred(p)))
 
1482
 
 
1483
    def get_root_id(self):
 
1484
        return self.path2id('')
 
1485
 
 
1486
    def id2path(self, file_id):
 
1487
        "Convert a file-id to a path."
 
1488
        entry = self._get_entry(file_id=file_id)
 
1489
        if entry == (None, None):
 
1490
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1491
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
 
1492
        return path_utf8.decode('utf8')
 
1493
 
 
1494
    def iter_references(self):
 
1495
        if not self._repo_supports_tree_reference:
 
1496
            # When the repo doesn't support references, we will have nothing to
 
1497
            # return
 
1498
            return iter([])
 
1499
        # Otherwise, fall back to the default implementation
 
1500
        return super(DirStateRevisionTree, self).iter_references()
 
1501
 
 
1502
    def _get_parent_index(self):
 
1503
        """Return the index in the dirstate referenced by this tree."""
 
1504
        return self._dirstate.get_parent_ids().index(self._revision_id) + 1
 
1505
 
 
1506
    def _get_entry(self, file_id=None, path=None):
 
1507
        """Get the dirstate row for file_id or path.
 
1508
 
 
1509
        If either file_id or path is supplied, it is used as the key to lookup.
 
1510
        If both are supplied, the fastest lookup is used, and an error is
 
1511
        raised if they do not both point at the same row.
 
1512
        
 
1513
        :param file_id: An optional unicode file_id to be looked up.
 
1514
        :param path: An optional unicode path to be looked up.
 
1515
        :return: The dirstate row tuple for path/file_id, or (None, None)
 
1516
        """
 
1517
        if file_id is None and path is None:
 
1518
            raise errors.BzrError('must supply file_id or path')
 
1519
        if path is not None:
 
1520
            path = path.encode('utf8')
 
1521
        parent_index = self._get_parent_index()
 
1522
        return self._dirstate._get_entry(parent_index, fileid_utf8=file_id, path_utf8=path)
 
1523
 
 
1524
    def _generate_inventory(self):
 
1525
        """Create and set self.inventory from the dirstate object.
 
1526
 
 
1527
        (So this is only called the first time the inventory is requested for
 
1528
        this tree; it then remains in memory until it's out of date.)
 
1529
 
 
1530
        This is relatively expensive: we have to walk the entire dirstate.
 
1531
        """
 
1532
        if not self._locked:
 
1533
            raise AssertionError(
 
1534
                'cannot generate inventory of an unlocked '
 
1535
                'dirstate revision tree')
 
1536
        # separate call for profiling - makes it clear where the costs are.
 
1537
        self._dirstate._read_dirblocks_if_needed()
 
1538
        if self._revision_id not in self._dirstate.get_parent_ids():
 
1539
            raise AssertionError(
 
1540
                'parent %s has disappeared from %s' % (
 
1541
                self._revision_id, self._dirstate.get_parent_ids()))
 
1542
        parent_index = self._dirstate.get_parent_ids().index(self._revision_id) + 1
 
1543
        # This is identical now to the WorkingTree _generate_inventory except
 
1544
        # for the tree index use.
 
1545
        root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
 
1546
        current_id = root_key[2]
 
1547
        if current_entry[parent_index][0] != 'd':
 
1548
            raise AssertionError()
 
1549
        inv = Inventory(root_id=current_id, revision_id=self._revision_id)
 
1550
        inv.root.revision = current_entry[parent_index][4]
 
1551
        # Turn some things into local variables
 
1552
        minikind_to_kind = dirstate.DirState._minikind_to_kind
 
1553
        factory = entry_factory
 
1554
        utf8_decode = cache_utf8._utf8_decode
 
1555
        inv_byid = inv._byid
 
1556
        # we could do this straight out of the dirstate; it might be fast
 
1557
        # and should be profiled - RBC 20070216
 
1558
        parent_ies = {'' : inv.root}
 
1559
        for block in self._dirstate._dirblocks[1:]: #skip root
 
1560
            dirname = block[0]
 
1561
            try:
 
1562
                parent_ie = parent_ies[dirname]
 
1563
            except KeyError:
 
1564
                # all the paths in this block are not versioned in this tree
 
1565
                continue
 
1566
            for key, entry in block[1]:
 
1567
                minikind, fingerprint, size, executable, revid = entry[parent_index]
 
1568
                if minikind in ('a', 'r'): # absent, relocated
 
1569
                    # not this tree
 
1570
                    continue
 
1571
                name = key[1]
 
1572
                name_unicode = utf8_decode(name)[0]
 
1573
                file_id = key[2]
 
1574
                kind = minikind_to_kind[minikind]
 
1575
                inv_entry = factory[kind](file_id, name_unicode,
 
1576
                                          parent_ie.file_id)
 
1577
                inv_entry.revision = revid
 
1578
                if kind == 'file':
 
1579
                    inv_entry.executable = executable
 
1580
                    inv_entry.text_size = size
 
1581
                    inv_entry.text_sha1 = fingerprint
 
1582
                elif kind == 'directory':
 
1583
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
 
1584
                elif kind == 'symlink':
 
1585
                    inv_entry.executable = False
 
1586
                    inv_entry.text_size = None
 
1587
                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
 
1588
                elif kind == 'tree-reference':
 
1589
                    inv_entry.reference_revision = fingerprint or None
 
1590
                else:
 
1591
                    raise AssertionError("cannot convert entry %r into an InventoryEntry"
 
1592
                            % entry)
 
1593
                # These checks cost us around 40ms on a 55k entry tree
 
1594
                if file_id in inv_byid:
 
1595
                    raise AssertionError('file_id %s already in'
 
1596
                        ' inventory as %s' % (file_id, inv_byid[file_id]))
 
1597
                if name_unicode in parent_ie.children:
 
1598
                    raise AssertionError('name %r already in parent'
 
1599
                        % (name_unicode,))
 
1600
                inv_byid[file_id] = inv_entry
 
1601
                parent_ie.children[name_unicode] = inv_entry
 
1602
        self._inventory = inv
 
1603
 
 
1604
    def get_file_mtime(self, file_id, path=None):
 
1605
        """Return the modification time for this record.
 
1606
 
 
1607
        We return the timestamp of the last-changed revision.
 
1608
        """
 
1609
        # Make sure the file exists
 
1610
        entry = self._get_entry(file_id, path=path)
 
1611
        if entry == (None, None): # do we raise?
 
1612
            return None
 
1613
        parent_index = self._get_parent_index()
 
1614
        last_changed_revision = entry[1][parent_index][4]
 
1615
        return self._repository.get_revision(last_changed_revision).timestamp
 
1616
 
 
1617
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
1618
        entry = self._get_entry(file_id=file_id, path=path)
 
1619
        parent_index = self._get_parent_index()
 
1620
        parent_details = entry[1][parent_index]
 
1621
        if parent_details[0] == 'f':
 
1622
            return parent_details[1]
 
1623
        return None
 
1624
 
 
1625
    def get_file(self, file_id, path=None):
 
1626
        return StringIO(self.get_file_text(file_id))
 
1627
 
 
1628
    def get_file_size(self, file_id):
 
1629
        """See Tree.get_file_size"""
 
1630
        return self.inventory[file_id].text_size
 
1631
 
 
1632
    def get_file_text(self, file_id, path=None):
 
1633
        return list(self.iter_files_bytes([(file_id, None)]))[0][1]
 
1634
 
 
1635
    def get_reference_revision(self, file_id, path=None):
 
1636
        return self.inventory[file_id].reference_revision
 
1637
 
 
1638
    def iter_files_bytes(self, desired_files):
 
1639
        """See Tree.iter_files_bytes.
 
1640
 
 
1641
        This version is implemented on top of Repository.iter_files_bytes"""
 
1642
        parent_index = self._get_parent_index()
 
1643
        repo_desired_files = []
 
1644
        for file_id, identifier in desired_files:
 
1645
            entry = self._get_entry(file_id)
 
1646
            if entry == (None, None):
 
1647
                raise errors.NoSuchId(self, file_id)
 
1648
            repo_desired_files.append((file_id, entry[1][parent_index][4],
 
1649
                                       identifier))
 
1650
        return self._repository.iter_files_bytes(repo_desired_files)
 
1651
 
 
1652
    def get_symlink_target(self, file_id):
 
1653
        entry = self._get_entry(file_id=file_id)
 
1654
        parent_index = self._get_parent_index()
 
1655
        if entry[1][parent_index][0] != 'l':
 
1656
            return None
 
1657
        else:
 
1658
            # At present, none of the tree implementations supports non-ascii
 
1659
            # symlink targets. So we will just assume that the dirstate path is
 
1660
            # correct.
 
1661
            return entry[1][parent_index][1]
 
1662
 
 
1663
    def get_revision_id(self):
 
1664
        """Return the revision id for this tree."""
 
1665
        return self._revision_id
 
1666
 
 
1667
    def _get_inventory(self):
 
1668
        if self._inventory is not None:
 
1669
            return self._inventory
 
1670
        self._must_be_locked()
 
1671
        self._generate_inventory()
 
1672
        return self._inventory
 
1673
 
 
1674
    inventory = property(_get_inventory,
 
1675
                         doc="Inventory of this Tree")
 
1676
 
 
1677
    def get_parent_ids(self):
 
1678
        """The parents of a tree in the dirstate are not cached."""
 
1679
        return self._repository.get_revision(self._revision_id).parent_ids
 
1680
 
 
1681
    def has_filename(self, filename):
 
1682
        return bool(self.path2id(filename))
 
1683
 
 
1684
    def kind(self, file_id):
 
1685
        entry = self._get_entry(file_id=file_id)[1]
 
1686
        if entry is None:
 
1687
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1688
        return dirstate.DirState._minikind_to_kind[entry[1][0]]
 
1689
 
 
1690
    def stored_kind(self, file_id):
 
1691
        """See Tree.stored_kind"""
 
1692
        return self.kind(file_id)
 
1693
 
 
1694
    def path_content_summary(self, path):
 
1695
        """See Tree.path_content_summary."""
 
1696
        id = self.inventory.path2id(path)
 
1697
        if id is None:
 
1698
            return ('missing', None, None, None)
 
1699
        entry = self._inventory[id]
 
1700
        kind = entry.kind
 
1701
        if kind == 'file':
 
1702
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
 
1703
        elif kind == 'symlink':
 
1704
            return (kind, None, None, entry.symlink_target)
 
1705
        else:
 
1706
            return (kind, None, None, None)
 
1707
 
 
1708
    def is_executable(self, file_id, path=None):
 
1709
        ie = self.inventory[file_id]
 
1710
        if ie.kind != "file":
 
1711
            return None
 
1712
        return ie.executable
 
1713
 
 
1714
    def list_files(self, include_root=False):
 
1715
        # We use a standard implementation, because DirStateRevisionTree is
 
1716
        # dealing with one of the parents of the current state
 
1717
        inv = self._get_inventory()
 
1718
        entries = inv.iter_entries()
 
1719
        if self.inventory.root is not None and not include_root:
 
1720
            entries.next()
 
1721
        for path, entry in entries:
 
1722
            yield path, 'V', entry.kind, entry.file_id, entry
 
1723
 
 
1724
    def lock_read(self):
 
1725
        """Lock the tree for a set of operations."""
 
1726
        if not self._locked:
 
1727
            self._repository.lock_read()
 
1728
            if self._dirstate._lock_token is None:
 
1729
                self._dirstate.lock_read()
 
1730
                self._dirstate_locked = True
 
1731
        self._locked += 1
 
1732
 
 
1733
    def _must_be_locked(self):
 
1734
        if not self._locked:
 
1735
            raise errors.ObjectNotLocked(self)
 
1736
 
 
1737
    @needs_read_lock
 
1738
    def path2id(self, path):
 
1739
        """Return the id for path in this tree."""
 
1740
        # lookup by path: faster than splitting and walking the ivnentory.
 
1741
        entry = self._get_entry(path=path)
 
1742
        if entry == (None, None):
 
1743
            return None
 
1744
        return entry[0][2]
 
1745
 
 
1746
    def unlock(self):
 
1747
        """Unlock, freeing any cache memory used during the lock."""
 
1748
        # outside of a lock, the inventory is suspect: release it.
 
1749
        self._locked -=1
 
1750
        if not self._locked:
 
1751
            self._inventory = None
 
1752
            self._locked = 0
 
1753
            if self._dirstate_locked:
 
1754
                self._dirstate.unlock()
 
1755
                self._dirstate_locked = False
 
1756
            self._repository.unlock()
 
1757
 
 
1758
    @needs_read_lock
 
1759
    def supports_tree_reference(self):
 
1760
        return self._repo_supports_tree_reference
 
1761
 
 
1762
    def walkdirs(self, prefix=""):
 
1763
        # TODO: jam 20070215 This is the lazy way by using the RevisionTree
 
1764
        # implementation based on an inventory.  
 
1765
        # This should be cleaned up to use the much faster Dirstate code
 
1766
        # So for now, we just build up the parent inventory, and extract
 
1767
        # it the same way RevisionTree does.
 
1768
        _directory = 'directory'
 
1769
        inv = self._get_inventory()
 
1770
        top_id = inv.path2id(prefix)
 
1771
        if top_id is None:
 
1772
            pending = []
 
1773
        else:
 
1774
            pending = [(prefix, top_id)]
 
1775
        while pending:
 
1776
            dirblock = []
 
1777
            relpath, file_id = pending.pop()
 
1778
            # 0 - relpath, 1- file-id
 
1779
            if relpath:
 
1780
                relroot = relpath + '/'
 
1781
            else:
 
1782
                relroot = ""
 
1783
            # FIXME: stash the node in pending
 
1784
            entry = inv[file_id]
 
1785
            for name, child in entry.sorted_children():
 
1786
                toppath = relroot + name
 
1787
                dirblock.append((toppath, name, child.kind, None,
 
1788
                    child.file_id, child.kind
 
1789
                    ))
 
1790
            yield (relpath, entry.file_id), dirblock
 
1791
            # push the user specified dirs from dirblock
 
1792
            for dir in reversed(dirblock):
 
1793
                if dir[2] == _directory:
 
1794
                    pending.append((dir[0], dir[4]))
 
1795
 
 
1796
 
 
1797
class InterDirStateTree(InterTree):
 
1798
    """Fast path optimiser for changes_from with dirstate trees.
 
1799
    
 
1800
    This is used only when both trees are in the dirstate working file, and 
 
1801
    the source is any parent within the dirstate, and the destination is 
 
1802
    the current working tree of the same dirstate.
 
1803
    """
 
1804
    # this could be generalized to allow comparisons between any trees in the
 
1805
    # dirstate, and possibly between trees stored in different dirstates.
 
1806
 
 
1807
    def __init__(self, source, target):
 
1808
        super(InterDirStateTree, self).__init__(source, target)
 
1809
        if not InterDirStateTree.is_compatible(source, target):
 
1810
            raise Exception, "invalid source %r and target %r" % (source, target)
 
1811
 
 
1812
    @staticmethod
 
1813
    def make_source_parent_tree(source, target):
 
1814
        """Change the source tree into a parent of the target."""
 
1815
        revid = source.commit('record tree')
 
1816
        target.branch.repository.fetch(source.branch.repository, revid)
 
1817
        target.set_parent_ids([revid])
 
1818
        return target.basis_tree(), target
 
1819
 
 
1820
    @classmethod
 
1821
    def make_source_parent_tree_python_dirstate(klass, test_case, source, target):
 
1822
        result = klass.make_source_parent_tree(source, target)
 
1823
        result[1]._iter_changes = dirstate.ProcessEntryPython
 
1824
        return result
 
1825
 
 
1826
    @classmethod
 
1827
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source, target):
 
1828
        from bzrlib.tests.test__dirstate_helpers import \
 
1829
            CompiledDirstateHelpersFeature
 
1830
        if not CompiledDirstateHelpersFeature.available():
 
1831
            from bzrlib.tests import UnavailableFeature
 
1832
            raise UnavailableFeature(CompiledDirstateHelpersFeature)
 
1833
        from bzrlib._dirstate_helpers_c import ProcessEntryC
 
1834
        result = klass.make_source_parent_tree(source, target)
 
1835
        result[1]._iter_changes = ProcessEntryC
 
1836
        return result
 
1837
 
 
1838
    _matching_from_tree_format = WorkingTreeFormat4()
 
1839
    _matching_to_tree_format = WorkingTreeFormat4()
 
1840
 
 
1841
    @classmethod
 
1842
    def _test_mutable_trees_to_test_trees(klass, test_case, source, target):
 
1843
        # This method shouldn't be called, because we have python and C
 
1844
        # specific flavours.
 
1845
        raise NotImplementedError
 
1846
 
 
1847
    def iter_changes(self, include_unchanged=False,
 
1848
                      specific_files=None, pb=None, extra_trees=[],
 
1849
                      require_versioned=True, want_unversioned=False):
 
1850
        """Return the changes from source to target.
 
1851
 
 
1852
        :return: An iterator that yields tuples. See InterTree.iter_changes
 
1853
            for details.
 
1854
        :param specific_files: An optional list of file paths to restrict the
 
1855
            comparison to. When mapping filenames to ids, all matches in all
 
1856
            trees (including optional extra_trees) are used, and all children of
 
1857
            matched directories are included.
 
1858
        :param include_unchanged: An optional boolean requesting the inclusion of
 
1859
            unchanged entries in the result.
 
1860
        :param extra_trees: An optional list of additional trees to use when
 
1861
            mapping the contents of specific_files (paths) to file_ids.
 
1862
        :param require_versioned: If True, all files in specific_files must be
 
1863
            versioned in one of source, target, extra_trees or
 
1864
            PathsNotVersionedError is raised.
 
1865
        :param want_unversioned: Should unversioned files be returned in the
 
1866
            output. An unversioned file is defined as one with (False, False)
 
1867
            for the versioned pair.
 
1868
        """
 
1869
        # NB: show_status depends on being able to pass in non-versioned files
 
1870
        # and report them as unknown
 
1871
        # TODO: handle extra trees in the dirstate.
 
1872
        if (extra_trees or specific_files == []):
 
1873
            # we can't fast-path these cases (yet)
 
1874
            return super(InterDirStateTree, self).iter_changes(
 
1875
                include_unchanged, specific_files, pb, extra_trees,
 
1876
                require_versioned, want_unversioned=want_unversioned)
 
1877
        parent_ids = self.target.get_parent_ids()
 
1878
        if not (self.source._revision_id in parent_ids
 
1879
                or self.source._revision_id == NULL_REVISION):
 
1880
            raise AssertionError(
 
1881
                "revision {%s} is not stored in {%s}, but %s "
 
1882
                "can only be used for trees stored in the dirstate"
 
1883
                % (self.source._revision_id, self.target, self.iter_changes))
 
1884
        target_index = 0
 
1885
        if self.source._revision_id == NULL_REVISION:
 
1886
            source_index = None
 
1887
            indices = (target_index,)
 
1888
        else:
 
1889
            if not (self.source._revision_id in parent_ids):
 
1890
                raise AssertionError(
 
1891
                    "Failure: source._revision_id: %s not in target.parent_ids(%s)" % (
 
1892
                    self.source._revision_id, parent_ids))
 
1893
            source_index = 1 + parent_ids.index(self.source._revision_id)
 
1894
            indices = (source_index, target_index)
 
1895
        # -- make all specific_files utf8 --
 
1896
        if specific_files:
 
1897
            specific_files_utf8 = set()
 
1898
            for path in specific_files:
 
1899
                # Note, if there are many specific files, using cache_utf8
 
1900
                # would be good here.
 
1901
                specific_files_utf8.add(path.encode('utf8'))
 
1902
            specific_files = specific_files_utf8
 
1903
        else:
 
1904
            specific_files = set([''])
 
1905
        # -- specific_files is now a utf8 path set --
 
1906
        search_specific_files = set()
 
1907
        # -- get the state object and prepare it.
 
1908
        state = self.target.current_dirstate()
 
1909
        state._read_dirblocks_if_needed()
 
1910
        if require_versioned:
 
1911
            # -- check all supplied paths are versioned in a search tree. --
 
1912
            all_versioned = True
 
1913
            for path in specific_files:
 
1914
                path_entries = state._entries_for_path(path)
 
1915
                if not path_entries:
 
1916
                    # this specified path is not present at all: error
 
1917
                    all_versioned = False
 
1918
                    break
 
1919
                found_versioned = False
 
1920
                # for each id at this path
 
1921
                for entry in path_entries:
 
1922
                    # for each tree.
 
1923
                    for index in indices:
 
1924
                        if entry[1][index][0] != 'a': # absent
 
1925
                            found_versioned = True
 
1926
                            # all good: found a versioned cell
 
1927
                            break
 
1928
                if not found_versioned:
 
1929
                    # none of the indexes was not 'absent' at all ids for this
 
1930
                    # path.
 
1931
                    all_versioned = False
 
1932
                    break
 
1933
            if not all_versioned:
 
1934
                raise errors.PathsNotVersionedError(specific_files)
 
1935
        # -- remove redundancy in supplied specific_files to prevent over-scanning --
 
1936
        for path in specific_files:
 
1937
            other_specific_files = specific_files.difference(set([path]))
 
1938
            if not osutils.is_inside_any(other_specific_files, path):
 
1939
                # this is a top level path, we must check it.
 
1940
                search_specific_files.add(path)
 
1941
 
 
1942
        use_filesystem_for_exec = (sys.platform != 'win32')
 
1943
        iter_changes = self.target._iter_changes(include_unchanged,
 
1944
            use_filesystem_for_exec, search_specific_files, state,
 
1945
            source_index, target_index, want_unversioned, self.target)
 
1946
        return iter_changes.iter_changes()
 
1947
 
 
1948
    @staticmethod
 
1949
    def is_compatible(source, target):
 
1950
        # the target must be a dirstate working tree
 
1951
        if not isinstance(target, WorkingTree4):
 
1952
            return False
 
1953
        # the source must be a revtreee or dirstate rev tree.
 
1954
        if not isinstance(source,
 
1955
            (revisiontree.RevisionTree, DirStateRevisionTree)):
 
1956
            return False
 
1957
        # the source revid must be in the target dirstate
 
1958
        if not (source._revision_id == NULL_REVISION or
 
1959
            source._revision_id in target.get_parent_ids()):
 
1960
            # TODO: what about ghosts? it may well need to 
 
1961
            # check for them explicitly.
 
1962
            return False
 
1963
        return True
 
1964
 
 
1965
InterTree.register_optimiser(InterDirStateTree)
 
1966
 
 
1967
 
 
1968
class Converter3to4(object):
 
1969
    """Perform an in-place upgrade of format 3 to format 4 trees."""
 
1970
 
 
1971
    def __init__(self):
 
1972
        self.target_format = WorkingTreeFormat4()
 
1973
 
 
1974
    def convert(self, tree):
 
1975
        # lock the control files not the tree, so that we dont get tree
 
1976
        # on-unlock behaviours, and so that noone else diddles with the 
 
1977
        # tree during upgrade.
 
1978
        tree._control_files.lock_write()
 
1979
        try:
 
1980
            tree.read_working_inventory()
 
1981
            self.create_dirstate_data(tree)
 
1982
            self.update_format(tree)
 
1983
            self.remove_xml_files(tree)
 
1984
        finally:
 
1985
            tree._control_files.unlock()
 
1986
 
 
1987
    def create_dirstate_data(self, tree):
 
1988
        """Create the dirstate based data for tree."""
 
1989
        local_path = tree.bzrdir.get_workingtree_transport(None
 
1990
            ).local_abspath('dirstate')
 
1991
        state = dirstate.DirState.from_tree(tree, local_path)
 
1992
        state.save()
 
1993
        state.unlock()
 
1994
 
 
1995
    def remove_xml_files(self, tree):
 
1996
        """Remove the oldformat 3 data."""
 
1997
        transport = tree.bzrdir.get_workingtree_transport(None)
 
1998
        for path in ['basis-inventory-cache', 'inventory', 'last-revision',
 
1999
            'pending-merges', 'stat-cache']:
 
2000
            try:
 
2001
                transport.delete(path)
 
2002
            except errors.NoSuchFile:
 
2003
                # some files are optional - just deal.
 
2004
                pass
 
2005
 
 
2006
    def update_format(self, tree):
 
2007
        """Change the format marker."""
 
2008
        tree._transport.put_bytes('format',
 
2009
            self.target_format.get_format_string(),
 
2010
            mode=tree.bzrdir._get_file_mode())