/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/workingtree_4.py

  • Committer: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 
25
25
from __future__ import absolute_import
26
26
 
 
27
from cStringIO import StringIO
27
28
import os
28
29
import sys
29
30
 
30
 
from ..lazy_import import lazy_import
 
31
from bzrlib.lazy_import import lazy_import
31
32
lazy_import(globals(), """
32
33
import errno
33
34
import stat
34
35
 
35
 
from breezy import (
 
36
from bzrlib import (
 
37
    bzrdir,
36
38
    cache_utf8,
37
 
    cleanup,
38
39
    config,
39
40
    conflicts as _mod_conflicts,
40
41
    controldir,
41
42
    debug,
 
43
    dirstate,
42
44
    errors,
43
45
    filters as _mod_filters,
44
46
    generate_ids,
49
51
    transform,
50
52
    views,
51
53
    )
52
 
from breezy.bzr import (
53
 
    bzrdir,
54
 
    dirstate,
55
 
    )
56
54
""")
57
55
 
58
 
from .inventory import Inventory, ROOT_ID, entry_factory
59
 
from ..lock import LogicalLockResult
60
 
from ..lockable_files import LockableFiles
61
 
from ..lockdir import LockDir
62
 
from .inventorytree import (
63
 
    InventoryTree,
64
 
    InventoryRevisionTree,
65
 
    )
66
 
from ..mutabletree import (
67
 
    BadReferenceTarget,
 
56
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
57
from bzrlib.inventory import Inventory, ROOT_ID, entry_factory
 
58
from bzrlib.lock import LogicalLockResult
 
59
from bzrlib.lockable_files import LockableFiles
 
60
from bzrlib.lockdir import LockDir
 
61
from bzrlib.mutabletree import (
68
62
    MutableTree,
 
63
    needs_tree_write_lock,
69
64
    )
70
 
from ..osutils import (
 
65
from bzrlib.osutils import (
71
66
    file_kind,
72
67
    isdir,
73
68
    pathjoin,
74
69
    realpath,
75
70
    safe_unicode,
76
71
    )
77
 
from ..sixish import (
78
 
    BytesIO,
79
 
    viewitems,
 
72
from bzrlib.symbol_versioning import (
 
73
    deprecated_in,
 
74
    deprecated_method,
80
75
    )
81
 
from ..transport.local import LocalTransport
82
 
from ..tree import (
83
 
    FileTimestampUnavailable,
 
76
from bzrlib.transport.local import LocalTransport
 
77
from bzrlib.tree import (
84
78
    InterTree,
 
79
    InventoryTree,
85
80
    )
86
 
from ..workingtree import (
 
81
from bzrlib.workingtree import (
 
82
    InventoryWorkingTree,
87
83
    WorkingTree,
88
 
    )
89
 
from .workingtree import (
90
 
    InventoryWorkingTree,
91
84
    WorkingTreeFormatMetaDir,
92
85
    )
93
86
 
98
91
                 branch,
99
92
                 _control_files=None,
100
93
                 _format=None,
101
 
                 _controldir=None):
 
94
                 _bzrdir=None):
102
95
        """Construct a WorkingTree for basedir.
103
96
 
104
97
        If the branch is not supplied, it is opened automatically.
107
100
        would be meaningless).
108
101
        """
109
102
        self._format = _format
110
 
        self.controldir = _controldir
 
103
        self.bzrdir = _bzrdir
111
104
        basedir = safe_unicode(basedir)
112
105
        trace.mutter("opening working tree %r", basedir)
113
106
        self._branch = branch
130
123
        #--- allow tests to select the dirstate iter_changes implementation
131
124
        self._iter_changes = dirstate._process_entry
132
125
 
 
126
    @needs_tree_write_lock
133
127
    def _add(self, files, ids, kinds):
134
128
        """See MutableTree._add."""
135
 
        with self.lock_tree_write():
136
 
            state = self.current_dirstate()
137
 
            for f, file_id, kind in zip(files, ids, kinds):
138
 
                f = f.strip(u'/')
139
 
                if self.path2id(f):
140
 
                    # special case tree root handling.
141
 
                    if f == b'' and self.path2id(f) == ROOT_ID:
142
 
                        state.set_path_id(b'', generate_ids.gen_file_id(f))
143
 
                    continue
144
 
                if file_id is None:
145
 
                    file_id = generate_ids.gen_file_id(f)
146
 
                # deliberately add the file with no cached stat or sha1
147
 
                # - on the first access it will be gathered, and we can
148
 
                # always change this once tests are all passing.
149
 
                state.add(f, file_id, kind, None, b'')
150
 
            self._make_dirty(reset_inventory=True)
 
129
        state = self.current_dirstate()
 
130
        for f, file_id, kind in zip(files, ids, kinds):
 
131
            f = f.strip('/')
 
132
            if self.path2id(f):
 
133
                # special case tree root handling.
 
134
                if f == '' and self.path2id(f) == ROOT_ID:
 
135
                    state.set_path_id('', generate_ids.gen_file_id(f))
 
136
                continue
 
137
            if file_id is None:
 
138
                file_id = generate_ids.gen_file_id(f)
 
139
            # deliberately add the file with no cached stat or sha1
 
140
            # - on the first access it will be gathered, and we can
 
141
            # always change this once tests are all passing.
 
142
            state.add(f, file_id, kind, None, '')
 
143
        self._make_dirty(reset_inventory=True)
151
144
 
152
145
    def _get_check_refs(self):
153
146
        """Return the references needed to perform a check of this tree."""
163
156
        if reset_inventory and self._inventory is not None:
164
157
            self._inventory = None
165
158
 
 
159
    @needs_tree_write_lock
166
160
    def add_reference(self, sub_tree):
167
161
        # use standard implementation, which calls back to self._add
168
162
        #
169
163
        # So we don't store the reference_revision in the working dirstate,
170
164
        # it's just recorded at the moment of commit.
171
 
        with self.lock_tree_write():
172
 
            try:
173
 
                sub_tree_path = self.relpath(sub_tree.basedir)
174
 
            except errors.PathNotChild:
175
 
                raise BadReferenceTarget(self, sub_tree,
176
 
                                         'Target not inside tree.')
177
 
            sub_tree_id = sub_tree.get_root_id()
178
 
            if sub_tree_id == self.get_root_id():
179
 
                raise BadReferenceTarget(self, sub_tree,
180
 
                                         'Trees have the same root id.')
181
 
            if self.has_id(sub_tree_id):
182
 
                raise BadReferenceTarget(self, sub_tree,
183
 
                                         'Root id already present in tree')
184
 
            self._add([sub_tree_path], [sub_tree_id], ['tree-reference'])
 
165
        self._add_reference(sub_tree)
185
166
 
186
167
    def break_lock(self):
187
168
        """Break a lock if one is present from another instance.
230
211
            kind = 'tree-reference'
231
212
        return kind, executable, stat_value
232
213
 
 
214
    @needs_write_lock
233
215
    def commit(self, message=None, revprops=None, *args, **kwargs):
234
 
        with self.lock_write():
235
 
            # mark the tree as dirty post commit - commit
236
 
            # can change the current versioned list by doing deletes.
237
 
            result = WorkingTree.commit(self, message, revprops, *args,
238
 
                                        **kwargs)
239
 
            self._make_dirty(reset_inventory=True)
240
 
            return result
 
216
        # mark the tree as dirty post commit - commit
 
217
        # can change the current versioned list by doing deletes.
 
218
        result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
 
219
        self._make_dirty(reset_inventory=True)
 
220
        return result
241
221
 
242
222
    def current_dirstate(self):
243
223
        """Return the current dirstate object.
257
237
        """
258
238
        if self._dirstate is not None:
259
239
            return self._dirstate
260
 
        local_path = self.controldir.get_workingtree_transport(None
 
240
        local_path = self.bzrdir.get_workingtree_transport(None
261
241
            ).local_abspath('dirstate')
262
242
        self._dirstate = dirstate.DirState.on_file(local_path,
263
243
            self._sha1_provider(), self._worth_saving_limit())
311
291
        self._inventory = None
312
292
        self._dirty = False
313
293
 
 
294
    @needs_tree_write_lock
314
295
    def _gather_kinds(self, files, kinds):
315
296
        """See MutableTree._gather_kinds."""
316
 
        with self.lock_tree_write():
317
 
            for pos, f in enumerate(files):
318
 
                if kinds[pos] is None:
319
 
                    kinds[pos] = self.kind(f)
 
297
        for pos, f in enumerate(files):
 
298
            if kinds[pos] is None:
 
299
                kinds[pos] = self._kind(f)
320
300
 
321
301
    def _generate_inventory(self):
322
302
        """Create and set self.inventory from the dirstate object.
330
310
        state._read_dirblocks_if_needed()
331
311
        root_key, current_entry = self._get_entry(path='')
332
312
        current_id = root_key[2]
333
 
        if not (current_entry[0][0] == b'd'): # directory
 
313
        if not (current_entry[0][0] == 'd'): # directory
334
314
            raise AssertionError(current_entry)
335
315
        inv = Inventory(root_id=current_id)
336
316
        # Turn some things into local variables
350
330
                continue
351
331
            for key, entry in block[1]:
352
332
                minikind, link_or_sha1, size, executable, stat = entry[0]
353
 
                if minikind in (b'a', b'r'): # absent, relocated
 
333
                if minikind in ('a', 'r'): # absent, relocated
354
334
                    # a parent tree only entry
355
335
                    continue
356
336
                name = key[1]
368
348
                    #inv_entry.text_sha1 = sha1
369
349
                elif kind == 'directory':
370
350
                    # add this entry to the parent map.
371
 
                    parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
 
351
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
372
352
                elif kind == 'tree-reference':
373
353
                    if not self._repo_supports_tree_reference:
374
354
                        raise errors.UnsupportedOperation(
406
386
            path = path.encode('utf8')
407
387
        return state._get_entry(0, fileid_utf8=file_id, path_utf8=path)
408
388
 
409
 
    def get_file_sha1(self, path, file_id=None, stat_value=None):
 
389
    def get_file_sha1(self, file_id, path=None, stat_value=None):
410
390
        # check file id is valid unconditionally.
411
391
        entry = self._get_entry(file_id=file_id, path=path)
412
392
        if entry[0] is None:
413
 
            raise errors.NoSuchFile(self, path)
 
393
            raise errors.NoSuchId(self, file_id)
414
394
        if path is None:
415
395
            path = pathjoin(entry[0][0], entry[0][1]).decode('utf8')
416
396
 
419
399
        if stat_value is None:
420
400
            try:
421
401
                stat_value = osutils.lstat(file_abspath)
422
 
            except OSError as e:
 
402
            except OSError, e:
423
403
                if e.errno == errno.ENOENT:
424
404
                    return None
425
405
                else:
426
406
                    raise
427
407
        link_or_sha1 = dirstate.update_entry(state, entry, file_abspath,
428
408
            stat_value=stat_value)
429
 
        if entry[1][0][0] == b'f':
 
409
        if entry[1][0][0] == 'f':
430
410
            if link_or_sha1 is None:
431
 
                file_obj, statvalue = self.get_file_with_stat(path, file_id)
 
411
                file_obj, statvalue = self.get_file_with_stat(file_id, path)
432
412
                try:
433
413
                    sha1 = osutils.sha_file(file_obj)
434
414
                finally:
450
430
        self._generate_inventory()
451
431
        return self._inventory
452
432
 
 
433
    @deprecated_method(deprecated_in((2, 5, 0)))
 
434
    def _get_inventory(self):
 
435
        return self.root_inventory
 
436
 
 
437
    inventory = property(_get_inventory,
 
438
                         doc="Inventory of this Tree")
 
439
 
453
440
    root_inventory = property(_get_root_inventory,
454
441
        "Root inventory of this tree")
455
442
 
 
443
    @needs_read_lock
456
444
    def get_parent_ids(self):
457
445
        """See Tree.get_parent_ids.
458
446
 
459
447
        This implementation requests the ids list from the dirstate file.
460
448
        """
461
 
        with self.lock_read():
462
 
            return self.current_dirstate().get_parent_ids()
 
449
        return self.current_dirstate().get_parent_ids()
463
450
 
464
 
    def get_reference_revision(self, path, file_id=None):
 
451
    def get_reference_revision(self, file_id, path=None):
465
452
        # referenced tree's revision is whatever's currently there
466
 
        return self.get_nested_tree(path, file_id).last_revision()
 
453
        return self.get_nested_tree(file_id, path).last_revision()
467
454
 
468
 
    def get_nested_tree(self, path, file_id=None):
 
455
    def get_nested_tree(self, file_id, path=None):
 
456
        if path is None:
 
457
            path = self.id2path(file_id)
 
458
        # else: check file_id is at path?
469
459
        return WorkingTree.open(self.abspath(path))
470
460
 
 
461
    @needs_read_lock
471
462
    def get_root_id(self):
472
463
        """Return the id of this trees root"""
473
 
        with self.lock_read():
474
 
            return self._get_entry(path='')[0][2]
 
464
        return self._get_entry(path='')[0][2]
475
465
 
476
466
    def has_id(self, file_id):
477
467
        state = self.current_dirstate()
486
476
        row, parents = self._get_entry(file_id=file_id)
487
477
        return row is not None
488
478
 
 
479
    @needs_read_lock
489
480
    def id2path(self, file_id):
490
481
        "Convert a file-id to a path."
491
 
        with self.lock_read():
492
 
            state = self.current_dirstate()
493
 
            entry = self._get_entry(file_id=file_id)
494
 
            if entry == (None, None):
495
 
                raise errors.NoSuchId(tree=self, file_id=file_id)
496
 
            path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
497
 
            return path_utf8.decode('utf8')
 
482
        state = self.current_dirstate()
 
483
        entry = self._get_entry(file_id=file_id)
 
484
        if entry == (None, None):
 
485
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
486
        path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
 
487
        return path_utf8.decode('utf8')
498
488
 
499
489
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
500
490
        entry = self._get_entry(path=path)
502
492
            return False # Missing entries are not executable
503
493
        return entry[1][0][3] # Executable?
504
494
 
505
 
    def is_executable(self, path, file_id=None):
 
495
    def is_executable(self, file_id, path=None):
506
496
        """Test if a file is executable or not.
507
497
 
508
498
        Note: The caller is expected to take a read-lock before calling this.
514
504
            return entry[1][0][3]
515
505
        else:
516
506
            self._must_be_locked()
 
507
            if not path:
 
508
                path = self.id2path(file_id)
517
509
            mode = osutils.lstat(self.abspath(path)).st_mode
518
510
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
519
511
 
522
514
        self._must_be_locked()
523
515
        result = set()
524
516
        for key, tree_details in self.current_dirstate()._iter_entries():
525
 
            if tree_details[0][0] in (b'a', b'r'): # relocated
 
517
            if tree_details[0][0] in ('a', 'r'): # relocated
526
518
                continue
527
519
            result.add(key[2])
528
520
        return result
529
521
 
530
 
    def all_versioned_paths(self):
531
 
        self._must_be_locked()
532
 
        return {path for path, entry in
533
 
                    self.root_inventory.iter_entries(recursive=True)}
534
 
 
 
522
    @needs_read_lock
535
523
    def __iter__(self):
536
524
        """Iterate through file_ids for this tree.
537
525
 
538
526
        file_ids are in a WorkingTree if they are in the working inventory
539
527
        and the working file exists.
540
528
        """
541
 
        with self.lock_read():
542
 
            result = []
543
 
            for key, tree_details in self.current_dirstate()._iter_entries():
544
 
                if tree_details[0][0] in (b'a', b'r'): # absent, relocated
545
 
                    # not relevant to the working tree
546
 
                    continue
547
 
                path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
548
 
                if osutils.lexists(path):
549
 
                    result.append(key[2])
550
 
            return iter(result)
 
529
        result = []
 
530
        for key, tree_details in self.current_dirstate()._iter_entries():
 
531
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
 
532
                # not relevant to the working tree
 
533
                continue
 
534
            path = pathjoin(self.basedir, key[0].decode('utf8'), key[1].decode('utf8'))
 
535
            if osutils.lexists(path):
 
536
                result.append(key[2])
 
537
        return iter(result)
551
538
 
552
539
    def iter_references(self):
553
540
        if not self._repo_supports_tree_reference:
555
542
            # return
556
543
            return
557
544
        for key, tree_details in self.current_dirstate()._iter_entries():
558
 
            if tree_details[0][0] in (b'a', b'r'): # absent, relocated
 
545
            if tree_details[0][0] in ('a', 'r'): # absent, relocated
559
546
                # not relevant to the working tree
560
547
                continue
561
548
            if not key[1]:
563
550
                continue
564
551
            relpath = pathjoin(key[0].decode('utf8'), key[1].decode('utf8'))
565
552
            try:
566
 
                if self.kind(relpath) == 'tree-reference':
 
553
                if self._kind(relpath) == 'tree-reference':
567
554
                    yield relpath, key[2]
568
555
            except errors.NoSuchFile:
569
556
                # path is missing on disk.
570
557
                continue
571
558
 
572
 
    def _observed_sha1(self, file_id, path, sha_and_stat):
 
559
    def _observed_sha1(self, file_id, path, (sha1, statvalue)):
573
560
        """See MutableTree._observed_sha1."""
574
561
        state = self.current_dirstate()
575
562
        entry = self._get_entry(file_id=file_id, path=path)
576
 
        state._observed_sha1(entry, *sha_and_stat)
577
 
 
578
 
    def kind(self, relpath, file_id=None):
 
563
        state._observed_sha1(entry, sha1, statvalue)
 
564
 
 
565
    def kind(self, file_id):
 
566
        """Return the kind of a file.
 
567
 
 
568
        This is always the actual kind that's on disk, regardless of what it
 
569
        was added as.
 
570
 
 
571
        Note: The caller is expected to take a read-lock before calling this.
 
572
        """
 
573
        relpath = self.id2path(file_id)
 
574
        if relpath is None:
 
575
            raise AssertionError(
 
576
                "path for id {%s} is None!" % file_id)
 
577
        return self._kind(relpath)
 
578
 
 
579
    def _kind(self, relpath):
579
580
        abspath = self.abspath(relpath)
580
581
        kind = file_kind(abspath)
581
582
        if (self._repo_supports_tree_reference and kind == 'directory'):
582
583
            entry = self._get_entry(path=relpath)
583
584
            if entry[1] is not None:
584
 
                if entry[1][0][0] == b't':
 
585
                if entry[1][0][0] == 't':
585
586
                    kind = 'tree-reference'
586
587
        return kind
587
588
 
 
589
    @needs_read_lock
588
590
    def _last_revision(self):
589
591
        """See Mutable.last_revision."""
590
 
        with self.lock_read():
591
 
            parent_ids = self.current_dirstate().get_parent_ids()
592
 
            if parent_ids:
593
 
                return parent_ids[0]
594
 
            else:
595
 
                return _mod_revision.NULL_REVISION
 
592
        parent_ids = self.current_dirstate().get_parent_ids()
 
593
        if parent_ids:
 
594
            return parent_ids[0]
 
595
        else:
 
596
            return _mod_revision.NULL_REVISION
596
597
 
597
598
    def lock_read(self):
598
599
        """See Branch.lock_read, and WorkingTree.unlock.
599
600
 
600
 
        :return: A breezy.lock.LogicalLockResult.
 
601
        :return: A bzrlib.lock.LogicalLockResult.
601
602
        """
602
603
        self.branch.lock_read()
603
604
        try:
643
644
    def lock_tree_write(self):
644
645
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
645
646
 
646
 
        :return: A breezy.lock.LogicalLockResult.
 
647
        :return: A bzrlib.lock.LogicalLockResult.
647
648
        """
648
649
        self.branch.lock_read()
649
650
        return self._lock_self_write()
651
652
    def lock_write(self):
652
653
        """See MutableTree.lock_write, and WorkingTree.unlock.
653
654
 
654
 
        :return: A breezy.lock.LogicalLockResult.
 
655
        :return: A bzrlib.lock.LogicalLockResult.
655
656
        """
656
657
        self.branch.lock_write()
657
658
        return self._lock_self_write()
658
659
 
 
660
    @needs_tree_write_lock
659
661
    def move(self, from_paths, to_dir, after=False):
660
662
        """See WorkingTree.move()."""
661
663
        result = []
662
664
        if not from_paths:
663
665
            return result
664
 
        with self.lock_tree_write():
665
 
            state = self.current_dirstate()
666
 
            if isinstance(from_paths, (str, bytes)):
667
 
                raise ValueError()
668
 
            to_dir_utf8 = to_dir.encode('utf8')
669
 
            to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
670
 
            id_index = state._get_id_index()
671
 
            # check destination directory
672
 
            # get the details for it
673
 
            to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
674
 
                state._get_block_entry_index(to_entry_dirname, to_basename, 0)
675
 
            if not entry_present:
676
 
                raise errors.BzrMoveFailedError('', to_dir,
677
 
                    errors.NotVersionedError(to_dir))
678
 
            to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
679
 
            # get a handle on the block itself.
680
 
            to_block_index = state._ensure_block(
681
 
                to_entry_block_index, to_entry_entry_index, to_dir_utf8)
682
 
            to_block = state._dirblocks[to_block_index]
683
 
            to_abs = self.abspath(to_dir)
684
 
            if not isdir(to_abs):
685
 
                raise errors.BzrMoveFailedError('', to_dir,
686
 
                    errors.NotADirectory(to_abs))
687
 
 
688
 
            if to_entry[1][0][0] != 'd':
689
 
                raise errors.BzrMoveFailedError('', to_dir,
690
 
                    errors.NotADirectory(to_abs))
691
 
 
692
 
            if self._inventory is not None:
693
 
                update_inventory = True
694
 
                inv = self.root_inventory
695
 
                to_dir_id = to_entry[0][2]
696
 
                to_dir_ie = inv.get_entry(to_dir_id)
697
 
            else:
698
 
                update_inventory = False
699
 
 
700
 
            # GZ 2017-03-28: The rollbacks variable was shadowed in the loop below
701
 
            # missing those added here, but there's also no test coverage for this.
702
 
            rollbacks = cleanup.ObjectWithCleanups()
703
 
            def move_one(old_entry, from_path_utf8, minikind, executable,
704
 
                         fingerprint, packed_stat, size,
705
 
                         to_block, to_key, to_path_utf8):
706
 
                state._make_absent(old_entry)
707
 
                from_key = old_entry[0]
708
 
                rollbacks.add_cleanup(
709
 
                    state.update_minimal,
710
 
                    from_key,
711
 
                    minikind,
712
 
                    executable=executable,
713
 
                    fingerprint=fingerprint,
714
 
                    packed_stat=packed_stat,
715
 
                    size=size,
716
 
                    path_utf8=from_path_utf8)
717
 
                state.update_minimal(to_key,
718
 
                        minikind,
719
 
                        executable=executable,
720
 
                        fingerprint=fingerprint,
721
 
                        packed_stat=packed_stat,
722
 
                        size=size,
723
 
                        path_utf8=to_path_utf8)
724
 
                added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
725
 
                new_entry = to_block[1][added_entry_index]
726
 
                rollbacks.add_cleanup(state._make_absent, new_entry)
727
 
 
728
 
            for from_rel in from_paths:
729
 
                # from_rel is 'pathinroot/foo/bar'
730
 
                from_rel_utf8 = from_rel.encode('utf8')
731
 
                from_dirname, from_tail = osutils.split(from_rel)
732
 
                from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
733
 
                from_entry = self._get_entry(path=from_rel)
734
 
                if from_entry == (None, None):
735
 
                    raise errors.BzrMoveFailedError(from_rel, to_dir,
736
 
                        errors.NotVersionedError(path=from_rel))
737
 
 
738
 
                from_id = from_entry[0][2]
739
 
                to_rel = pathjoin(to_dir, from_tail)
740
 
                to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
741
 
                item_to_entry = self._get_entry(path=to_rel)
742
 
                if item_to_entry != (None, None):
743
 
                    raise errors.BzrMoveFailedError(from_rel, to_rel,
744
 
                        "Target is already versioned.")
745
 
 
746
 
                if from_rel == to_rel:
747
 
                    raise errors.BzrMoveFailedError(from_rel, to_rel,
748
 
                        "Source and target are identical.")
749
 
 
750
 
                from_missing = not self.has_filename(from_rel)
751
 
                to_missing = not self.has_filename(to_rel)
752
 
                if after:
 
666
        state = self.current_dirstate()
 
667
        if isinstance(from_paths, basestring):
 
668
            raise ValueError()
 
669
        to_dir_utf8 = to_dir.encode('utf8')
 
670
        to_entry_dirname, to_basename = os.path.split(to_dir_utf8)
 
671
        id_index = state._get_id_index()
 
672
        # check destination directory
 
673
        # get the details for it
 
674
        to_entry_block_index, to_entry_entry_index, dir_present, entry_present = \
 
675
            state._get_block_entry_index(to_entry_dirname, to_basename, 0)
 
676
        if not entry_present:
 
677
            raise errors.BzrMoveFailedError('', to_dir,
 
678
                errors.NotVersionedError(to_dir))
 
679
        to_entry = state._dirblocks[to_entry_block_index][1][to_entry_entry_index]
 
680
        # get a handle on the block itself.
 
681
        to_block_index = state._ensure_block(
 
682
            to_entry_block_index, to_entry_entry_index, to_dir_utf8)
 
683
        to_block = state._dirblocks[to_block_index]
 
684
        to_abs = self.abspath(to_dir)
 
685
        if not isdir(to_abs):
 
686
            raise errors.BzrMoveFailedError('',to_dir,
 
687
                errors.NotADirectory(to_abs))
 
688
 
 
689
        if to_entry[1][0][0] != 'd':
 
690
            raise errors.BzrMoveFailedError('',to_dir,
 
691
                errors.NotADirectory(to_abs))
 
692
 
 
693
        if self._inventory is not None:
 
694
            update_inventory = True
 
695
            inv = self.root_inventory
 
696
            to_dir_id = to_entry[0][2]
 
697
            to_dir_ie = inv[to_dir_id]
 
698
        else:
 
699
            update_inventory = False
 
700
 
 
701
        rollbacks = []
 
702
        def move_one(old_entry, from_path_utf8, minikind, executable,
 
703
                     fingerprint, packed_stat, size,
 
704
                     to_block, to_key, to_path_utf8):
 
705
            state._make_absent(old_entry)
 
706
            from_key = old_entry[0]
 
707
            rollbacks.append(
 
708
                lambda:state.update_minimal(from_key,
 
709
                    minikind,
 
710
                    executable=executable,
 
711
                    fingerprint=fingerprint,
 
712
                    packed_stat=packed_stat,
 
713
                    size=size,
 
714
                    path_utf8=from_path_utf8))
 
715
            state.update_minimal(to_key,
 
716
                    minikind,
 
717
                    executable=executable,
 
718
                    fingerprint=fingerprint,
 
719
                    packed_stat=packed_stat,
 
720
                    size=size,
 
721
                    path_utf8=to_path_utf8)
 
722
            added_entry_index, _ = state._find_entry_index(to_key, to_block[1])
 
723
            new_entry = to_block[1][added_entry_index]
 
724
            rollbacks.append(lambda:state._make_absent(new_entry))
 
725
 
 
726
        for from_rel in from_paths:
 
727
            # from_rel is 'pathinroot/foo/bar'
 
728
            from_rel_utf8 = from_rel.encode('utf8')
 
729
            from_dirname, from_tail = osutils.split(from_rel)
 
730
            from_dirname, from_tail_utf8 = osutils.split(from_rel_utf8)
 
731
            from_entry = self._get_entry(path=from_rel)
 
732
            if from_entry == (None, None):
 
733
                raise errors.BzrMoveFailedError(from_rel,to_dir,
 
734
                    errors.NotVersionedError(path=from_rel))
 
735
 
 
736
            from_id = from_entry[0][2]
 
737
            to_rel = pathjoin(to_dir, from_tail)
 
738
            to_rel_utf8 = pathjoin(to_dir_utf8, from_tail_utf8)
 
739
            item_to_entry = self._get_entry(path=to_rel)
 
740
            if item_to_entry != (None, None):
 
741
                raise errors.BzrMoveFailedError(from_rel, to_rel,
 
742
                    "Target is already versioned.")
 
743
 
 
744
            if from_rel == to_rel:
 
745
                raise errors.BzrMoveFailedError(from_rel, to_rel,
 
746
                    "Source and target are identical.")
 
747
 
 
748
            from_missing = not self.has_filename(from_rel)
 
749
            to_missing = not self.has_filename(to_rel)
 
750
            if after:
 
751
                move_file = False
 
752
            else:
 
753
                move_file = True
 
754
            if to_missing:
 
755
                if not move_file:
 
756
                    raise errors.BzrMoveFailedError(from_rel, to_rel,
 
757
                        errors.NoSuchFile(path=to_rel,
 
758
                        extra="New file has not been created yet"))
 
759
                elif from_missing:
 
760
                    # neither path exists
 
761
                    raise errors.BzrRenameFailedError(from_rel, to_rel,
 
762
                        errors.PathsDoNotExist(paths=(from_rel, to_rel)))
 
763
            else:
 
764
                if from_missing: # implicitly just update our path mapping
753
765
                    move_file = False
754
 
                else:
755
 
                    move_file = True
756
 
                if to_missing:
757
 
                    if not move_file:
758
 
                        raise errors.BzrMoveFailedError(from_rel, to_rel,
759
 
                            errors.NoSuchFile(path=to_rel,
760
 
                            extra="New file has not been created yet"))
761
 
                    elif from_missing:
762
 
                        # neither path exists
763
 
                        raise errors.BzrRenameFailedError(from_rel, to_rel,
764
 
                            errors.PathsDoNotExist(paths=(from_rel, to_rel)))
765
 
                else:
766
 
                    if from_missing: # implicitly just update our path mapping
767
 
                        move_file = False
768
 
                    elif not after:
769
 
                        raise errors.RenameFailedFilesExist(from_rel, to_rel)
 
766
                elif not after:
 
767
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
770
768
 
771
 
                # perform the disk move first - its the most likely failure point.
772
 
                if move_file:
773
 
                    from_rel_abs = self.abspath(from_rel)
774
 
                    to_rel_abs = self.abspath(to_rel)
 
769
            rollbacks = []
 
770
            def rollback_rename():
 
771
                """A single rename has failed, roll it back."""
 
772
                # roll back everything, even if we encounter trouble doing one
 
773
                # of them.
 
774
                #
 
775
                # TODO: at least log the other exceptions rather than just
 
776
                # losing them mbp 20070307
 
777
                exc_info = None
 
778
                for rollback in reversed(rollbacks):
775
779
                    try:
776
 
                        osutils.rename(from_rel_abs, to_rel_abs)
777
 
                    except OSError as e:
778
 
                        raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
779
 
                    rollbacks.add_cleanup(osutils.rename, to_rel_abs, from_rel_abs)
 
780
                        rollback()
 
781
                    except Exception, e:
 
782
                        exc_info = sys.exc_info()
 
783
                if exc_info:
 
784
                    raise exc_info[0], exc_info[1], exc_info[2]
 
785
 
 
786
            # perform the disk move first - its the most likely failure point.
 
787
            if move_file:
 
788
                from_rel_abs = self.abspath(from_rel)
 
789
                to_rel_abs = self.abspath(to_rel)
780
790
                try:
781
 
                    # perform the rename in the inventory next if needed: its easy
782
 
                    # to rollback
783
 
                    if update_inventory:
784
 
                        # rename the entry
785
 
                        from_entry = inv.get_entry(from_id)
786
 
                        current_parent = from_entry.parent_id
787
 
                        inv.rename(from_id, to_dir_id, from_tail)
788
 
                        rollbacks.add_cleanup(
789
 
                            inv.rename, from_id, current_parent, from_tail)
790
 
                    # finally do the rename in the dirstate, which is a little
791
 
                    # tricky to rollback, but least likely to need it.
792
 
                    old_block_index, old_entry_index, dir_present, file_present = \
793
 
                        state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
794
 
                    old_block = state._dirblocks[old_block_index][1]
795
 
                    old_entry = old_block[old_entry_index]
796
 
                    from_key, old_entry_details = old_entry
797
 
                    cur_details = old_entry_details[0]
798
 
                    # remove the old row
799
 
                    to_key = ((to_block[0],) + from_key[1:3])
800
 
                    minikind = cur_details[0]
801
 
                    move_one(old_entry, from_path_utf8=from_rel_utf8,
802
 
                             minikind=minikind,
803
 
                             executable=cur_details[3],
804
 
                             fingerprint=cur_details[1],
805
 
                             packed_stat=cur_details[4],
806
 
                             size=cur_details[2],
807
 
                             to_block=to_block,
808
 
                             to_key=to_key,
809
 
                             to_path_utf8=to_rel_utf8)
810
 
 
811
 
                    if minikind == b'd':
812
 
                        def update_dirblock(from_dir, to_key, to_dir_utf8):
813
 
                            """Recursively update all entries in this dirblock."""
814
 
                            if from_dir == b'':
815
 
                                raise AssertionError("renaming root not supported")
816
 
                            from_key = (from_dir, '')
817
 
                            from_block_idx, present = \
818
 
                                state._find_block_index_from_key(from_key)
819
 
                            if not present:
820
 
                                # This is the old record, if it isn't present, then
821
 
                                # there is theoretically nothing to update.
822
 
                                # (Unless it isn't present because of lazy loading,
823
 
                                # but we don't do that yet)
824
 
                                return
825
 
                            from_block = state._dirblocks[from_block_idx]
826
 
                            to_block_index, to_entry_index, _, _ = \
827
 
                                state._get_block_entry_index(to_key[0], to_key[1], 0)
828
 
                            to_block_index = state._ensure_block(
829
 
                                to_block_index, to_entry_index, to_dir_utf8)
830
 
                            to_block = state._dirblocks[to_block_index]
831
 
 
832
 
                            # Grab a copy since move_one may update the list.
833
 
                            for entry in from_block[1][:]:
834
 
                                if not (entry[0][0] == from_dir):
835
 
                                    raise AssertionError()
836
 
                                cur_details = entry[1][0]
837
 
                                to_key = (to_dir_utf8, entry[0][1], entry[0][2])
838
 
                                from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
839
 
                                to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
840
 
                                minikind = cur_details[0]
841
 
                                if minikind in (b'a', b'r'):
842
 
                                    # Deleted children of a renamed directory
843
 
                                    # Do not need to be updated.
844
 
                                    # Children that have been renamed out of this
845
 
                                    # directory should also not be updated
846
 
                                    continue
847
 
                                move_one(entry, from_path_utf8=from_path_utf8,
848
 
                                         minikind=minikind,
849
 
                                         executable=cur_details[3],
850
 
                                         fingerprint=cur_details[1],
851
 
                                         packed_stat=cur_details[4],
852
 
                                         size=cur_details[2],
853
 
                                         to_block=to_block,
854
 
                                         to_key=to_key,
855
 
                                         to_path_utf8=to_path_utf8)
856
 
                                if minikind == b'd':
857
 
                                    # We need to move all the children of this
858
 
                                    # entry
859
 
                                    update_dirblock(from_path_utf8, to_key,
860
 
                                                    to_path_utf8)
861
 
                        update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
862
 
                except:
863
 
                    rollbacks.cleanup_now()
864
 
                    raise
865
 
                result.append((from_rel, to_rel))
866
 
                state._mark_modified()
867
 
                self._make_dirty(reset_inventory=False)
868
 
 
869
 
            return result
 
791
                    osutils.rename(from_rel_abs, to_rel_abs)
 
792
                except OSError, e:
 
793
                    raise errors.BzrMoveFailedError(from_rel, to_rel, e[1])
 
794
                rollbacks.append(lambda: osutils.rename(to_rel_abs, from_rel_abs))
 
795
            try:
 
796
                # perform the rename in the inventory next if needed: its easy
 
797
                # to rollback
 
798
                if update_inventory:
 
799
                    # rename the entry
 
800
                    from_entry = inv[from_id]
 
801
                    current_parent = from_entry.parent_id
 
802
                    inv.rename(from_id, to_dir_id, from_tail)
 
803
                    rollbacks.append(
 
804
                        lambda: inv.rename(from_id, current_parent, from_tail))
 
805
                # finally do the rename in the dirstate, which is a little
 
806
                # tricky to rollback, but least likely to need it.
 
807
                old_block_index, old_entry_index, dir_present, file_present = \
 
808
                    state._get_block_entry_index(from_dirname, from_tail_utf8, 0)
 
809
                old_block = state._dirblocks[old_block_index][1]
 
810
                old_entry = old_block[old_entry_index]
 
811
                from_key, old_entry_details = old_entry
 
812
                cur_details = old_entry_details[0]
 
813
                # remove the old row
 
814
                to_key = ((to_block[0],) + from_key[1:3])
 
815
                minikind = cur_details[0]
 
816
                move_one(old_entry, from_path_utf8=from_rel_utf8,
 
817
                         minikind=minikind,
 
818
                         executable=cur_details[3],
 
819
                         fingerprint=cur_details[1],
 
820
                         packed_stat=cur_details[4],
 
821
                         size=cur_details[2],
 
822
                         to_block=to_block,
 
823
                         to_key=to_key,
 
824
                         to_path_utf8=to_rel_utf8)
 
825
 
 
826
                if minikind == 'd':
 
827
                    def update_dirblock(from_dir, to_key, to_dir_utf8):
 
828
                        """Recursively update all entries in this dirblock."""
 
829
                        if from_dir == '':
 
830
                            raise AssertionError("renaming root not supported")
 
831
                        from_key = (from_dir, '')
 
832
                        from_block_idx, present = \
 
833
                            state._find_block_index_from_key(from_key)
 
834
                        if not present:
 
835
                            # This is the old record, if it isn't present, then
 
836
                            # there is theoretically nothing to update.
 
837
                            # (Unless it isn't present because of lazy loading,
 
838
                            # but we don't do that yet)
 
839
                            return
 
840
                        from_block = state._dirblocks[from_block_idx]
 
841
                        to_block_index, to_entry_index, _, _ = \
 
842
                            state._get_block_entry_index(to_key[0], to_key[1], 0)
 
843
                        to_block_index = state._ensure_block(
 
844
                            to_block_index, to_entry_index, to_dir_utf8)
 
845
                        to_block = state._dirblocks[to_block_index]
 
846
 
 
847
                        # Grab a copy since move_one may update the list.
 
848
                        for entry in from_block[1][:]:
 
849
                            if not (entry[0][0] == from_dir):
 
850
                                raise AssertionError()
 
851
                            cur_details = entry[1][0]
 
852
                            to_key = (to_dir_utf8, entry[0][1], entry[0][2])
 
853
                            from_path_utf8 = osutils.pathjoin(entry[0][0], entry[0][1])
 
854
                            to_path_utf8 = osutils.pathjoin(to_dir_utf8, entry[0][1])
 
855
                            minikind = cur_details[0]
 
856
                            if minikind in 'ar':
 
857
                                # Deleted children of a renamed directory
 
858
                                # Do not need to be updated.
 
859
                                # Children that have been renamed out of this
 
860
                                # directory should also not be updated
 
861
                                continue
 
862
                            move_one(entry, from_path_utf8=from_path_utf8,
 
863
                                     minikind=minikind,
 
864
                                     executable=cur_details[3],
 
865
                                     fingerprint=cur_details[1],
 
866
                                     packed_stat=cur_details[4],
 
867
                                     size=cur_details[2],
 
868
                                     to_block=to_block,
 
869
                                     to_key=to_key,
 
870
                                     to_path_utf8=to_path_utf8)
 
871
                            if minikind == 'd':
 
872
                                # We need to move all the children of this
 
873
                                # entry
 
874
                                update_dirblock(from_path_utf8, to_key,
 
875
                                                to_path_utf8)
 
876
                    update_dirblock(from_rel_utf8, to_key, to_rel_utf8)
 
877
            except:
 
878
                rollback_rename()
 
879
                raise
 
880
            result.append((from_rel, to_rel))
 
881
            state._mark_modified()
 
882
            self._make_dirty(reset_inventory=False)
 
883
 
 
884
        return result
870
885
 
871
886
    def _must_be_locked(self):
872
887
        if not self._control_files._lock_count:
876
891
        """Initialize the state in this tree to be a new tree."""
877
892
        self._dirty = True
878
893
 
 
894
    @needs_read_lock
879
895
    def path2id(self, path):
880
896
        """Return the id for path in this tree."""
881
 
        with self.lock_read():
882
 
            if isinstance(path, list):
883
 
                if path == []:
884
 
                    path = [""]
885
 
                path = osutils.pathjoin(*path)
886
 
            path = path.strip('/')
887
 
            entry = self._get_entry(path=path)
888
 
            if entry == (None, None):
889
 
                return None
890
 
            return entry[0][2]
 
897
        if isinstance(path, list):
 
898
            if path == []:
 
899
                path = [""]
 
900
            path = osutils.pathjoin(*path)
 
901
        path = path.strip('/')
 
902
        entry = self._get_entry(path=path)
 
903
        if entry == (None, None):
 
904
            return None
 
905
        return entry[0][2]
891
906
 
892
907
    def paths2ids(self, paths, trees=[], require_versioned=True):
893
908
        """See Tree.paths2ids().
913
928
        # -- get the state object and prepare it.
914
929
        state = self.current_dirstate()
915
930
        if False and (state._dirblock_state == dirstate.DirState.NOT_IN_MEMORY
916
 
            and b'' not in paths):
 
931
            and '' not in paths):
917
932
            paths2ids = self._paths2ids_using_bisect
918
933
        else:
919
934
            paths2ids = self._paths2ids_in_memory
928
943
            """Return a list with all the entries that match path for all ids.
929
944
            """
930
945
            dirname, basename = os.path.split(path)
931
 
            key = (dirname, basename, b'')
 
946
            key = (dirname, basename, '')
932
947
            block_index, present = state._find_block_index_from_key(key)
933
948
            if not present:
934
949
                # the block which should contain path is absent.
956
971
                for entry in path_entries:
957
972
                    # for each tree.
958
973
                    for index in search_indexes:
959
 
                        if entry[1][index][0] != b'a': # absent
 
974
                        if entry[1][index][0] != 'a': # absent
960
975
                            found_versioned = True
961
976
                            # all good: found a versioned cell
962
977
                            break
985
1000
            nothing. Otherwise add the id to found_ids.
986
1001
            """
987
1002
            for index in search_indexes:
988
 
                if entry[1][index][0] == b'r': # relocated
 
1003
                if entry[1][index][0] == 'r': # relocated
989
1004
                    if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
990
1005
                        search_paths.add(entry[1][index][1])
991
 
                elif entry[1][index][0] != b'a': # absent
 
1006
                elif entry[1][index][0] != 'a': # absent
992
1007
                    found_ids.add(entry[0][2])
993
1008
        while search_paths:
994
1009
            current_root = search_paths.pop()
1001
1016
                continue
1002
1017
            for entry in root_entries:
1003
1018
                _process_entry(entry)
1004
 
            initial_key = (current_root, b'', b'')
 
1019
            initial_key = (current_root, '', '')
1005
1020
            block_index, _ = state._find_block_index_from_key(initial_key)
1006
1021
            while (block_index < len(state._dirblocks) and
1007
1022
                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
1025
1040
                    raise errors.PathsNotVersionedError(
1026
1041
                        [p.decode('utf-8') for p in paths])
1027
1042
 
1028
 
        for dir_name_id, trees_info in viewitems(found):
 
1043
        for dir_name_id, trees_info in found.iteritems():
1029
1044
            for index in search_indexes:
1030
 
                if trees_info[index][0] not in (b'r', b'a'):
 
1045
                if trees_info[index][0] not in ('r', 'a'):
1031
1046
                    found_ids.add(dir_name_id[2])
1032
1047
        return found_ids
1033
1048
 
1038
1053
        """
1039
1054
        return self.root_inventory
1040
1055
 
 
1056
    @needs_read_lock
1041
1057
    def revision_tree(self, revision_id):
1042
1058
        """See Tree.revision_tree.
1043
1059
 
1044
1060
        WorkingTree4 supplies revision_trees for any basis tree.
1045
1061
        """
1046
 
        with self.lock_read():
1047
 
            dirstate = self.current_dirstate()
1048
 
            parent_ids = dirstate.get_parent_ids()
1049
 
            if revision_id not in parent_ids:
1050
 
                raise errors.NoSuchRevisionInTree(self, revision_id)
1051
 
            if revision_id in dirstate.get_ghosts():
1052
 
                raise errors.NoSuchRevisionInTree(self, revision_id)
1053
 
            return DirStateRevisionTree(dirstate, revision_id,
1054
 
                self.branch.repository)
 
1062
        dirstate = self.current_dirstate()
 
1063
        parent_ids = dirstate.get_parent_ids()
 
1064
        if revision_id not in parent_ids:
 
1065
            raise errors.NoSuchRevisionInTree(self, revision_id)
 
1066
        if revision_id in dirstate.get_ghosts():
 
1067
            raise errors.NoSuchRevisionInTree(self, revision_id)
 
1068
        return DirStateRevisionTree(dirstate, revision_id,
 
1069
            self.branch.repository)
1055
1070
 
 
1071
    @needs_tree_write_lock
1056
1072
    def set_last_revision(self, new_revision):
1057
1073
        """Change the last revision in the working tree."""
1058
 
        with self.lock_tree_write():
1059
 
            parents = self.get_parent_ids()
1060
 
            if new_revision in (_mod_revision.NULL_REVISION, None):
1061
 
                if len(parents) >= 2:
1062
 
                    raise AssertionError(
1063
 
                        "setting the last parent to none with a pending merge "
1064
 
                        "is unsupported.")
1065
 
                self.set_parent_ids([])
1066
 
            else:
1067
 
                self.set_parent_ids(
1068
 
                        [new_revision] + parents[1:],
1069
 
                        allow_leftmost_as_ghost=True)
 
1074
        parents = self.get_parent_ids()
 
1075
        if new_revision in (_mod_revision.NULL_REVISION, None):
 
1076
            if len(parents) >= 2:
 
1077
                raise AssertionError(
 
1078
                    "setting the last parent to none with a pending merge is "
 
1079
                    "unsupported.")
 
1080
            self.set_parent_ids([])
 
1081
        else:
 
1082
            self.set_parent_ids([new_revision] + parents[1:],
 
1083
                allow_leftmost_as_ghost=True)
1070
1084
 
 
1085
    @needs_tree_write_lock
1071
1086
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
1072
1087
        """Set the parent ids to revision_ids.
1073
1088
 
1080
1095
        :param revision_ids: The revision_ids to set as the parent ids of this
1081
1096
            working tree. Any of these may be ghosts.
1082
1097
        """
1083
 
        with self.lock_tree_write():
1084
 
            trees = []
1085
 
            for revision_id in revision_ids:
1086
 
                try:
1087
 
                    revtree = self.branch.repository.revision_tree(revision_id)
1088
 
                    # TODO: jam 20070213 KnitVersionedFile raises
1089
 
                    #       RevisionNotPresent rather than NoSuchRevision if a
1090
 
                    #       given revision_id is not present. Should Repository be
1091
 
                    #       catching it and re-raising NoSuchRevision?
1092
 
                except (errors.NoSuchRevision, errors.RevisionNotPresent):
1093
 
                    revtree = None
1094
 
                trees.append((revision_id, revtree))
1095
 
            self.set_parent_trees(
1096
 
                trees, allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
1098
        trees = []
 
1099
        for revision_id in revision_ids:
 
1100
            try:
 
1101
                revtree = self.branch.repository.revision_tree(revision_id)
 
1102
                # TODO: jam 20070213 KnitVersionedFile raises
 
1103
                #       RevisionNotPresent rather than NoSuchRevision if a
 
1104
                #       given revision_id is not present. Should Repository be
 
1105
                #       catching it and re-raising NoSuchRevision?
 
1106
            except (errors.NoSuchRevision, errors.RevisionNotPresent):
 
1107
                revtree = None
 
1108
            trees.append((revision_id, revtree))
 
1109
        self.set_parent_trees(trees,
 
1110
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
1097
1111
 
 
1112
    @needs_tree_write_lock
1098
1113
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
1099
1114
        """Set the parents of the working tree.
1100
1115
 
1102
1117
            If tree is None, then that element is treated as an unreachable
1103
1118
            parent tree - i.e. a ghost.
1104
1119
        """
1105
 
        with self.lock_tree_write():
1106
 
            dirstate = self.current_dirstate()
1107
 
            if len(parents_list) > 0:
1108
 
                if not allow_leftmost_as_ghost and parents_list[0][1] is None:
1109
 
                    raise errors.GhostRevisionUnusableHere(parents_list[0][0])
1110
 
            real_trees = []
1111
 
            ghosts = []
1112
 
 
1113
 
            parent_ids = [rev_id for rev_id, tree in parents_list]
1114
 
            graph = self.branch.repository.get_graph()
1115
 
            heads = graph.heads(parent_ids)
1116
 
            accepted_revisions = set()
1117
 
 
1118
 
            # convert absent trees to the null tree, which we convert back to
1119
 
            # missing on access.
1120
 
            for rev_id, tree in parents_list:
1121
 
                if len(accepted_revisions) > 0:
1122
 
                    # we always accept the first tree
1123
 
                    if rev_id in accepted_revisions or rev_id not in heads:
1124
 
                        # We have already included either this tree, or its
1125
 
                        # descendent, so we skip it.
1126
 
                        continue
1127
 
                _mod_revision.check_not_reserved_id(rev_id)
1128
 
                if tree is not None:
1129
 
                    real_trees.append((rev_id, tree))
1130
 
                else:
1131
 
                    real_trees.append((rev_id,
1132
 
                        self.branch.repository.revision_tree(
1133
 
                            _mod_revision.NULL_REVISION)))
1134
 
                    ghosts.append(rev_id)
1135
 
                accepted_revisions.add(rev_id)
1136
 
            updated = False
1137
 
            if (len(real_trees) == 1
1138
 
                and not ghosts
1139
 
                and self.branch.repository._format.fast_deltas
1140
 
                and isinstance(real_trees[0][1], InventoryRevisionTree)
1141
 
                and self.get_parent_ids()):
1142
 
                rev_id, rev_tree = real_trees[0]
1143
 
                basis_id = self.get_parent_ids()[0]
1144
 
                # There are times when basis_tree won't be in
1145
 
                # self.branch.repository, (switch, for example)
1146
 
                try:
1147
 
                    basis_tree = self.branch.repository.revision_tree(basis_id)
1148
 
                except errors.NoSuchRevision:
1149
 
                    # Fall back to the set_parent_trees(), since we can't use
1150
 
                    # _make_delta if we can't get the RevisionTree
1151
 
                    pass
1152
 
                else:
1153
 
                    delta = rev_tree.root_inventory._make_delta(
1154
 
                        basis_tree.root_inventory)
1155
 
                    dirstate.update_basis_by_delta(delta, rev_id)
1156
 
                    updated = True
1157
 
            if not updated:
1158
 
                dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1159
 
            self._make_dirty(reset_inventory=False)
 
1120
        dirstate = self.current_dirstate()
 
1121
        if len(parents_list) > 0:
 
1122
            if not allow_leftmost_as_ghost and parents_list[0][1] is None:
 
1123
                raise errors.GhostRevisionUnusableHere(parents_list[0][0])
 
1124
        real_trees = []
 
1125
        ghosts = []
 
1126
 
 
1127
        parent_ids = [rev_id for rev_id, tree in parents_list]
 
1128
        graph = self.branch.repository.get_graph()
 
1129
        heads = graph.heads(parent_ids)
 
1130
        accepted_revisions = set()
 
1131
 
 
1132
        # convert absent trees to the null tree, which we convert back to
 
1133
        # missing on access.
 
1134
        for rev_id, tree in parents_list:
 
1135
            if len(accepted_revisions) > 0:
 
1136
                # we always accept the first tree
 
1137
                if rev_id in accepted_revisions or rev_id not in heads:
 
1138
                    # We have already included either this tree, or its
 
1139
                    # descendent, so we skip it.
 
1140
                    continue
 
1141
            _mod_revision.check_not_reserved_id(rev_id)
 
1142
            if tree is not None:
 
1143
                real_trees.append((rev_id, tree))
 
1144
            else:
 
1145
                real_trees.append((rev_id,
 
1146
                    self.branch.repository.revision_tree(
 
1147
                        _mod_revision.NULL_REVISION)))
 
1148
                ghosts.append(rev_id)
 
1149
            accepted_revisions.add(rev_id)
 
1150
        updated = False
 
1151
        if (len(real_trees) == 1
 
1152
            and not ghosts
 
1153
            and self.branch.repository._format.fast_deltas
 
1154
            and isinstance(real_trees[0][1],
 
1155
                revisiontree.InventoryRevisionTree)
 
1156
            and self.get_parent_ids()):
 
1157
            rev_id, rev_tree = real_trees[0]
 
1158
            basis_id = self.get_parent_ids()[0]
 
1159
            # There are times when basis_tree won't be in
 
1160
            # self.branch.repository, (switch, for example)
 
1161
            try:
 
1162
                basis_tree = self.branch.repository.revision_tree(basis_id)
 
1163
            except errors.NoSuchRevision:
 
1164
                # Fall back to the set_parent_trees(), since we can't use
 
1165
                # _make_delta if we can't get the RevisionTree
 
1166
                pass
 
1167
            else:
 
1168
                delta = rev_tree.root_inventory._make_delta(
 
1169
                    basis_tree.root_inventory)
 
1170
                dirstate.update_basis_by_delta(delta, rev_id)
 
1171
                updated = True
 
1172
        if not updated:
 
1173
            dirstate.set_parent_trees(real_trees, ghosts=ghosts)
 
1174
        self._make_dirty(reset_inventory=False)
1160
1175
 
1161
1176
    def _set_root_id(self, file_id):
1162
1177
        """See WorkingTree.set_root_id."""
1163
1178
        state = self.current_dirstate()
1164
 
        state.set_path_id(b'', file_id)
 
1179
        state.set_path_id('', file_id)
1165
1180
        if state._dirblock_state == dirstate.DirState.IN_MEMORY_MODIFIED:
1166
1181
            self._make_dirty(reset_inventory=True)
1167
1182
 
1175
1190
        """
1176
1191
        return self.current_dirstate().sha1_from_stat(path, stat_result)
1177
1192
 
 
1193
    @needs_read_lock
1178
1194
    def supports_tree_reference(self):
1179
1195
        return self._repo_supports_tree_reference
1180
1196
 
1205
1221
        finally:
1206
1222
            self.branch.unlock()
1207
1223
 
1208
 
    def unversion(self, paths, file_ids=None):
1209
 
        """Remove the file ids in paths from the current versioned set.
 
1224
    @needs_tree_write_lock
 
1225
    def unversion(self, file_ids):
 
1226
        """Remove the file ids in file_ids from the current versioned set.
1210
1227
 
1211
1228
        When a file_id is unversioned, all of its children are automatically
1212
1229
        unversioned.
1213
1230
 
1214
 
        :param paths: The file ids to stop versioning.
 
1231
        :param file_ids: The file ids to stop versioning.
1215
1232
        :raises: NoSuchId if any fileid is not currently versioned.
1216
1233
        """
1217
 
        with self.lock_tree_write():
1218
 
            if not paths:
1219
 
                return
1220
 
            state = self.current_dirstate()
1221
 
            state._read_dirblocks_if_needed()
1222
 
            if file_ids is None:
1223
 
                file_ids = set()
1224
 
                for path in paths:
1225
 
                    file_id = self.path2id(path)
1226
 
                    if file_id is None:
1227
 
                        raise errors.NoSuchFile(self, path)
1228
 
                    file_ids.add(file_id)
1229
 
            ids_to_unversion = set(file_ids)
1230
 
            paths_to_unversion = set()
1231
 
            # sketch:
1232
 
            # check if the root is to be unversioned, if so, assert for now.
1233
 
            # walk the state marking unversioned things as absent.
1234
 
            # if there are any un-unversioned ids at the end, raise
1235
 
            for key, details in state._dirblocks[0][1]:
1236
 
                if (details[0][0] not in ('a', 'r') and # absent or relocated
1237
 
                    key[2] in ids_to_unversion):
1238
 
                    # I haven't written the code to unversion / yet - it should be
1239
 
                    # supported.
1240
 
                    raise errors.BzrError(
1241
 
                        'Unversioning the / is not currently supported')
1242
 
            block_index = 0
1243
 
            while block_index < len(state._dirblocks):
1244
 
                # process one directory at a time.
1245
 
                block = state._dirblocks[block_index]
1246
 
                # first check: is the path one to remove - it or its children
1247
 
                delete_block = False
1248
 
                for path in paths_to_unversion:
1249
 
                    if (block[0].startswith(path) and
1250
 
                        (len(block[0]) == len(path) or
1251
 
                         block[0][len(path)] == '/')):
1252
 
                        # this entire block should be deleted - its the block for a
1253
 
                        # path to unversion; or the child of one
1254
 
                        delete_block = True
1255
 
                        break
1256
 
                # TODO: trim paths_to_unversion as we pass by paths
1257
 
                if delete_block:
1258
 
                    # this block is to be deleted: process it.
1259
 
                    # TODO: we can special case the no-parents case and
1260
 
                    # just forget the whole block.
1261
 
                    entry_index = 0
1262
 
                    while entry_index < len(block[1]):
1263
 
                        entry = block[1][entry_index]
1264
 
                        if entry[1][0][0] in 'ar':
1265
 
                            # don't remove absent or renamed entries
1266
 
                            entry_index += 1
1267
 
                        else:
1268
 
                            # Mark this file id as having been removed
1269
 
                            ids_to_unversion.discard(entry[0][2])
1270
 
                            if not state._make_absent(entry):
1271
 
                                # The block has not shrunk.
1272
 
                                entry_index += 1
1273
 
                    # go to the next block. (At the moment we dont delete empty
1274
 
                    # dirblocks)
1275
 
                    block_index += 1
1276
 
                    continue
 
1234
        if not file_ids:
 
1235
            return
 
1236
        state = self.current_dirstate()
 
1237
        state._read_dirblocks_if_needed()
 
1238
        ids_to_unversion = set(file_ids)
 
1239
        paths_to_unversion = set()
 
1240
        # sketch:
 
1241
        # check if the root is to be unversioned, if so, assert for now.
 
1242
        # walk the state marking unversioned things as absent.
 
1243
        # if there are any un-unversioned ids at the end, raise
 
1244
        for key, details in state._dirblocks[0][1]:
 
1245
            if (details[0][0] not in ('a', 'r') and # absent or relocated
 
1246
                key[2] in ids_to_unversion):
 
1247
                # I haven't written the code to unversion / yet - it should be
 
1248
                # supported.
 
1249
                raise errors.BzrError('Unversioning the / is not currently supported')
 
1250
        block_index = 0
 
1251
        while block_index < len(state._dirblocks):
 
1252
            # process one directory at a time.
 
1253
            block = state._dirblocks[block_index]
 
1254
            # first check: is the path one to remove - it or its children
 
1255
            delete_block = False
 
1256
            for path in paths_to_unversion:
 
1257
                if (block[0].startswith(path) and
 
1258
                    (len(block[0]) == len(path) or
 
1259
                     block[0][len(path)] == '/')):
 
1260
                    # this entire block should be deleted - its the block for a
 
1261
                    # path to unversion; or the child of one
 
1262
                    delete_block = True
 
1263
                    break
 
1264
            # TODO: trim paths_to_unversion as we pass by paths
 
1265
            if delete_block:
 
1266
                # this block is to be deleted: process it.
 
1267
                # TODO: we can special case the no-parents case and
 
1268
                # just forget the whole block.
1277
1269
                entry_index = 0
1278
1270
                while entry_index < len(block[1]):
1279
1271
                    entry = block[1][entry_index]
1280
 
                    if (entry[1][0][0] in ('a', 'r') or # absent, relocated
1281
 
                        # ^ some parent row.
1282
 
                        entry[0][2] not in ids_to_unversion):
1283
 
                        # ^ not an id to unversion
1284
 
                        entry_index += 1
1285
 
                        continue
1286
 
                    if entry[1][0][0] == 'd':
1287
 
                        paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
1288
 
                    if not state._make_absent(entry):
1289
 
                        entry_index += 1
1290
 
                    # we have unversioned this id
1291
 
                    ids_to_unversion.remove(entry[0][2])
 
1272
                    if entry[1][0][0] in 'ar':
 
1273
                        # don't remove absent or renamed entries
 
1274
                        entry_index += 1
 
1275
                    else:
 
1276
                        # Mark this file id as having been removed
 
1277
                        ids_to_unversion.discard(entry[0][2])
 
1278
                        if not state._make_absent(entry):
 
1279
                            # The block has not shrunk.
 
1280
                            entry_index += 1
 
1281
                # go to the next block. (At the moment we dont delete empty
 
1282
                # dirblocks)
1292
1283
                block_index += 1
1293
 
            if ids_to_unversion:
1294
 
                raise errors.NoSuchId(self, next(iter(ids_to_unversion)))
1295
 
            self._make_dirty(reset_inventory=False)
1296
 
            # have to change the legacy inventory too.
1297
 
            if self._inventory is not None:
1298
 
                for file_id in file_ids:
1299
 
                    if self._inventory.has_id(file_id):
1300
 
                        self._inventory.remove_recursive_id(file_id)
 
1284
                continue
 
1285
            entry_index = 0
 
1286
            while entry_index < len(block[1]):
 
1287
                entry = block[1][entry_index]
 
1288
                if (entry[1][0][0] in ('a', 'r') or # absent, relocated
 
1289
                    # ^ some parent row.
 
1290
                    entry[0][2] not in ids_to_unversion):
 
1291
                    # ^ not an id to unversion
 
1292
                    entry_index += 1
 
1293
                    continue
 
1294
                if entry[1][0][0] == 'd':
 
1295
                    paths_to_unversion.add(pathjoin(entry[0][0], entry[0][1]))
 
1296
                if not state._make_absent(entry):
 
1297
                    entry_index += 1
 
1298
                # we have unversioned this id
 
1299
                ids_to_unversion.remove(entry[0][2])
 
1300
            block_index += 1
 
1301
        if ids_to_unversion:
 
1302
            raise errors.NoSuchId(self, iter(ids_to_unversion).next())
 
1303
        self._make_dirty(reset_inventory=False)
 
1304
        # have to change the legacy inventory too.
 
1305
        if self._inventory is not None:
 
1306
            for file_id in file_ids:
 
1307
                if self._inventory.has_id(file_id):
 
1308
                    self._inventory.remove_recursive_id(file_id)
1301
1309
 
 
1310
    @needs_tree_write_lock
1302
1311
    def rename_one(self, from_rel, to_rel, after=False):
1303
1312
        """See WorkingTree.rename_one"""
1304
 
        with self.lock_tree_write():
1305
 
            self.flush()
1306
 
            super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
 
1313
        self.flush()
 
1314
        super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1307
1315
 
 
1316
    @needs_tree_write_lock
1308
1317
    def apply_inventory_delta(self, changes):
1309
1318
        """See MutableTree.apply_inventory_delta"""
1310
 
        with self.lock_tree_write():
1311
 
            state = self.current_dirstate()
1312
 
            state.update_by_delta(changes)
1313
 
            self._make_dirty(reset_inventory=True)
 
1319
        state = self.current_dirstate()
 
1320
        state.update_by_delta(changes)
 
1321
        self._make_dirty(reset_inventory=True)
1314
1322
 
1315
1323
    def update_basis_by_delta(self, new_revid, delta):
1316
1324
        """See MutableTree.update_basis_by_delta."""
1318
1326
            raise AssertionError()
1319
1327
        self.current_dirstate().update_basis_by_delta(delta, new_revid)
1320
1328
 
 
1329
    @needs_read_lock
1321
1330
    def _validate(self):
1322
 
        with self.lock_read():
1323
 
            self._dirstate._validate()
 
1331
        self._dirstate._validate()
1324
1332
 
 
1333
    @needs_tree_write_lock
1325
1334
    def _write_inventory(self, inv):
1326
1335
        """Write inventory as the current inventory."""
1327
1336
        if self._dirty:
1328
1337
            raise AssertionError("attempting to write an inventory when the "
1329
1338
                "dirstate is dirty will lose pending changes")
1330
 
        with self.lock_tree_write():
1331
 
            had_inventory = self._inventory is not None
1332
 
            # Setting self._inventory = None forces the dirstate to regenerate the
1333
 
            # working inventory. We do this because self.inventory may be inv, or
1334
 
            # may have been modified, and either case would prevent a clean delta
1335
 
            # being created.
1336
 
            self._inventory = None
1337
 
            # generate a delta,
1338
 
            delta = inv._make_delta(self.root_inventory)
1339
 
            # and apply it.
1340
 
            self.apply_inventory_delta(delta)
1341
 
            if had_inventory:
1342
 
                self._inventory = inv
1343
 
            self.flush()
 
1339
        had_inventory = self._inventory is not None
 
1340
        # Setting self._inventory = None forces the dirstate to regenerate the
 
1341
        # working inventory. We do this because self.inventory may be inv, or
 
1342
        # may have been modified, and either case would prevent a clean delta
 
1343
        # being created.
 
1344
        self._inventory = None
 
1345
        # generate a delta,
 
1346
        delta = inv._make_delta(self.root_inventory)
 
1347
        # and apply it.
 
1348
        self.apply_inventory_delta(delta)
 
1349
        if had_inventory:
 
1350
            self._inventory = inv
 
1351
        self.flush()
1344
1352
 
 
1353
    @needs_tree_write_lock
1345
1354
    def reset_state(self, revision_ids=None):
1346
1355
        """Reset the state of the working tree.
1347
1356
 
1348
1357
        This does a hard-reset to a last-known-good state. This is a way to
1349
1358
        fix if something got corrupted (like the .bzr/checkout/dirstate file)
1350
1359
        """
1351
 
        with self.lock_tree_write():
1352
 
            if revision_ids is None:
1353
 
                revision_ids = self.get_parent_ids()
1354
 
            if not revision_ids:
1355
 
                base_tree = self.branch.repository.revision_tree(
1356
 
                    _mod_revision.NULL_REVISION)
1357
 
                trees = []
1358
 
            else:
1359
 
                trees = list(zip(revision_ids,
1360
 
                            self.branch.repository.revision_trees(revision_ids)))
1361
 
                base_tree = trees[0][1]
1362
 
            state = self.current_dirstate()
1363
 
            # We don't support ghosts yet
1364
 
            state.set_state_from_scratch(base_tree.root_inventory, trees, [])
 
1360
        if revision_ids is None:
 
1361
            revision_ids = self.get_parent_ids()
 
1362
        if not revision_ids:
 
1363
            base_tree = self.branch.repository.revision_tree(
 
1364
                _mod_revision.NULL_REVISION)
 
1365
            trees = []
 
1366
        else:
 
1367
            trees = zip(revision_ids,
 
1368
                        self.branch.repository.revision_trees(revision_ids))
 
1369
            base_tree = trees[0][1]
 
1370
        state = self.current_dirstate()
 
1371
        # We don't support ghosts yet
 
1372
        state.set_state_from_scratch(base_tree.root_inventory, trees, [])
1365
1373
 
1366
1374
 
1367
1375
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1379
1387
        """See dirstate.SHA1Provider.stat_and_sha1()."""
1380
1388
        filters = self.tree._content_filter_stack(
1381
1389
            self.tree.relpath(osutils.safe_unicode(abspath)))
1382
 
        with file(abspath, 'rb', 65000) as file_obj:
 
1390
        file_obj = file(abspath, 'rb', 65000)
 
1391
        try:
1383
1392
            statvalue = os.fstat(file_obj.fileno())
1384
1393
            if filters:
1385
1394
                file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1386
1395
            sha1 = osutils.size_sha_file(file_obj)[1]
 
1396
        finally:
 
1397
            file_obj.close()
1387
1398
        return statvalue, sha1
1388
1399
 
1389
1400
 
1459
1470
    _lock_class = LockDir
1460
1471
    _lock_file_name = 'lock'
1461
1472
 
1462
 
    def _open_control_files(self, a_controldir):
1463
 
        transport = a_controldir.get_workingtree_transport(None)
 
1473
    def _open_control_files(self, a_bzrdir):
 
1474
        transport = a_bzrdir.get_workingtree_transport(None)
1464
1475
        return LockableFiles(transport, self._lock_file_name,
1465
1476
                             self._lock_class)
1466
1477
 
1467
 
    def initialize(self, a_controldir, revision_id=None, from_branch=None,
 
1478
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1468
1479
                   accelerator_tree=None, hardlink=False):
1469
1480
        """See WorkingTreeFormat.initialize().
1470
1481
 
1480
1491
        These trees get an initial random root id, if their repository supports
1481
1492
        rich root data, TREE_ROOT otherwise.
1482
1493
        """
1483
 
        if not isinstance(a_controldir.transport, LocalTransport):
1484
 
            raise errors.NotLocalUrl(a_controldir.transport.base)
1485
 
        transport = a_controldir.get_workingtree_transport(self)
1486
 
        control_files = self._open_control_files(a_controldir)
 
1494
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1495
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1496
        transport = a_bzrdir.get_workingtree_transport(self)
 
1497
        control_files = self._open_control_files(a_bzrdir)
1487
1498
        control_files.create_lock()
1488
1499
        control_files.lock_write()
1489
1500
        transport.put_bytes('format', self.as_string(),
1490
 
            mode=a_controldir._get_file_mode())
 
1501
            mode=a_bzrdir._get_file_mode())
1491
1502
        if from_branch is not None:
1492
1503
            branch = from_branch
1493
1504
        else:
1494
 
            branch = a_controldir.open_branch()
 
1505
            branch = a_bzrdir.open_branch()
1495
1506
        if revision_id is None:
1496
1507
            revision_id = branch.last_revision()
1497
1508
        local_path = transport.local_abspath('dirstate')
1499
1510
        state = dirstate.DirState.initialize(local_path)
1500
1511
        state.unlock()
1501
1512
        del state
1502
 
        wt = self._tree_class(a_controldir.root_transport.local_abspath('.'),
 
1513
        wt = self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
1503
1514
                         branch,
1504
1515
                         _format=self,
1505
 
                         _controldir=a_controldir,
 
1516
                         _bzrdir=a_bzrdir,
1506
1517
                         _control_files=control_files)
1507
1518
        wt._new_tree()
1508
1519
        wt.lock_tree_write()
1529
1540
                parents_list = []
1530
1541
            else:
1531
1542
                parents_list = [(revision_id, basis)]
1532
 
            with basis.lock_read():
 
1543
            basis.lock_read()
 
1544
            try:
1533
1545
                wt.set_parent_trees(parents_list, allow_leftmost_as_ghost=True)
1534
1546
                wt.flush()
1535
1547
                # if the basis has a root id we have to use that; otherwise we
1553
1565
                                     delta_from_tree=delta_from_tree)
1554
1566
                for hook in MutableTree.hooks['post_build_tree']:
1555
1567
                    hook(wt)
 
1568
            finally:
 
1569
                basis.unlock()
1556
1570
        finally:
1557
1571
            control_files.unlock()
1558
1572
            wt.unlock()
1567
1581
        :param wt: the WorkingTree object
1568
1582
        """
1569
1583
 
1570
 
    def open(self, a_controldir, _found=False):
1571
 
        """Return the WorkingTree object for a_controldir
 
1584
    def open(self, a_bzrdir, _found=False):
 
1585
        """Return the WorkingTree object for a_bzrdir
1572
1586
 
1573
1587
        _found is a private parameter, do not use it. It is used to indicate
1574
1588
               if format probing has already been done.
1576
1590
        if not _found:
1577
1591
            # we are being called directly and must probe.
1578
1592
            raise NotImplementedError
1579
 
        if not isinstance(a_controldir.transport, LocalTransport):
1580
 
            raise errors.NotLocalUrl(a_controldir.transport.base)
1581
 
        wt = self._open(a_controldir, self._open_control_files(a_controldir))
 
1593
        if not isinstance(a_bzrdir.transport, LocalTransport):
 
1594
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
 
1595
        wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
1582
1596
        return wt
1583
1597
 
1584
 
    def _open(self, a_controldir, control_files):
 
1598
    def _open(self, a_bzrdir, control_files):
1585
1599
        """Open the tree itself.
1586
1600
 
1587
 
        :param a_controldir: the dir for the tree.
 
1601
        :param a_bzrdir: the dir for the tree.
1588
1602
        :param control_files: the control files for the tree.
1589
1603
        """
1590
 
        return self._tree_class(a_controldir.root_transport.local_abspath('.'),
1591
 
                           branch=a_controldir.open_branch(),
 
1604
        return self._tree_class(a_bzrdir.root_transport.local_abspath('.'),
 
1605
                           branch=a_bzrdir.open_branch(),
1592
1606
                           _format=self,
1593
 
                           _controldir=a_controldir,
 
1607
                           _bzrdir=a_bzrdir,
1594
1608
                           _control_files=control_files)
1595
1609
 
1596
 
    def __get_matchingcontroldir(self):
1597
 
        return self._get_matchingcontroldir()
 
1610
    def __get_matchingbzrdir(self):
 
1611
        return self._get_matchingbzrdir()
1598
1612
 
1599
 
    def _get_matchingcontroldir(self):
 
1613
    def _get_matchingbzrdir(self):
1600
1614
        """Overrideable method to get a bzrdir for testing."""
1601
1615
        # please test against something that will let us do tree references
1602
 
        return controldir.format_registry.make_controldir(
 
1616
        return controldir.format_registry.make_bzrdir(
1603
1617
            'development-subtree')
1604
1618
 
1605
 
    _matchingcontroldir = property(__get_matchingcontroldir)
 
1619
    _matchingbzrdir = property(__get_matchingbzrdir)
1606
1620
 
1607
1621
 
1608
1622
class WorkingTreeFormat4(DirStateWorkingTreeFormat):
1624
1638
    @classmethod
1625
1639
    def get_format_string(cls):
1626
1640
        """See WorkingTreeFormat.get_format_string()."""
1627
 
        return b"Bazaar Working Tree Format 4 (bzr 0.15)\n"
 
1641
        return "Bazaar Working Tree Format 4 (bzr 0.15)\n"
1628
1642
 
1629
1643
    def get_format_description(self):
1630
1644
        """See WorkingTreeFormat.get_format_description()."""
1642
1656
    @classmethod
1643
1657
    def get_format_string(cls):
1644
1658
        """See WorkingTreeFormat.get_format_string()."""
1645
 
        return b"Bazaar Working Tree Format 5 (bzr 1.11)\n"
 
1659
        return "Bazaar Working Tree Format 5 (bzr 1.11)\n"
1646
1660
 
1647
1661
    def get_format_description(self):
1648
1662
        """See WorkingTreeFormat.get_format_description()."""
1663
1677
    @classmethod
1664
1678
    def get_format_string(cls):
1665
1679
        """See WorkingTreeFormat.get_format_string()."""
1666
 
        return b"Bazaar Working Tree Format 6 (bzr 1.14)\n"
 
1680
        return "Bazaar Working Tree Format 6 (bzr 1.14)\n"
1667
1681
 
1668
1682
    def get_format_description(self):
1669
1683
        """See WorkingTreeFormat.get_format_description()."""
1671
1685
 
1672
1686
    def _init_custom_control_files(self, wt):
1673
1687
        """Subclasses with custom control files should override this method."""
1674
 
        wt._transport.put_bytes('views', b'',
1675
 
            mode=wt.controldir._get_file_mode())
 
1688
        wt._transport.put_bytes('views', '', mode=wt.bzrdir._get_file_mode())
1676
1689
 
1677
1690
    def supports_content_filtering(self):
1678
1691
        return True
1680
1693
    def supports_views(self):
1681
1694
        return True
1682
1695
 
1683
 
    def _get_matchingcontroldir(self):
 
1696
    def _get_matchingbzrdir(self):
1684
1697
        """Overrideable method to get a bzrdir for testing."""
1685
1698
        # We use 'development-subtree' instead of '2a', because we have a
1686
1699
        # few tests that want to test tree references
1687
 
        return controldir.format_registry.make_controldir('development-subtree')
 
1700
        return bzrdir.format_registry.make_bzrdir('development-subtree')
1688
1701
 
1689
1702
 
1690
1703
class DirStateRevisionTree(InventoryTree):
1709
1722
        return "<%s of %s in %s>" % \
1710
1723
            (self.__class__.__name__, self._revision_id, self._dirstate)
1711
1724
 
1712
 
    def annotate_iter(self, path, file_id=None,
 
1725
    def annotate_iter(self, file_id,
1713
1726
                      default_revision=_mod_revision.CURRENT_REVISION):
1714
1727
        """See Tree.annotate_iter"""
1715
 
        if file_id is None:
1716
 
            file_id = self.path2id(path)
1717
 
        text_key = (file_id, self.get_file_revision(path, file_id))
 
1728
        text_key = (file_id, self.get_file_revision(file_id))
1718
1729
        annotations = self._repository.texts.annotate(text_key)
1719
1730
        return [(key[-1], line) for (key, line) in annotations]
1720
1731
 
1726
1737
        # sensible: the entry might not have come from us?
1727
1738
        return entry.kind, entry.executable, None
1728
1739
 
 
1740
    def _file_size(self, entry, stat_value):
 
1741
        return entry.text_size
 
1742
 
1729
1743
    def filter_unversioned_files(self, paths):
1730
1744
        """Filter out paths that are not versioned.
1731
1745
 
1799
1813
        # for the tree index use.
1800
1814
        root_key, current_entry = self._dirstate._get_entry(parent_index, path_utf8='')
1801
1815
        current_id = root_key[2]
1802
 
        if current_entry[parent_index][0] != b'd':
 
1816
        if current_entry[parent_index][0] != 'd':
1803
1817
            raise AssertionError()
1804
1818
        inv = Inventory(root_id=current_id, revision_id=self._revision_id)
1805
1819
        inv.root.revision = current_entry[parent_index][4]
1820
1834
                continue
1821
1835
            for key, entry in block[1]:
1822
1836
                minikind, fingerprint, size, executable, revid = entry[parent_index]
1823
 
                if minikind in (b'a', b'r'): # absent, relocated
 
1837
                if minikind in ('a', 'r'): # absent, relocated
1824
1838
                    # not this tree
1825
1839
                    continue
1826
1840
                name = key[1]
1835
1849
                    inv_entry.text_size = size
1836
1850
                    inv_entry.text_sha1 = fingerprint
1837
1851
                elif kind == 'directory':
1838
 
                    parent_ies[(dirname + b'/' + name).strip(b'/')] = inv_entry
 
1852
                    parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1839
1853
                elif kind == 'symlink':
1840
1854
                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1841
1855
                elif kind == 'tree-reference':
1854
1868
                parent_ie.children[name_unicode] = inv_entry
1855
1869
        self._inventory = inv
1856
1870
 
1857
 
    def get_file_mtime(self, path, file_id=None):
 
1871
    def get_file_mtime(self, file_id, path=None):
1858
1872
        """Return the modification time for this record.
1859
1873
 
1860
1874
        We return the timestamp of the last-changed revision.
1862
1876
        # Make sure the file exists
1863
1877
        entry = self._get_entry(file_id, path=path)
1864
1878
        if entry == (None, None): # do we raise?
1865
 
            raise errors.NoSuchFile(path)
 
1879
            raise errors.NoSuchId(self, file_id)
1866
1880
        parent_index = self._get_parent_index()
1867
1881
        last_changed_revision = entry[1][parent_index][4]
1868
1882
        try:
1869
1883
            rev = self._repository.get_revision(last_changed_revision)
1870
1884
        except errors.NoSuchRevision:
1871
 
            raise FileTimestampUnavailable(self.id2path(file_id))
 
1885
            raise errors.FileTimestampUnavailable(self.id2path(file_id))
1872
1886
        return rev.timestamp
1873
1887
 
1874
 
    def get_file_sha1(self, path, file_id=None, stat_value=None):
 
1888
    def get_file_sha1(self, file_id, path=None, stat_value=None):
1875
1889
        entry = self._get_entry(file_id=file_id, path=path)
1876
1890
        parent_index = self._get_parent_index()
1877
1891
        parent_details = entry[1][parent_index]
1878
 
        if parent_details[0] == b'f':
 
1892
        if parent_details[0] == 'f':
1879
1893
            return parent_details[1]
1880
1894
        return None
1881
1895
 
1882
 
    def get_file_revision(self, path, file_id=None):
1883
 
        with self.lock_read():
1884
 
            inv, inv_file_id = self._path2inv_file_id(path)
1885
 
            return inv.get_entry(inv_file_id).revision
1886
 
 
1887
 
    def get_file(self, path, file_id=None):
1888
 
        return BytesIO(self.get_file_text(path, file_id))
1889
 
 
1890
 
    def get_file_size(self, path, file_id=None):
 
1896
    @needs_read_lock
 
1897
    def get_file_revision(self, file_id):
 
1898
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1899
        return inv[inv_file_id].revision
 
1900
 
 
1901
    def get_file(self, file_id, path=None):
 
1902
        return StringIO(self.get_file_text(file_id))
 
1903
 
 
1904
    def get_file_size(self, file_id):
1891
1905
        """See Tree.get_file_size"""
1892
 
        inv, inv_file_id = self._path2inv_file_id(path)
1893
 
        return inv.get_entry(inv_file_id).text_size
 
1906
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1907
        return inv[inv_file_id].text_size
1894
1908
 
1895
 
    def get_file_text(self, path, file_id=None):
 
1909
    def get_file_text(self, file_id, path=None):
1896
1910
        content = None
1897
 
        for _, content_iter in self.iter_files_bytes([(path, None)]):
 
1911
        for _, content_iter in self.iter_files_bytes([(file_id, None)]):
1898
1912
            if content is not None:
1899
1913
                raise AssertionError('iter_files_bytes returned'
1900
1914
                    ' too many entries')
1906
1920
                ' the requested data')
1907
1921
        return content
1908
1922
 
1909
 
    def get_reference_revision(self, path, file_id=None):
1910
 
        inv, inv_file_id = self._path2inv_file_id(path)
1911
 
        return inv.get_entry(inv_file_id).reference_revision
 
1923
    def get_reference_revision(self, file_id, path=None):
 
1924
        inv, inv_file_id = self._unpack_file_id(file_id)
 
1925
        return inv[inv_file_id].reference_revision
1912
1926
 
1913
1927
    def iter_files_bytes(self, desired_files):
1914
1928
        """See Tree.iter_files_bytes.
1916
1930
        This version is implemented on top of Repository.iter_files_bytes"""
1917
1931
        parent_index = self._get_parent_index()
1918
1932
        repo_desired_files = []
1919
 
        for path, identifier in desired_files:
1920
 
            entry = self._get_entry(path=path)
 
1933
        for file_id, identifier in desired_files:
 
1934
            entry = self._get_entry(file_id)
1921
1935
            if entry == (None, None):
1922
 
                raise errors.NoSuchFile(self, path)
1923
 
            repo_desired_files.append((entry[0][2], entry[1][parent_index][4],
 
1936
                raise errors.NoSuchId(self, file_id)
 
1937
            repo_desired_files.append((file_id, entry[1][parent_index][4],
1924
1938
                                       identifier))
1925
1939
        return self._repository.iter_files_bytes(repo_desired_files)
1926
1940
 
1927
 
    def get_symlink_target(self, path, file_id=None):
1928
 
        entry = self._get_entry(file_id=file_id, path=path)
1929
 
        if entry is None:
1930
 
            raise errors.NoSuchId(tree=self, file_id=file_id)
 
1941
    def get_symlink_target(self, file_id, path=None):
 
1942
        entry = self._get_entry(file_id=file_id)
1931
1943
        parent_index = self._get_parent_index()
1932
 
        if entry[1][parent_index][0] != b'l':
 
1944
        if entry[1][parent_index][0] != 'l':
1933
1945
            return None
1934
1946
        else:
1935
1947
            target = entry[1][parent_index][1]
1950
1962
    root_inventory = property(_get_root_inventory,
1951
1963
                         doc="Inventory of this Tree")
1952
1964
 
 
1965
    @deprecated_method(deprecated_in((2, 5, 0)))
 
1966
    def _get_inventory(self):
 
1967
        return self.root_inventory
 
1968
 
 
1969
    inventory = property(_get_inventory,
 
1970
                         doc="Inventory of this Tree")
 
1971
 
1953
1972
    def get_parent_ids(self):
1954
1973
        """The parents of a tree in the dirstate are not cached."""
1955
1974
        return self._repository.get_revision(self._revision_id).parent_ids
1957
1976
    def has_filename(self, filename):
1958
1977
        return bool(self.path2id(filename))
1959
1978
 
1960
 
    def kind(self, path, file_id=None):
1961
 
        entry = self._get_entry(file_id=file_id, path=path)[1]
 
1979
    def kind(self, file_id):
 
1980
        entry = self._get_entry(file_id=file_id)[1]
1962
1981
        if entry is None:
1963
 
            raise errors.NoSuchFile(path)
 
1982
            raise errors.NoSuchId(tree=self, file_id=file_id)
1964
1983
        parent_index = self._get_parent_index()
1965
1984
        return dirstate.DirState._minikind_to_kind[entry[parent_index][0]]
1966
1985
 
1967
 
    def stored_kind(self, path, file_id=None):
 
1986
    def stored_kind(self, file_id):
1968
1987
        """See Tree.stored_kind"""
1969
 
        return self.kind(path, file_id)
 
1988
        return self.kind(file_id)
1970
1989
 
1971
1990
    def path_content_summary(self, path):
1972
1991
        """See Tree.path_content_summary."""
1973
1992
        inv, inv_file_id = self._path2inv_file_id(path)
1974
1993
        if inv_file_id is None:
1975
1994
            return ('missing', None, None, None)
1976
 
        entry = inv.get_entry(inv_file_id)
 
1995
        entry = inv[inv_file_id]
1977
1996
        kind = entry.kind
1978
1997
        if kind == 'file':
1979
1998
            return (kind, entry.text_size, entry.executable, entry.text_sha1)
1982
2001
        else:
1983
2002
            return (kind, None, None, None)
1984
2003
 
1985
 
    def is_executable(self, path, file_id=None):
1986
 
        inv, inv_file_id = self._path2inv_file_id(path)
1987
 
        ie = inv.get_entry(inv_file_id)
 
2004
    def is_executable(self, file_id, path=None):
 
2005
        inv, inv_file_id = self._unpack_file_id(file_id)
 
2006
        ie = inv[inv_file_id]
1988
2007
        if ie.kind != "file":
1989
2008
            return False
1990
2009
        return ie.executable
2006
2025
        # FIXME: Support nested trees
2007
2026
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
2008
2027
        if inv.root is not None and not include_root and from_dir is None:
2009
 
            next(entries)
 
2028
            entries.next()
2010
2029
        for path, entry in entries:
2011
2030
            yield path, 'V', entry.kind, entry.file_id, entry
2012
2031
 
2013
2032
    def lock_read(self):
2014
2033
        """Lock the tree for a set of operations.
2015
2034
 
2016
 
        :return: A breezy.lock.LogicalLockResult.
 
2035
        :return: A bzrlib.lock.LogicalLockResult.
2017
2036
        """
2018
2037
        if not self._locked:
2019
2038
            self._repository.lock_read()
2027
2046
        if not self._locked:
2028
2047
            raise errors.ObjectNotLocked(self)
2029
2048
 
 
2049
    @needs_read_lock
2030
2050
    def path2id(self, path):
2031
2051
        """Return the id for path in this tree."""
2032
2052
        # lookup by path: faster than splitting and walking the ivnentory.
2034
2054
            if path == []:
2035
2055
                path = [""]
2036
2056
            path = osutils.pathjoin(*path)
2037
 
        with self.lock_read():
2038
 
            entry = self._get_entry(path=path)
2039
 
            if entry == (None, None):
2040
 
                return None
2041
 
            return entry[0][2]
 
2057
        entry = self._get_entry(path=path)
 
2058
        if entry == (None, None):
 
2059
            return None
 
2060
        return entry[0][2]
2042
2061
 
2043
2062
    def unlock(self):
2044
2063
        """Unlock, freeing any cache memory used during the lock."""
2052
2071
                self._dirstate_locked = False
2053
2072
            self._repository.unlock()
2054
2073
 
 
2074
    @needs_read_lock
2055
2075
    def supports_tree_reference(self):
2056
 
        with self.lock_read():
2057
 
            return self._repo_supports_tree_reference
 
2076
        return self._repo_supports_tree_reference
2058
2077
 
2059
2078
    def walkdirs(self, prefix=""):
2060
2079
        # TODO: jam 20070215 This is the lazy way by using the RevisionTree
2078
2097
            else:
2079
2098
                relroot = ""
2080
2099
            # FIXME: stash the node in pending
2081
 
            entry = inv.get_entry(file_id)
 
2100
            entry = inv[file_id]
2082
2101
            for name, child in entry.sorted_children():
2083
2102
                toppath = relroot + name
2084
2103
                dirblock.append((toppath, name, child.kind, None,
2104
2123
    def __init__(self, source, target):
2105
2124
        super(InterDirStateTree, self).__init__(source, target)
2106
2125
        if not InterDirStateTree.is_compatible(source, target):
2107
 
            raise Exception("invalid source %r and target %r" % (source, target))
 
2126
            raise Exception, "invalid source %r and target %r" % (source, target)
2108
2127
 
2109
2128
    @staticmethod
2110
2129
    def make_source_parent_tree(source, target):
2123
2142
    @classmethod
2124
2143
    def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
2125
2144
                                                  target):
2126
 
        from ..tests.test__dirstate_helpers import \
 
2145
        from bzrlib.tests.test__dirstate_helpers import \
2127
2146
            compiled_dirstate_helpers_feature
2128
2147
        test_case.requireFeature(compiled_dirstate_helpers_feature)
2129
 
        from ._dirstate_helpers_pyx import ProcessEntryC
 
2148
        from bzrlib._dirstate_helpers_pyx import ProcessEntryC
2130
2149
        result = klass.make_source_parent_tree(source, target)
2131
2150
        result[1]._iter_changes = ProcessEntryC
2132
2151
        return result
2195
2214
                specific_files_utf8.add(path.encode('utf8'))
2196
2215
            specific_files = specific_files_utf8
2197
2216
        else:
2198
 
            specific_files = {b''}
 
2217
            specific_files = set([''])
2199
2218
        # -- specific_files is now a utf8 path set --
2200
2219
 
2201
2220
        # -- get the state object and prepare it.
2215
2234
                for entry in path_entries:
2216
2235
                    # for each tree.
2217
2236
                    for index in indices:
2218
 
                        if entry[1][index][0] != b'a': # absent
 
2237
                        if entry[1][index][0] != 'a': # absent
2219
2238
                            found_versioned = True
2220
2239
                            # all good: found a versioned cell
2221
2240
                            break
2275
2294
 
2276
2295
    def create_dirstate_data(self, tree):
2277
2296
        """Create the dirstate based data for tree."""
2278
 
        local_path = tree.controldir.get_workingtree_transport(None
 
2297
        local_path = tree.bzrdir.get_workingtree_transport(None
2279
2298
            ).local_abspath('dirstate')
2280
2299
        state = dirstate.DirState.from_tree(tree, local_path)
2281
2300
        state.save()
2283
2302
 
2284
2303
    def remove_xml_files(self, tree):
2285
2304
        """Remove the oldformat 3 data."""
2286
 
        transport = tree.controldir.get_workingtree_transport(None)
 
2305
        transport = tree.bzrdir.get_workingtree_transport(None)
2287
2306
        for path in ['basis-inventory-cache', 'inventory', 'last-revision',
2288
2307
            'pending-merges', 'stat-cache']:
2289
2308
            try:
2296
2315
        """Change the format marker."""
2297
2316
        tree._transport.put_bytes('format',
2298
2317
            self.target_format.as_string(),
2299
 
            mode=tree.controldir._get_file_mode())
 
2318
            mode=tree.bzrdir._get_file_mode())
2300
2319
 
2301
2320
 
2302
2321
class Converter4to5(object):
2319
2338
        """Change the format marker."""
2320
2339
        tree._transport.put_bytes('format',
2321
2340
            self.target_format.as_string(),
2322
 
            mode=tree.controldir._get_file_mode())
 
2341
            mode=tree.bzrdir._get_file_mode())
2323
2342
 
2324
2343
 
2325
2344
class Converter4or5to6(object):
2341
2360
 
2342
2361
    def init_custom_control_files(self, tree):
2343
2362
        """Initialize custom control files."""
2344
 
        tree._transport.put_bytes('views', b'',
2345
 
            mode=tree.controldir._get_file_mode())
 
2363
        tree._transport.put_bytes('views', '',
 
2364
            mode=tree.bzrdir._get_file_mode())
2346
2365
 
2347
2366
    def update_format(self, tree):
2348
2367
        """Change the format marker."""
2349
2368
        tree._transport.put_bytes('format',
2350
2369
            self.target_format.as_string(),
2351
 
            mode=tree.controldir._get_file_mode())
 
2370
            mode=tree.bzrdir._get_file_mode())