~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Andrew Bennetts
  • Date: 2007-03-26 06:24:01 UTC
  • mto: This revision was merged to the branch mainline in revision 2376.
  • Revision ID: andrew.bennetts@canonical.com-20070326062401-k3nbefzje5332jaf
Deal with review comments from Robert:

  * Add my name to the NEWS file
  * Move the test case to a new module in branch_implementations
  * Remove revision_history cruft from identitymap and test_identitymap
  * Improve some docstrings

Also, this fixes a bug where revision_history was not returning a copy of the
cached data, allowing the cache to be corrupted.

Show diffs side-by-side

added added

removed removed

Lines of Context:
38
38
 
39
39
from cStringIO import StringIO
40
40
import os
41
 
import sys
42
41
 
43
42
from bzrlib.lazy_import import lazy_import
44
43
lazy_import(globals(), """
45
44
from bisect import bisect_left
46
45
import collections
 
46
from copy import deepcopy
47
47
import errno
48
48
import itertools
49
49
import operator
64
64
    hashcache,
65
65
    ignores,
66
66
    merge,
67
 
    revision as _mod_revision,
 
67
    osutils,
68
68
    revisiontree,
69
69
    repository,
70
70
    textui,
71
 
    trace,
72
71
    transform,
73
 
    ui,
74
72
    urlutils,
75
73
    xml5,
76
74
    xml6,
89
87
from bzrlib.lockdir import LockDir
90
88
import bzrlib.mutabletree
91
89
from bzrlib.mutabletree import needs_tree_write_lock
92
 
from bzrlib import osutils
93
90
from bzrlib.osutils import (
94
91
    compact_date,
95
92
    file_kind,
111
108
        deprecated_method,
112
109
        deprecated_function,
113
110
        DEPRECATED_PARAMETER,
 
111
        zero_eight,
 
112
        zero_eleven,
 
113
        zero_thirteen,
114
114
        )
115
115
 
116
116
 
117
117
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
118
118
CONFLICT_HEADER_1 = "BZR conflict list format 1"
119
119
 
120
 
ERROR_PATH_NOT_FOUND = 3    # WindowsError errno code, equivalent to ENOENT
 
120
 
 
121
@deprecated_function(zero_thirteen)
 
122
def gen_file_id(name):
 
123
    """Return new file id for the basename 'name'.
 
124
 
 
125
    Use bzrlib.generate_ids.gen_file_id() instead
 
126
    """
 
127
    return generate_ids.gen_file_id(name)
 
128
 
 
129
 
 
130
@deprecated_function(zero_thirteen)
 
131
def gen_root_id():
 
132
    """Return a new tree-root file id.
 
133
 
 
134
    This has been deprecated in favor of bzrlib.generate_ids.gen_root_id()
 
135
    """
 
136
    return generate_ids.gen_root_id()
121
137
 
122
138
 
123
139
class TreeEntry(object):
192
208
                 _internal=False,
193
209
                 _format=None,
194
210
                 _bzrdir=None):
195
 
        """Construct a WorkingTree instance. This is not a public API.
 
211
        """Construct a WorkingTree for basedir.
196
212
 
197
 
        :param branch: A branch to override probing for the branch.
 
213
        If the branch is not supplied, it is opened automatically.
 
214
        If the branch is supplied, it must be the branch for this basedir.
 
215
        (branch.base is not cross checked, because for remote branches that
 
216
        would be meaningless).
198
217
        """
199
218
        self._format = _format
200
219
        self.bzrdir = _bzrdir
201
220
        if not _internal:
202
 
            raise errors.BzrError("Please use bzrdir.open_workingtree or "
203
 
                "WorkingTree.open() to obtain a WorkingTree.")
 
221
            # not created via open etc.
 
222
            warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
223
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
 
224
                 DeprecationWarning,
 
225
                 stacklevel=2)
 
226
            wt = WorkingTree.open(basedir)
 
227
            self._branch = wt.branch
 
228
            self.basedir = wt.basedir
 
229
            self._control_files = wt._control_files
 
230
            self._hashcache = wt._hashcache
 
231
            self._set_inventory(wt._inventory, dirty=False)
 
232
            self._format = wt._format
 
233
            self.bzrdir = wt.bzrdir
 
234
        assert isinstance(basedir, basestring), \
 
235
            "base directory %r is not a string" % basedir
204
236
        basedir = safe_unicode(basedir)
205
237
        mutter("opening working tree %r", basedir)
206
238
        if deprecated_passed(branch):
 
239
            if not _internal:
 
240
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated"
 
241
                     " as of bzr 0.8. Please use bzrdir.open_workingtree() or"
 
242
                     " WorkingTree.open().",
 
243
                     DeprecationWarning,
 
244
                     stacklevel=2
 
245
                     )
207
246
            self._branch = branch
208
247
        else:
209
248
            self._branch = self.bzrdir.open_branch()
214
253
            self._control_files = self.branch.control_files
215
254
        else:
216
255
            # assume all other formats have their own control files.
 
256
            assert isinstance(_control_files, LockableFiles), \
 
257
                    "_control_files must be a LockableFiles, not %r" \
 
258
                    % _control_files
217
259
            self._control_files = _control_files
218
 
        self._transport = self._control_files._transport
219
260
        # update the whole cache up front and write to disk if anything changed;
220
261
        # in the future we might want to do this more selectively
221
262
        # two possible ways offer themselves : in self._unlock, write the cache
225
266
        wt_trans = self.bzrdir.get_workingtree_transport(None)
226
267
        cache_filename = wt_trans.local_abspath('stat-cache')
227
268
        self._hashcache = hashcache.HashCache(basedir, cache_filename,
228
 
            self.bzrdir._get_file_mode())
 
269
                                              self._control_files._file_mode)
229
270
        hc = self._hashcache
230
271
        hc.read()
231
272
        # is this scan needed ? it makes things kinda slow.
245
286
            # the Format factory and creation methods that are
246
287
            # permitted to do this.
247
288
            self._set_inventory(_inventory, dirty=False)
248
 
        self._detect_case_handling()
249
 
        self._rules_searcher = None
250
 
 
251
 
    def _detect_case_handling(self):
252
 
        wt_trans = self.bzrdir.get_workingtree_transport(None)
253
 
        try:
254
 
            wt_trans.stat("FoRMaT")
255
 
        except errors.NoSuchFile:
256
 
            self.case_sensitive = True
257
 
        else:
258
 
            self.case_sensitive = False
259
 
 
260
 
        self._setup_directory_is_tree_reference()
261
289
 
262
290
    branch = property(
263
291
        fget=lambda self: self._branch,
294
322
            False then the inventory is the same as that on disk and any
295
323
            serialisation would be unneeded overhead.
296
324
        """
 
325
        assert inv.root is not None
297
326
        self._inventory = inv
298
327
        self._inventory_is_modified = dirty
299
328
 
334
363
        """
335
364
        return WorkingTree.open(path, _unsupported=True)
336
365
 
337
 
    @staticmethod
338
 
    def find_trees(location):
339
 
        def list_current(transport):
340
 
            return [d for d in transport.list_dir('') if d != '.bzr']
341
 
        def evaluate(bzrdir):
342
 
            try:
343
 
                tree = bzrdir.open_workingtree()
344
 
            except errors.NoWorkingTree:
345
 
                return True, None
346
 
            else:
347
 
                return True, tree
348
 
        transport = get_transport(location)
349
 
        iterator = bzrdir.BzrDir.find_bzrdirs(transport, evaluate=evaluate,
350
 
                                              list_current=list_current)
351
 
        return [t for t in iterator if t is not None]
352
 
 
353
366
    # should be deprecated - this is slow and in any case treating them as a
354
367
    # container is (we now know) bad style -- mbp 20070302
355
368
    ## @deprecated_method(zero_fifteen)
364
377
            if osutils.lexists(self.abspath(path)):
365
378
                yield ie.file_id
366
379
 
367
 
    def all_file_ids(self):
368
 
        """See Tree.iter_all_file_ids"""
369
 
        return set(self.inventory)
370
 
 
371
380
    def __repr__(self):
372
381
        return "<%s of %s>" % (self.__class__.__name__,
373
382
                               getattr(self, 'basedir', None))
374
383
 
375
384
    def abspath(self, filename):
376
385
        return pathjoin(self.basedir, filename)
377
 
 
 
386
    
378
387
    def basis_tree(self):
379
388
        """Return RevisionTree for the current last revision.
380
389
        
397
406
        # at this point ?
398
407
        try:
399
408
            return self.branch.repository.revision_tree(revision_id)
400
 
        except (errors.RevisionNotPresent, errors.NoSuchRevision):
 
409
        except errors.RevisionNotPresent:
401
410
            # the basis tree *may* be a ghost or a low level error may have
402
411
            # occured. If the revision is present, its a problem, if its not
403
412
            # its a ghost.
406
415
            # the basis tree is a ghost so return an empty tree.
407
416
            return self.branch.repository.revision_tree(None)
408
417
 
409
 
    def _cleanup(self):
410
 
        self._flush_ignore_list_cache()
 
418
    @staticmethod
 
419
    @deprecated_method(zero_eight)
 
420
    def create(branch, directory):
 
421
        """Create a workingtree for branch at directory.
 
422
 
 
423
        If existing_directory already exists it must have a .bzr directory.
 
424
        If it does not exist, it will be created.
 
425
 
 
426
        This returns a new WorkingTree object for the new checkout.
 
427
 
 
428
        TODO FIXME RBC 20060124 when we have checkout formats in place this
 
429
        should accept an optional revisionid to checkout [and reject this if
 
430
        checking out into the same dir as a pre-checkout-aware branch format.]
 
431
 
 
432
        XXX: When BzrDir is present, these should be created through that 
 
433
        interface instead.
 
434
        """
 
435
        warnings.warn('delete WorkingTree.create', stacklevel=3)
 
436
        transport = get_transport(directory)
 
437
        if branch.bzrdir.root_transport.base == transport.base:
 
438
            # same dir 
 
439
            return branch.bzrdir.create_workingtree()
 
440
        # different directory, 
 
441
        # create a branch reference
 
442
        # and now a working tree.
 
443
        raise NotImplementedError
 
444
 
 
445
    @staticmethod
 
446
    @deprecated_method(zero_eight)
 
447
    def create_standalone(directory):
 
448
        """Create a checkout and a branch and a repo at directory.
 
449
 
 
450
        Directory must exist and be empty.
 
451
 
 
452
        please use BzrDir.create_standalone_workingtree
 
453
        """
 
454
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
411
455
 
412
456
    def relpath(self, path):
413
457
        """Return the local path portion from a given path.
420
464
    def has_filename(self, filename):
421
465
        return osutils.lexists(self.abspath(filename))
422
466
 
423
 
    def get_file(self, file_id, path=None):
424
 
        if path is None:
425
 
            path = self.id2path(file_id)
426
 
        return self.get_file_byname(path)
 
467
    def get_file(self, file_id):
 
468
        file_id = osutils.safe_file_id(file_id)
 
469
        return self.get_file_byname(self.id2path(file_id))
427
470
 
428
471
    def get_file_text(self, file_id):
 
472
        file_id = osutils.safe_file_id(file_id)
429
473
        return self.get_file(file_id).read()
430
474
 
431
475
    def get_file_byname(self, filename):
432
476
        return file(self.abspath(filename), 'rb')
433
477
 
434
478
    @needs_read_lock
435
 
    def annotate_iter(self, file_id, default_revision=CURRENT_REVISION):
 
479
    def annotate_iter(self, file_id):
436
480
        """See Tree.annotate_iter
437
481
 
438
482
        This implementation will use the basis tree implementation if possible.
442
486
        incorrectly attributed to CURRENT_REVISION (but after committing, the
443
487
        attribution will be correct).
444
488
        """
 
489
        file_id = osutils.safe_file_id(file_id)
445
490
        basis = self.basis_tree()
446
491
        basis.lock_read()
447
492
        try:
448
 
            changes = self.iter_changes(basis, True, [self.id2path(file_id)],
 
493
            changes = self._iter_changes(basis, True, [self.id2path(file_id)],
449
494
                require_versioned=True).next()
450
495
            changed_content, kind = changes[2], changes[6]
451
496
            if not changed_content:
464
509
                    continue
465
510
                old.append(list(tree.annotate_iter(file_id)))
466
511
            return annotate.reannotate(old, self.get_file(file_id).readlines(),
467
 
                                       default_revision)
 
512
                                       CURRENT_REVISION)
468
513
        finally:
469
514
            basis.unlock()
470
515
 
471
 
    def _get_ancestors(self, default_revision):
472
 
        ancestors = set([default_revision])
473
 
        for parent_id in self.get_parent_ids():
474
 
            ancestors.update(self.branch.repository.get_ancestry(
475
 
                             parent_id, topo_sorted=False))
476
 
        return ancestors
477
 
 
478
516
    def get_parent_ids(self):
479
517
        """See Tree.get_parent_ids.
480
518
        
481
519
        This implementation reads the pending merges list and last_revision
482
520
        value and uses that to decide what the parents list should be.
483
521
        """
484
 
        last_rev = _mod_revision.ensure_null(self._last_revision())
485
 
        if _mod_revision.NULL_REVISION == last_rev:
 
522
        last_rev = self._last_revision()
 
523
        if last_rev is None:
486
524
            parents = []
487
525
        else:
488
526
            parents = [last_rev]
489
527
        try:
490
 
            merges_file = self._transport.get('pending-merges')
 
528
            merges_file = self._control_files.get('pending-merges')
491
529
        except errors.NoSuchFile:
492
530
            pass
493
531
        else:
494
532
            for l in merges_file.readlines():
495
 
                revision_id = l.rstrip('\n')
 
533
                revision_id = osutils.safe_revision_id(l.rstrip('\n'))
496
534
                parents.append(revision_id)
497
535
        return parents
498
536
 
503
541
        
504
542
    def _get_store_filename(self, file_id):
505
543
        ## XXX: badly named; this is not in the store at all
 
544
        file_id = osutils.safe_file_id(file_id)
506
545
        return self.abspath(self.id2path(file_id))
507
546
 
508
547
    @needs_read_lock
509
 
    def clone(self, to_bzrdir, revision_id=None):
 
548
    def clone(self, to_bzrdir, revision_id=None, basis=None):
510
549
        """Duplicate this working tree into to_bzr, including all state.
511
550
        
512
551
        Specifically modified files are kept as modified, but
518
557
            If not None, the cloned tree will have its last revision set to 
519
558
            revision, and and difference between the source trees last revision
520
559
            and this one merged in.
 
560
 
 
561
        basis
 
562
            If not None, a closer copy of a tree which may have some files in
 
563
            common, and which file content should be preferentially copied from.
521
564
        """
522
565
        # assumes the target bzr dir format is compatible.
523
566
        result = self._format.initialize(to_bzrdir)
537
580
            tree.set_parent_ids([revision_id])
538
581
 
539
582
    def id2abspath(self, file_id):
 
583
        file_id = osutils.safe_file_id(file_id)
540
584
        return self.abspath(self.id2path(file_id))
541
585
 
542
586
    def has_id(self, file_id):
543
587
        # files that have been deleted are excluded
 
588
        file_id = osutils.safe_file_id(file_id)
544
589
        inv = self.inventory
545
590
        if not inv.has_id(file_id):
546
591
            return False
548
593
        return osutils.lexists(self.abspath(path))
549
594
 
550
595
    def has_or_had_id(self, file_id):
 
596
        file_id = osutils.safe_file_id(file_id)
551
597
        if file_id == self.inventory.root.file_id:
552
598
            return True
553
599
        return self.inventory.has_id(file_id)
555
601
    __contains__ = has_id
556
602
 
557
603
    def get_file_size(self, file_id):
558
 
        """See Tree.get_file_size"""
559
 
        try:
560
 
            return os.path.getsize(self.id2abspath(file_id))
561
 
        except OSError, e:
562
 
            if e.errno != errno.ENOENT:
563
 
                raise
564
 
            else:
565
 
                return None
 
604
        file_id = osutils.safe_file_id(file_id)
 
605
        return os.path.getsize(self.id2abspath(file_id))
566
606
 
567
607
    @needs_read_lock
568
608
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
609
        file_id = osutils.safe_file_id(file_id)
569
610
        if not path:
570
611
            path = self._inventory.id2path(file_id)
571
612
        return self._hashcache.get_sha1(path, stat_value)
572
613
 
573
614
    def get_file_mtime(self, file_id, path=None):
 
615
        file_id = osutils.safe_file_id(file_id)
574
616
        if not path:
575
617
            path = self.inventory.id2path(file_id)
576
618
        return os.lstat(self.abspath(path)).st_mtime
577
619
 
578
 
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
579
 
        file_id = self.path2id(path)
580
 
        return self._inventory[file_id].executable
581
 
 
582
 
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
583
 
        mode = stat_result.st_mode
584
 
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
585
 
 
586
620
    if not supports_executable():
587
621
        def is_executable(self, file_id, path=None):
 
622
            file_id = osutils.safe_file_id(file_id)
588
623
            return self._inventory[file_id].executable
589
 
 
590
 
        _is_executable_from_path_and_stat = \
591
 
            _is_executable_from_path_and_stat_from_basis
592
624
    else:
593
625
        def is_executable(self, file_id, path=None):
594
626
            if not path:
 
627
                file_id = osutils.safe_file_id(file_id)
595
628
                path = self.id2path(file_id)
596
629
            mode = os.lstat(self.abspath(path)).st_mode
597
630
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
598
631
 
599
 
        _is_executable_from_path_and_stat = \
600
 
            _is_executable_from_path_and_stat_from_stat
601
 
 
602
632
    @needs_tree_write_lock
603
633
    def _add(self, files, ids, kinds):
604
634
        """See MutableTree._add."""
606
636
        # should probably put it back with the previous ID.
607
637
        # the read and write working inventory should not occur in this 
608
638
        # function - they should be part of lock_write and unlock.
609
 
        inv = self.inventory
 
639
        inv = self.read_working_inventory()
610
640
        for f, file_id, kind in zip(files, ids, kinds):
 
641
            assert kind is not None
611
642
            if file_id is None:
612
643
                inv.add_path(f, kind=kind)
613
644
            else:
 
645
                file_id = osutils.safe_file_id(file_id)
614
646
                inv.add_path(f, kind=kind, file_id=file_id)
615
 
            self._inventory_is_modified = True
 
647
        self._write_inventory(inv)
616
648
 
617
649
    @needs_tree_write_lock
618
650
    def _gather_kinds(self, files, kinds):
678
710
        if updated:
679
711
            self.set_parent_ids(parents, allow_leftmost_as_ghost=True)
680
712
 
681
 
    def path_content_summary(self, path, _lstat=os.lstat,
682
 
        _mapper=osutils.file_kind_from_stat_mode):
683
 
        """See Tree.path_content_summary."""
684
 
        abspath = self.abspath(path)
685
 
        try:
686
 
            stat_result = _lstat(abspath)
687
 
        except OSError, e:
688
 
            if getattr(e, 'errno', None) == errno.ENOENT:
689
 
                # no file.
690
 
                return ('missing', None, None, None)
691
 
            # propagate other errors
692
 
            raise
693
 
        kind = _mapper(stat_result.st_mode)
694
 
        if kind == 'file':
695
 
            size = stat_result.st_size
696
 
            # try for a stat cache lookup
697
 
            executable = self._is_executable_from_path_and_stat(path, stat_result)
698
 
            return (kind, size, executable, self._sha_from_stat(
699
 
                path, stat_result))
700
 
        elif kind == 'directory':
701
 
            # perhaps it looks like a plain directory, but it's really a
702
 
            # reference.
703
 
            if self._directory_is_tree_reference(path):
704
 
                kind = 'tree-reference'
705
 
            return kind, None, None, None
706
 
        elif kind == 'symlink':
707
 
            return ('symlink', None, None, os.readlink(abspath))
708
 
        else:
709
 
            return (kind, None, None, None)
 
713
    @deprecated_method(zero_eleven)
 
714
    @needs_read_lock
 
715
    def pending_merges(self):
 
716
        """Return a list of pending merges.
 
717
 
 
718
        These are revisions that have been merged into the working
 
719
        directory but not yet committed.
 
720
 
 
721
        As of 0.11 this is deprecated. Please see WorkingTree.get_parent_ids()
 
722
        instead - which is available on all tree objects.
 
723
        """
 
724
        return self.get_parent_ids()[1:]
710
725
 
711
726
    def _check_parents_for_ghosts(self, revision_ids, allow_leftmost_as_ghost):
712
727
        """Common ghost checking functionality from set_parent_*.
722
737
 
723
738
    def _set_merges_from_parent_ids(self, parent_ids):
724
739
        merges = parent_ids[1:]
725
 
        self._transport.put_bytes('pending-merges', '\n'.join(merges),
726
 
            mode=self._control_files._file_mode)
727
 
 
728
 
    def _filter_parent_ids_by_ancestry(self, revision_ids):
729
 
        """Check that all merged revisions are proper 'heads'.
730
 
 
731
 
        This will always return the first revision_id, and any merged revisions
732
 
        which are 
733
 
        """
734
 
        if len(revision_ids) == 0:
735
 
            return revision_ids
736
 
        graph = self.branch.repository.get_graph()
737
 
        heads = graph.heads(revision_ids)
738
 
        new_revision_ids = revision_ids[:1]
739
 
        for revision_id in revision_ids[1:]:
740
 
            if revision_id in heads and revision_id not in new_revision_ids:
741
 
                new_revision_ids.append(revision_id)
742
 
        if new_revision_ids != revision_ids:
743
 
            trace.mutter('requested to set revision_ids = %s,'
744
 
                         ' but filtered to %s', revision_ids, new_revision_ids)
745
 
        return new_revision_ids
 
740
        self._control_files.put_bytes('pending-merges', '\n'.join(merges))
746
741
 
747
742
    @needs_tree_write_lock
748
743
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
757
752
        :param revision_ids: The revision_ids to set as the parent ids of this
758
753
            working tree. Any of these may be ghosts.
759
754
        """
 
755
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
760
756
        self._check_parents_for_ghosts(revision_ids,
761
757
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
762
 
        for revision_id in revision_ids:
763
 
            _mod_revision.check_not_reserved_id(revision_id)
764
 
 
765
 
        revision_ids = self._filter_parent_ids_by_ancestry(revision_ids)
766
758
 
767
759
        if len(revision_ids) > 0:
768
760
            self.set_last_revision(revision_ids[0])
769
761
        else:
770
 
            self.set_last_revision(_mod_revision.NULL_REVISION)
 
762
            self.set_last_revision(None)
771
763
 
772
764
        self._set_merges_from_parent_ids(revision_ids)
773
765
 
774
766
    @needs_tree_write_lock
775
767
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
776
768
        """See MutableTree.set_parent_trees."""
777
 
        parent_ids = [rev for (rev, tree) in parents_list]
778
 
        for revision_id in parent_ids:
779
 
            _mod_revision.check_not_reserved_id(revision_id)
 
769
        parent_ids = [osutils.safe_revision_id(rev) for (rev, tree) in parents_list]
780
770
 
781
771
        self._check_parents_for_ghosts(parent_ids,
782
772
            allow_leftmost_as_ghost=allow_leftmost_as_ghost)
783
773
 
784
 
        parent_ids = self._filter_parent_ids_by_ancestry(parent_ids)
785
 
 
786
774
        if len(parent_ids) == 0:
787
 
            leftmost_parent_id = _mod_revision.NULL_REVISION
 
775
            leftmost_parent_id = None
788
776
            leftmost_parent_tree = None
789
777
        else:
790
778
            leftmost_parent_id, leftmost_parent_tree = parents_list[0]
815
803
                yield Stanza(file_id=file_id.decode('utf8'), hash=hash)
816
804
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
817
805
 
818
 
    def _sha_from_stat(self, path, stat_result):
819
 
        """Get a sha digest from the tree's stat cache.
820
 
 
821
 
        The default implementation assumes no stat cache is present.
822
 
 
823
 
        :param path: The path.
824
 
        :param stat_result: The stat result being looked up.
825
 
        """
826
 
        return None
827
 
 
828
806
    def _put_rio(self, filename, stanzas, header):
829
807
        self._must_be_locked()
830
808
        my_file = rio_file(stanzas, header)
831
 
        self._transport.put_file(filename, my_file,
832
 
            mode=self._control_files._file_mode)
 
809
        self._control_files.put(filename, my_file)
833
810
 
834
811
    @needs_write_lock # because merge pulls data into the branch.
835
 
    def merge_from_branch(self, branch, to_revision=None, from_revision=None,
836
 
        merge_type=None):
 
812
    def merge_from_branch(self, branch, to_revision=None):
837
813
        """Merge from a branch into this working tree.
838
814
 
839
815
        :param branch: The branch to merge from.
852
828
            # local alterations
853
829
            merger.check_basis(check_clean=True, require_commits=False)
854
830
            if to_revision is None:
855
 
                to_revision = _mod_revision.ensure_null(branch.last_revision())
 
831
                to_revision = branch.last_revision()
 
832
            else:
 
833
                to_revision = osutils.safe_revision_id(to_revision)
856
834
            merger.other_rev_id = to_revision
857
 
            if _mod_revision.is_null(merger.other_rev_id):
858
 
                raise errors.NoCommits(branch)
 
835
            if merger.other_rev_id is None:
 
836
                raise error.NoCommits(branch)
859
837
            self.branch.fetch(branch, last_revision=merger.other_rev_id)
860
838
            merger.other_basis = merger.other_rev_id
861
839
            merger.other_tree = self.branch.repository.revision_tree(
862
840
                merger.other_rev_id)
863
841
            merger.other_branch = branch
864
842
            merger.pp.next_phase()
865
 
            if from_revision is None:
866
 
                merger.find_base()
867
 
            else:
868
 
                merger.set_base_revision(from_revision, branch)
 
843
            merger.find_base()
869
844
            if merger.base_rev_id == merger.other_rev_id:
870
845
                raise errors.PointlessMerge
871
846
            merger.backup_files = False
872
 
            if merge_type is None:
873
 
                merger.merge_type = Merge3Merger
874
 
            else:
875
 
                merger.merge_type = merge_type
 
847
            merger.merge_type = Merge3Merger
876
848
            merger.set_interesting_files(None)
877
849
            merger.show_base = False
878
850
            merger.reprocess = False
894
866
        still in the working inventory and have that text hash.
895
867
        """
896
868
        try:
897
 
            hashfile = self._transport.get('merge-hashes')
 
869
            hashfile = self._control_files.get('merge-hashes')
898
870
        except errors.NoSuchFile:
899
871
            return {}
900
872
        merge_hashes = {}
923
895
        return file_id
924
896
 
925
897
    def get_symlink_target(self, file_id):
 
898
        file_id = osutils.safe_file_id(file_id)
926
899
        return os.readlink(self.id2abspath(file_id))
927
900
 
928
901
    @needs_write_lock
967
940
            other_tree.unlock()
968
941
        other_tree.bzrdir.retire_bzrdir()
969
942
 
970
 
    def _setup_directory_is_tree_reference(self):
971
 
        if self._branch.repository._format.supports_tree_reference:
972
 
            self._directory_is_tree_reference = \
973
 
                self._directory_may_be_tree_reference
974
 
        else:
975
 
            self._directory_is_tree_reference = \
976
 
                self._directory_is_never_tree_reference
977
 
 
978
 
    def _directory_is_never_tree_reference(self, relpath):
979
 
        return False
980
 
 
981
 
    def _directory_may_be_tree_reference(self, relpath):
982
 
        # as a special case, if a directory contains control files then 
983
 
        # it's a tree reference, except that the root of the tree is not
984
 
        return relpath and osutils.isdir(self.abspath(relpath) + u"/.bzr")
985
 
        # TODO: We could ask all the control formats whether they
986
 
        # recognize this directory, but at the moment there's no cheap api
987
 
        # to do that.  Since we probably can only nest bzr checkouts and
988
 
        # they always use this name it's ok for now.  -- mbp 20060306
989
 
        #
990
 
        # FIXME: There is an unhandled case here of a subdirectory
991
 
        # containing .bzr but not a branch; that will probably blow up
992
 
        # when you try to commit it.  It might happen if there is a
993
 
        # checkout in a subdirectory.  This can be avoided by not adding
994
 
        # it.  mbp 20070306
995
 
 
996
943
    @needs_tree_write_lock
997
944
    def extract(self, file_id, format=None):
998
945
        """Extract a subtree from this tree.
1005
952
            transport = self.branch.bzrdir.root_transport
1006
953
            for name in segments:
1007
954
                transport = transport.clone(name)
1008
 
                transport.ensure_base()
 
955
                try:
 
956
                    transport.mkdir('.')
 
957
                except errors.FileExists:
 
958
                    pass
1009
959
            return transport
1010
960
            
1011
961
        sub_path = self.id2path(file_id)
1012
962
        branch_transport = mkdirs(sub_path)
1013
963
        if format is None:
1014
 
            format = self.bzrdir.cloning_metadir()
1015
 
        branch_transport.ensure_base()
 
964
            format = bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
 
965
        try:
 
966
            branch_transport.mkdir('.')
 
967
        except errors.FileExists:
 
968
            pass
1016
969
        branch_bzrdir = format.initialize_on_transport(branch_transport)
1017
970
        try:
1018
971
            repo = branch_bzrdir.find_repository()
1019
972
        except errors.NoRepositoryPresent:
1020
973
            repo = branch_bzrdir.create_repository()
1021
 
        if not repo.supports_rich_root():
1022
 
            raise errors.RootNotRich()
 
974
            assert repo.supports_rich_root()
 
975
        else:
 
976
            if not repo.supports_rich_root():
 
977
                raise errors.RootNotRich()
1023
978
        new_branch = branch_bzrdir.create_branch()
1024
979
        new_branch.pull(self.branch)
1025
980
        for parent_id in self.get_parent_ids():
1043
998
        return wt
1044
999
 
1045
1000
    def _serialize(self, inventory, out_file):
1046
 
        xml5.serializer_v5.write_inventory(self._inventory, out_file,
1047
 
            working=True)
 
1001
        xml5.serializer_v5.write_inventory(self._inventory, out_file)
1048
1002
 
1049
1003
    def _deserialize(selt, in_file):
1050
1004
        return xml5.serializer_v5.read_inventory(in_file)
1057
1011
        sio = StringIO()
1058
1012
        self._serialize(self._inventory, sio)
1059
1013
        sio.seek(0)
1060
 
        self._transport.put_file('inventory', sio,
1061
 
            mode=self._control_files._file_mode)
 
1014
        self._control_files.put('inventory', sio)
1062
1015
        self._inventory_is_modified = False
1063
1016
 
1064
1017
    def _kind(self, relpath):
1225
1178
                                       DeprecationWarning)
1226
1179
 
1227
1180
        # check destination directory
1228
 
        if isinstance(from_paths, basestring):
1229
 
            raise ValueError()
 
1181
        assert not isinstance(from_paths, basestring)
1230
1182
        inv = self.inventory
1231
1183
        to_abs = self.abspath(to_dir)
1232
1184
        if not isdir(to_abs):
1316
1268
                only_change_inv = True
1317
1269
            elif self.has_filename(from_rel) and not self.has_filename(to_rel):
1318
1270
                only_change_inv = False
1319
 
            elif (sys.platform == 'win32'
1320
 
                and from_rel.lower() == to_rel.lower()
1321
 
                and self.has_filename(from_rel)):
1322
 
                only_change_inv = False
1323
1271
            else:
1324
1272
                # something is wrong, so lets determine what exactly
1325
1273
                if not self.has_filename(from_rel) and \
1328
1276
                        errors.PathsDoNotExist(paths=(str(from_rel),
1329
1277
                        str(to_rel))))
1330
1278
                else:
1331
 
                    raise errors.RenameFailedFilesExist(from_rel, to_rel)
 
1279
                    raise errors.RenameFailedFilesExist(from_rel, to_rel,
 
1280
                        extra="(Use --after to update the Bazaar id)")
1332
1281
            rename_entry.only_change_inv = only_change_inv
1333
1282
        return rename_entries
1334
1283
 
1469
1418
        # prevent race conditions with the lock
1470
1419
        return iter(
1471
1420
            [subp for subp in self.extras() if not self.is_ignored(subp)])
1472
 
 
 
1421
    
1473
1422
    @needs_tree_write_lock
1474
1423
    def unversion(self, file_ids):
1475
1424
        """Remove the file ids in file_ids from the current versioned set.
1481
1430
        :raises: NoSuchId if any fileid is not currently versioned.
1482
1431
        """
1483
1432
        for file_id in file_ids:
 
1433
            file_id = osutils.safe_file_id(file_id)
1484
1434
            if self._inventory.has_id(file_id):
1485
1435
                self._inventory.remove_recursive_id(file_id)
1486
1436
            else:
1496
1446
            # - RBC 20060907
1497
1447
            self._write_inventory(self._inventory)
1498
1448
    
 
1449
    @deprecated_method(zero_eight)
 
1450
    def iter_conflicts(self):
 
1451
        """List all files in the tree that have text or content conflicts.
 
1452
        DEPRECATED.  Use conflicts instead."""
 
1453
        return self._iter_conflicts()
 
1454
 
1499
1455
    def _iter_conflicts(self):
1500
1456
        conflicted = set()
1501
1457
        for info in self.list_files():
1509
1465
 
1510
1466
    @needs_write_lock
1511
1467
    def pull(self, source, overwrite=False, stop_revision=None,
1512
 
             change_reporter=None, possible_transports=None):
 
1468
             change_reporter=None):
1513
1469
        top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1514
1470
        source.lock_read()
1515
1471
        try:
1517
1473
            pp.next_phase()
1518
1474
            old_revision_info = self.branch.last_revision_info()
1519
1475
            basis_tree = self.basis_tree()
1520
 
            count = self.branch.pull(source, overwrite, stop_revision,
1521
 
                                     possible_transports=possible_transports)
 
1476
            count = self.branch.pull(source, overwrite, stop_revision)
1522
1477
            new_revision_info = self.branch.last_revision_info()
1523
1478
            if new_revision_info != old_revision_info:
1524
1479
                pp.next_phase()
1536
1491
                                change_reporter=change_reporter)
1537
1492
                    if (basis_tree.inventory.root is None and
1538
1493
                        new_basis_tree.inventory.root is not None):
1539
 
                        self.set_root_id(new_basis_tree.get_root_id())
 
1494
                        self.set_root_id(new_basis_tree.inventory.root.file_id)
1540
1495
                finally:
1541
1496
                    pb.finished()
1542
1497
                    basis_tree.unlock()
1562
1517
    @needs_write_lock
1563
1518
    def put_file_bytes_non_atomic(self, file_id, bytes):
1564
1519
        """See MutableTree.put_file_bytes_non_atomic."""
 
1520
        file_id = osutils.safe_file_id(file_id)
1565
1521
        stream = file(self.id2abspath(file_id), 'wb')
1566
1522
        try:
1567
1523
            stream.write(bytes)
1592
1548
                if subf == '.bzr':
1593
1549
                    continue
1594
1550
                if subf not in dir_entry.children:
1595
 
                    try:
1596
 
                        (subf_norm,
1597
 
                         can_access) = osutils.normalized_filename(subf)
1598
 
                    except UnicodeDecodeError:
1599
 
                        path_os_enc = path.encode(osutils._fs_enc)
1600
 
                        relpath = path_os_enc + '/' + subf
1601
 
                        raise errors.BadFilenameEncoding(relpath,
1602
 
                                                         osutils._fs_enc)
 
1551
                    subf_norm, can_access = osutils.normalized_filename(subf)
1603
1552
                    if subf_norm != subf and can_access:
1604
1553
                        if subf_norm not in dir_entry.children:
1605
1554
                            fl.append(subf_norm)
1627
1576
        if ignoreset is not None:
1628
1577
            return ignoreset
1629
1578
 
1630
 
        ignore_globs = set()
 
1579
        ignore_globs = set(bzrlib.DEFAULT_IGNORE)
1631
1580
        ignore_globs.update(ignores.get_runtime_ignores())
1632
1581
        ignore_globs.update(ignores.get_user_ignores())
1633
1582
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1660
1609
    def kind(self, file_id):
1661
1610
        return file_kind(self.id2abspath(file_id))
1662
1611
 
1663
 
    def stored_kind(self, file_id):
1664
 
        """See Tree.stored_kind"""
1665
 
        return self.inventory[file_id].kind
1666
 
 
1667
1612
    def _comparison_data(self, entry, path):
1668
1613
        abspath = self.abspath(path)
1669
1614
        try:
1679
1624
            mode = stat_value.st_mode
1680
1625
            kind = osutils.file_kind_from_stat_mode(mode)
1681
1626
            if not supports_executable():
1682
 
                executable = entry is not None and entry.executable
 
1627
                executable = entry.executable
1683
1628
            else:
1684
1629
                executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1685
1630
        return kind, executable, stat_value
1700
1645
    @needs_read_lock
1701
1646
    def _last_revision(self):
1702
1647
        """helper for get_parent_ids."""
1703
 
        return _mod_revision.ensure_null(self.branch.last_revision())
 
1648
        return self.branch.last_revision()
1704
1649
 
1705
1650
    def is_locked(self):
1706
1651
        return self._control_files.is_locked()
1751
1696
    def _reset_data(self):
1752
1697
        """Reset transient data that cannot be revalidated."""
1753
1698
        self._inventory_is_modified = False
1754
 
        result = self._deserialize(self._transport.get('inventory'))
 
1699
        result = self._deserialize(self._control_files.get('inventory'))
1755
1700
        self._set_inventory(result, dirty=False)
1756
1701
 
1757
1702
    @needs_tree_write_lock
1758
1703
    def set_last_revision(self, new_revision):
1759
1704
        """Change the last revision in the working tree."""
 
1705
        new_revision = osutils.safe_revision_id(new_revision)
1760
1706
        if self._change_last_revision(new_revision):
1761
1707
            self._cache_basis_inventory(new_revision)
1762
1708
 
1766
1712
        This is used to allow WorkingTree3 instances to not affect branch
1767
1713
        when their last revision is set.
1768
1714
        """
1769
 
        if _mod_revision.is_null(new_revision):
 
1715
        if new_revision is None:
1770
1716
            self.branch.set_revision_history([])
1771
1717
            return False
1772
1718
        try:
1778
1724
 
1779
1725
    def _write_basis_inventory(self, xml):
1780
1726
        """Write the basis inventory XML to the basis-inventory file"""
 
1727
        assert isinstance(xml, str), 'serialised xml must be bytestring.'
1781
1728
        path = self._basis_inventory_name()
1782
1729
        sio = StringIO(xml)
1783
 
        self._transport.put_file(path, sio,
1784
 
            mode=self._control_files._file_mode)
 
1730
        self._control_files.put(path, sio)
1785
1731
 
1786
1732
    def _create_basis_xml_from_inventory(self, revision_id, inventory):
1787
1733
        """Create the text that will be saved in basis-inventory"""
1788
 
        inventory.revision_id = revision_id
 
1734
        # TODO: jam 20070209 This should be redundant, as the revision_id
 
1735
        #       as all callers should have already converted the revision_id to
 
1736
        #       utf8
 
1737
        inventory.revision_id = osutils.safe_revision_id(revision_id)
1789
1738
        return xml7.serializer_v7.write_inventory_to_string(inventory)
1790
1739
 
1791
1740
    def _cache_basis_inventory(self, new_revision):
1818
1767
    def read_basis_inventory(self):
1819
1768
        """Read the cached basis inventory."""
1820
1769
        path = self._basis_inventory_name()
1821
 
        return self._transport.get_bytes(path)
 
1770
        return self._control_files.get(path).read()
1822
1771
        
1823
1772
    @needs_read_lock
1824
1773
    def read_working_inventory(self):
1833
1782
        # binary.
1834
1783
        if self._inventory_is_modified:
1835
1784
            raise errors.InventoryModified(self)
1836
 
        result = self._deserialize(self._transport.get('inventory'))
 
1785
        result = self._deserialize(self._control_files.get('inventory'))
1837
1786
        self._set_inventory(result, dirty=False)
1838
1787
        return result
1839
1788
 
1840
1789
    @needs_tree_write_lock
1841
 
    def remove(self, files, verbose=False, to_file=None, keep_files=True,
1842
 
        force=False):
1843
 
        """Remove nominated files from the working inventory.
1844
 
 
1845
 
        :files: File paths relative to the basedir.
1846
 
        :keep_files: If true, the files will also be kept.
1847
 
        :force: Delete files and directories, even if they are changed and
1848
 
            even if the directories are not empty.
 
1790
    def remove(self, files, verbose=False, to_file=None):
 
1791
        """Remove nominated files from the working inventory..
 
1792
 
 
1793
        This does not remove their text.  This does not run on XXX on what? RBC
 
1794
 
 
1795
        TODO: Refuse to remove modified files unless --force is given?
 
1796
 
 
1797
        TODO: Do something useful with directories.
 
1798
 
 
1799
        TODO: Should this remove the text or not?  Tough call; not
 
1800
        removing may be useful and the user can just use use rm, and
 
1801
        is the opposite of add.  Removing it is consistent with most
 
1802
        other tools.  Maybe an option.
1849
1803
        """
 
1804
        ## TODO: Normalize names
 
1805
        ## TODO: Remove nested loops; better scalability
1850
1806
        if isinstance(files, basestring):
1851
1807
            files = [files]
1852
1808
 
1853
 
        inv_delta = []
1854
 
 
1855
 
        new_files=set()
1856
 
        unknown_nested_files=set()
1857
 
 
1858
 
        def recurse_directory_to_add_files(directory):
1859
 
            # Recurse directory and add all files
1860
 
            # so we can check if they have changed.
1861
 
            for parent_info, file_infos in\
1862
 
                self.walkdirs(directory):
1863
 
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
1864
 
                    # Is it versioned or ignored?
1865
 
                    if self.path2id(relpath) or self.is_ignored(relpath):
1866
 
                        # Add nested content for deletion.
1867
 
                        new_files.add(relpath)
1868
 
                    else:
1869
 
                        # Files which are not versioned and not ignored
1870
 
                        # should be treated as unknown.
1871
 
                        unknown_nested_files.add((relpath, None, kind))
1872
 
 
1873
 
        for filename in files:
1874
 
            # Get file name into canonical form.
1875
 
            abspath = self.abspath(filename)
1876
 
            filename = self.relpath(abspath)
1877
 
            if len(filename) > 0:
1878
 
                new_files.add(filename)
1879
 
                recurse_directory_to_add_files(filename)
1880
 
 
1881
 
        files = list(new_files)
1882
 
 
1883
 
        if len(files) == 0:
1884
 
            return # nothing to do
1885
 
 
1886
 
        # Sort needed to first handle directory content before the directory
1887
 
        files.sort(reverse=True)
1888
 
 
1889
 
        # Bail out if we are going to delete files we shouldn't
1890
 
        if not keep_files and not force:
1891
 
            has_changed_files = len(unknown_nested_files) > 0
1892
 
            if not has_changed_files:
1893
 
                for (file_id, path, content_change, versioned, parent_id, name,
1894
 
                     kind, executable) in self.iter_changes(self.basis_tree(),
1895
 
                         include_unchanged=True, require_versioned=False,
1896
 
                         want_unversioned=True, specific_files=files):
1897
 
                    if versioned == (False, False):
1898
 
                        # The record is unknown ...
1899
 
                        if not self.is_ignored(path[1]):
1900
 
                            # ... but not ignored
1901
 
                            has_changed_files = True
1902
 
                            break
1903
 
                    elif content_change and (kind[1] is not None):
1904
 
                        # Versioned and changed, but not deleted
1905
 
                        has_changed_files = True
1906
 
                        break
1907
 
 
1908
 
            if has_changed_files:
1909
 
                # Make delta show ALL applicable changes in error message.
1910
 
                tree_delta = self.changes_from(self.basis_tree(),
1911
 
                    require_versioned=False, want_unversioned=True,
1912
 
                    specific_files=files)
1913
 
                for unknown_file in unknown_nested_files:
1914
 
                    if unknown_file not in tree_delta.unversioned:
1915
 
                        tree_delta.unversioned.extend((unknown_file,))
1916
 
                raise errors.BzrRemoveChangedFilesError(tree_delta)
1917
 
 
1918
 
        # Build inv_delta and delete files where applicaple,
1919
 
        # do this before any modifications to inventory.
 
1809
        inv = self.inventory
 
1810
 
 
1811
        # do this before any modifications
1920
1812
        for f in files:
1921
 
            fid = self.path2id(f)
1922
 
            message = None
 
1813
            fid = inv.path2id(f)
1923
1814
            if not fid:
1924
 
                message = "%s is not versioned." % (f,)
 
1815
                note("%s is not versioned."%f)
1925
1816
            else:
1926
1817
                if verbose:
1927
 
                    # having removed it, it must be either ignored or unknown
 
1818
                    # having remove it, it must be either ignored or unknown
1928
1819
                    if self.is_ignored(f):
1929
1820
                        new_status = 'I'
1930
1821
                    else:
1931
1822
                        new_status = '?'
1932
 
                    textui.show_status(new_status, self.kind(fid), f,
 
1823
                    textui.show_status(new_status, inv[fid].kind, f,
1933
1824
                                       to_file=to_file)
1934
 
                # Unversion file
1935
 
                inv_delta.append((f, None, fid, None))
1936
 
                message = "removed %s" % (f,)
1937
 
 
1938
 
            if not keep_files:
1939
 
                abs_path = self.abspath(f)
1940
 
                if osutils.lexists(abs_path):
1941
 
                    if (osutils.isdir(abs_path) and
1942
 
                        len(os.listdir(abs_path)) > 0):
1943
 
                        if force:
1944
 
                            osutils.rmtree(abs_path)
1945
 
                        else:
1946
 
                            message = "%s is not an empty directory "\
1947
 
                                "and won't be deleted." % (f,)
1948
 
                    else:
1949
 
                        osutils.delete_any(abs_path)
1950
 
                        message = "deleted %s" % (f,)
1951
 
                elif message is not None:
1952
 
                    # Only care if we haven't done anything yet.
1953
 
                    message = "%s does not exist." % (f,)
1954
 
 
1955
 
            # Print only one message (if any) per file.
1956
 
            if message is not None:
1957
 
                note(message)
1958
 
        self.apply_inventory_delta(inv_delta)
 
1825
                del inv[fid]
 
1826
 
 
1827
        self._write_inventory(inv)
1959
1828
 
1960
1829
    @needs_tree_write_lock
1961
 
    def revert(self, filenames=None, old_tree=None, backups=True,
 
1830
    def revert(self, filenames, old_tree=None, backups=True, 
1962
1831
               pb=DummyProgress(), report_changes=False):
1963
1832
        from bzrlib.conflicts import resolve
1964
 
        if filenames == []:
1965
 
            filenames = None
1966
 
            symbol_versioning.warn('Using [] to revert all files is deprecated'
1967
 
                ' as of bzr 0.91.  Please use None (the default) instead.',
1968
 
                DeprecationWarning, stacklevel=2)
1969
1833
        if old_tree is None:
1970
 
            basis_tree = self.basis_tree()
1971
 
            basis_tree.lock_read()
1972
 
            old_tree = basis_tree
 
1834
            old_tree = self.basis_tree()
 
1835
        conflicts = transform.revert(self, old_tree, filenames, backups, pb,
 
1836
                                     report_changes)
 
1837
        if not len(filenames):
 
1838
            self.set_parent_ids(self.get_parent_ids()[:1])
 
1839
            resolve(self)
1973
1840
        else:
1974
 
            basis_tree = None
1975
 
        try:
1976
 
            conflicts = transform.revert(self, old_tree, filenames, backups, pb,
1977
 
                                         report_changes)
1978
 
            if filenames is None and len(self.get_parent_ids()) > 1:
1979
 
                parent_trees = []
1980
 
                last_revision = self.last_revision()
1981
 
                if last_revision != NULL_REVISION:
1982
 
                    if basis_tree is None:
1983
 
                        basis_tree = self.basis_tree()
1984
 
                        basis_tree.lock_read()
1985
 
                    parent_trees.append((last_revision, basis_tree))
1986
 
                self.set_parent_trees(parent_trees)
1987
 
                resolve(self)
1988
 
            else:
1989
 
                resolve(self, filenames, ignore_misses=True, recursive=True)
1990
 
        finally:
1991
 
            if basis_tree is not None:
1992
 
                basis_tree.unlock()
 
1841
            resolve(self, filenames, ignore_misses=True)
1993
1842
        return conflicts
1994
1843
 
1995
1844
    def revision_tree(self, revision_id):
2046
1895
        """Set the root id for this tree."""
2047
1896
        # for compatability 
2048
1897
        if file_id is None:
2049
 
            raise ValueError(
2050
 
                'WorkingTree.set_root_id with fileid=None')
2051
 
        file_id = osutils.safe_file_id(file_id)
 
1898
            symbol_versioning.warn(symbol_versioning.zero_twelve
 
1899
                % 'WorkingTree.set_root_id with fileid=None',
 
1900
                DeprecationWarning,
 
1901
                stacklevel=3)
 
1902
            file_id = ROOT_ID
 
1903
        else:
 
1904
            file_id = osutils.safe_file_id(file_id)
2052
1905
        self._set_root_id(file_id)
2053
1906
 
2054
1907
    def _set_root_id(self, file_id):
2088
1941
        """
2089
1942
        raise NotImplementedError(self.unlock)
2090
1943
 
2091
 
    def update(self, change_reporter=None, possible_transports=None):
 
1944
    def update(self):
2092
1945
        """Update a working tree along its branch.
2093
1946
 
2094
1947
        This will update the branch if its bound too, which means we have
2113
1966
          basis.
2114
1967
        - Do a 'normal' merge of the old branch basis if it is relevant.
2115
1968
        """
2116
 
        if self.branch.get_bound_location() is not None:
 
1969
        if self.branch.get_master_branch() is not None:
2117
1970
            self.lock_write()
2118
1971
            update_branch = True
2119
1972
        else:
2121
1974
            update_branch = False
2122
1975
        try:
2123
1976
            if update_branch:
2124
 
                old_tip = self.branch.update(possible_transports)
 
1977
                old_tip = self.branch.update()
2125
1978
            else:
2126
1979
                old_tip = None
2127
 
            return self._update_tree(old_tip, change_reporter)
 
1980
            return self._update_tree(old_tip)
2128
1981
        finally:
2129
1982
            self.unlock()
2130
1983
 
2131
1984
    @needs_tree_write_lock
2132
 
    def _update_tree(self, old_tip=None, change_reporter=None):
 
1985
    def _update_tree(self, old_tip=None):
2133
1986
        """Update a tree to the master branch.
2134
1987
 
2135
1988
        :param old_tip: if supplied, the previous tip revision the branch,
2149
2002
        try:
2150
2003
            last_rev = self.get_parent_ids()[0]
2151
2004
        except IndexError:
2152
 
            last_rev = _mod_revision.NULL_REVISION
2153
 
        if last_rev != _mod_revision.ensure_null(self.branch.last_revision()):
 
2005
            last_rev = None
 
2006
        if last_rev != self.branch.last_revision():
2154
2007
            # merge tree state up to new branch tip.
2155
2008
            basis = self.basis_tree()
2156
2009
            basis.lock_read()
2157
2010
            try:
2158
2011
                to_tree = self.branch.basis_tree()
2159
2012
                if basis.inventory.root is None:
2160
 
                    self.set_root_id(to_tree.get_root_id())
 
2013
                    self.set_root_id(to_tree.inventory.root.file_id)
2161
2014
                    self.flush()
2162
2015
                result += merge.merge_inner(
2163
2016
                                      self.branch,
2164
2017
                                      to_tree,
2165
2018
                                      basis,
2166
 
                                      this_tree=self,
2167
 
                                      change_reporter=change_reporter)
 
2019
                                      this_tree=self)
2168
2020
            finally:
2169
2021
                basis.unlock()
2170
2022
            # TODO - dedup parents list with things merged by pull ?
2179
2031
            for parent in merges:
2180
2032
                parent_trees.append(
2181
2033
                    (parent, self.branch.repository.revision_tree(parent)))
2182
 
            if (old_tip is not None and not _mod_revision.is_null(old_tip)):
 
2034
            if old_tip is not None:
2183
2035
                parent_trees.append(
2184
2036
                    (old_tip, self.branch.repository.revision_tree(old_tip)))
2185
2037
            self.set_parent_trees(parent_trees)
2188
2040
            # the working tree had the same last-revision as the master
2189
2041
            # branch did. We may still have pivot local work from the local
2190
2042
            # branch into old_tip:
2191
 
            if (old_tip is not None and not _mod_revision.is_null(old_tip)):
 
2043
            if old_tip is not None:
2192
2044
                self.add_parent_tree_id(old_tip)
2193
 
        if (old_tip is not None and not _mod_revision.is_null(old_tip)
2194
 
            and old_tip != last_rev):
 
2045
        if old_tip and old_tip != last_rev:
2195
2046
            # our last revision was not the prior branch last revision
2196
2047
            # and we have converted that last revision to a pending merge.
2197
2048
            # base is somewhere between the branch tip now
2204
2055
            #       inventory and calls tree._write_inventory(). Ultimately we
2205
2056
            #       should be able to remove this extra flush.
2206
2057
            self.flush()
2207
 
            graph = self.branch.repository.get_graph()
2208
 
            base_rev_id = graph.find_unique_lca(self.branch.last_revision(),
2209
 
                                                old_tip)
 
2058
            from bzrlib.revision import common_ancestor
 
2059
            try:
 
2060
                base_rev_id = common_ancestor(self.branch.last_revision(),
 
2061
                                              old_tip,
 
2062
                                              self.branch.repository)
 
2063
            except errors.NoCommonAncestor:
 
2064
                base_rev_id = None
2210
2065
            base_tree = self.branch.repository.revision_tree(base_rev_id)
2211
2066
            other_tree = self.branch.repository.revision_tree(old_tip)
2212
2067
            result += merge.merge_inner(
2213
2068
                                  self.branch,
2214
2069
                                  other_tree,
2215
2070
                                  base_tree,
2216
 
                                  this_tree=self,
2217
 
                                  change_reporter=change_reporter)
 
2071
                                  this_tree=self)
2218
2072
        return result
2219
2073
 
2220
2074
    def _write_hashcache_if_dirty(self):
2272
2126
    def walkdirs(self, prefix=""):
2273
2127
        """Walk the directories of this tree.
2274
2128
 
2275
 
        returns a generator which yields items in the form:
2276
 
                ((curren_directory_path, fileid),
2277
 
                 [(file1_path, file1_name, file1_kind, (lstat), file1_id,
2278
 
                   file1_kind), ... ])
2279
 
 
2280
2129
        This API returns a generator, which is only valid during the current
2281
2130
        tree transaction - within a single lock_read or lock_write duration.
2282
2131
 
2283
 
        If the tree is not locked, it may cause an error to be raised,
2284
 
        depending on the tree implementation.
 
2132
        If the tree is not locked, it may cause an error to be raised, depending
 
2133
        on the tree implementation.
2285
2134
        """
2286
2135
        disk_top = self.abspath(prefix)
2287
2136
        if disk_top.endswith('/'):
2293
2142
            current_disk = disk_iterator.next()
2294
2143
            disk_finished = False
2295
2144
        except OSError, e:
2296
 
            if not (e.errno == errno.ENOENT or
2297
 
                (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
 
2145
            if e.errno != errno.ENOENT:
2298
2146
                raise
2299
2147
            current_disk = None
2300
2148
            disk_finished = True
2305
2153
            current_inv = None
2306
2154
            inv_finished = True
2307
2155
        while not inv_finished or not disk_finished:
2308
 
            if current_disk:
2309
 
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
2310
 
                    cur_disk_dir_content) = current_disk
2311
 
            else:
2312
 
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
2313
 
                    cur_disk_dir_content) = ((None, None), None)
2314
2156
            if not disk_finished:
2315
2157
                # strip out .bzr dirs
2316
 
                if (cur_disk_dir_path_from_top[top_strip_len:] == '' and
2317
 
                    len(cur_disk_dir_content) > 0):
2318
 
                    # osutils.walkdirs can be made nicer -
 
2158
                if current_disk[0][1][top_strip_len:] == '':
 
2159
                    # osutils.walkdirs can be made nicer - 
2319
2160
                    # yield the path-from-prefix rather than the pathjoined
2320
2161
                    # value.
2321
 
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
2322
 
                        ('.bzr', '.bzr'))
2323
 
                    if cur_disk_dir_content[bzrdir_loc][0] == '.bzr':
 
2162
                    bzrdir_loc = bisect_left(current_disk[1], ('.bzr', '.bzr'))
 
2163
                    if current_disk[1][bzrdir_loc][0] == '.bzr':
2324
2164
                        # we dont yield the contents of, or, .bzr itself.
2325
 
                        del cur_disk_dir_content[bzrdir_loc]
 
2165
                        del current_disk[1][bzrdir_loc]
2326
2166
            if inv_finished:
2327
2167
                # everything is unknown
2328
2168
                direction = 1
2330
2170
                # everything is missing
2331
2171
                direction = -1
2332
2172
            else:
2333
 
                direction = cmp(current_inv[0][0], cur_disk_dir_relpath)
 
2173
                direction = cmp(current_inv[0][0], current_disk[0][0])
2334
2174
            if direction > 0:
2335
2175
                # disk is before inventory - unknown
2336
2176
                dirblock = [(relpath, basename, kind, stat, None, None) for
2337
 
                    relpath, basename, kind, stat, top_path in
2338
 
                    cur_disk_dir_content]
2339
 
                yield (cur_disk_dir_relpath, None), dirblock
 
2177
                    relpath, basename, kind, stat, top_path in current_disk[1]]
 
2178
                yield (current_disk[0][0], None), dirblock
2340
2179
                try:
2341
2180
                    current_disk = disk_iterator.next()
2342
2181
                except StopIteration:
2344
2183
            elif direction < 0:
2345
2184
                # inventory is before disk - missing.
2346
2185
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
2347
 
                    for relpath, basename, dkind, stat, fileid, kind in
 
2186
                    for relpath, basename, dkind, stat, fileid, kind in 
2348
2187
                    current_inv[1]]
2349
2188
                yield (current_inv[0][0], current_inv[0][1]), dirblock
2350
2189
                try:
2356
2195
                # merge the inventory and disk data together
2357
2196
                dirblock = []
2358
2197
                for relpath, subiterator in itertools.groupby(sorted(
2359
 
                    current_inv[1] + cur_disk_dir_content,
2360
 
                    key=operator.itemgetter(0)), operator.itemgetter(1)):
 
2198
                    current_inv[1] + current_disk[1], key=operator.itemgetter(0)), operator.itemgetter(1)):
2361
2199
                    path_elements = list(subiterator)
2362
2200
                    if len(path_elements) == 2:
2363
2201
                        inv_row, disk_row = path_elements
2389
2227
                    disk_finished = True
2390
2228
 
2391
2229
    def _walkdirs(self, prefix=""):
2392
 
        """Walk the directories of this tree.
2393
 
 
2394
 
           :prefix: is used as the directrory to start with.
2395
 
           returns a generator which yields items in the form:
2396
 
                ((curren_directory_path, fileid),
2397
 
                 [(file1_path, file1_name, file1_kind, None, file1_id,
2398
 
                   file1_kind), ... ])
2399
 
        """
2400
2230
        _directory = 'directory'
2401
2231
        # get the root in the inventory
2402
2232
        inv = self.inventory
2416
2246
                relroot = ""
2417
2247
            # FIXME: stash the node in pending
2418
2248
            entry = inv[top_id]
2419
 
            if entry.kind == 'directory':
2420
 
                for name, child in entry.sorted_children():
2421
 
                    dirblock.append((relroot + name, name, child.kind, None,
2422
 
                        child.file_id, child.kind
2423
 
                        ))
 
2249
            for name, child in entry.sorted_children():
 
2250
                dirblock.append((relroot + name, name, child.kind, None,
 
2251
                    child.file_id, child.kind
 
2252
                    ))
2424
2253
            yield (currentdir[0], entry.file_id), dirblock
2425
2254
            # push the user specified dirs from dirblock
2426
2255
            for dir in reversed(dirblock):
2459
2288
        self.set_conflicts(un_resolved)
2460
2289
        return un_resolved, resolved
2461
2290
 
2462
 
    @needs_read_lock
2463
 
    def _check(self):
2464
 
        tree_basis = self.basis_tree()
2465
 
        tree_basis.lock_read()
2466
 
        try:
2467
 
            repo_basis = self.branch.repository.revision_tree(
2468
 
                self.last_revision())
2469
 
            if len(list(repo_basis.iter_changes(tree_basis))) > 0:
2470
 
                raise errors.BzrCheckError(
2471
 
                    "Mismatched basis inventory content.")
2472
 
            self._validate()
2473
 
        finally:
2474
 
            tree_basis.unlock()
2475
 
 
2476
 
    def _validate(self):
2477
 
        """Validate internal structures.
2478
 
 
2479
 
        This is meant mostly for the test suite. To give it a chance to detect
2480
 
        corruption after actions have occurred. The default implementation is a
2481
 
        just a no-op.
2482
 
 
2483
 
        :return: None. An exception should be raised if there is an error.
2484
 
        """
2485
 
        return
2486
 
 
2487
 
    @needs_read_lock
2488
 
    def _get_rules_searcher(self, default_searcher):
2489
 
        """See Tree._get_rules_searcher."""
2490
 
        if self._rules_searcher is None:
2491
 
            self._rules_searcher = super(WorkingTree,
2492
 
                self)._get_rules_searcher(default_searcher)
2493
 
        return self._rules_searcher
2494
 
 
2495
2291
 
2496
2292
class WorkingTree2(WorkingTree):
2497
2293
    """This is the Format 2 working tree.
2526
2322
            raise
2527
2323
 
2528
2324
    def unlock(self):
2529
 
        # do non-implementation specific cleanup
2530
 
        self._cleanup()
2531
 
 
2532
2325
        # we share control files:
2533
2326
        if self._control_files._lock_count == 3:
2534
2327
            # _inventory_is_modified is always False during a read lock.
2557
2350
    def _last_revision(self):
2558
2351
        """See Mutable.last_revision."""
2559
2352
        try:
2560
 
            return self._transport.get_bytes('last-revision')
 
2353
            return osutils.safe_revision_id(
 
2354
                        self._control_files.get('last-revision').read())
2561
2355
        except errors.NoSuchFile:
2562
 
            return _mod_revision.NULL_REVISION
 
2356
            return None
2563
2357
 
2564
2358
    def _change_last_revision(self, revision_id):
2565
2359
        """See WorkingTree._change_last_revision."""
2566
2360
        if revision_id is None or revision_id == NULL_REVISION:
2567
2361
            try:
2568
 
                self._transport.delete('last-revision')
 
2362
                self._control_files._transport.delete('last-revision')
2569
2363
            except errors.NoSuchFile:
2570
2364
                pass
2571
2365
            return False
2572
2366
        else:
2573
 
            self._transport.put_bytes('last-revision', revision_id,
2574
 
                mode=self._control_files._file_mode)
 
2367
            self._control_files.put_bytes('last-revision', revision_id)
2575
2368
            return True
2576
2369
 
2577
2370
    @needs_tree_write_lock
2589
2382
    @needs_read_lock
2590
2383
    def conflicts(self):
2591
2384
        try:
2592
 
            confile = self._transport.get('conflicts')
 
2385
            confile = self._control_files.get('conflicts')
2593
2386
        except errors.NoSuchFile:
2594
2387
            return _mod_conflicts.ConflictList()
2595
2388
        try:
2600
2393
        return _mod_conflicts.ConflictList.from_stanzas(RioReader(confile))
2601
2394
 
2602
2395
    def unlock(self):
2603
 
        # do non-implementation specific cleanup
2604
 
        self._cleanup()
2605
2396
        if self._control_files._lock_count == 1:
2606
2397
            # _inventory_is_modified is always False during a read lock.
2607
2398
            if self._inventory_is_modified:
2620
2411
            return path[:-len(suffix)]
2621
2412
 
2622
2413
 
 
2414
@deprecated_function(zero_eight)
 
2415
def is_control_file(filename):
 
2416
    """See WorkingTree.is_control_filename(filename)."""
 
2417
    ## FIXME: better check
 
2418
    filename = normpath(filename)
 
2419
    while filename != '':
 
2420
        head, tail = os.path.split(filename)
 
2421
        ## mutter('check %r for control file' % ((head, tail),))
 
2422
        if tail == '.bzr':
 
2423
            return True
 
2424
        if filename == head:
 
2425
            break
 
2426
        filename = head
 
2427
    return False
 
2428
 
 
2429
 
2623
2430
class WorkingTreeFormat(object):
2624
2431
    """An encapsulation of the initialization and open routines for a format.
2625
2432
 
2646
2453
 
2647
2454
    requires_rich_root = False
2648
2455
 
2649
 
    upgrade_recommended = False
2650
 
 
2651
2456
    @classmethod
2652
2457
    def find_format(klass, a_bzrdir):
2653
2458
        """Return the format for the working tree object in a_bzrdir."""
2658
2463
        except errors.NoSuchFile:
2659
2464
            raise errors.NoWorkingTree(base=transport.base)
2660
2465
        except KeyError:
2661
 
            raise errors.UnknownFormatError(format=format_string,
2662
 
                                            kind="working tree")
 
2466
            raise errors.UnknownFormatError(format=format_string)
2663
2467
 
2664
2468
    def __eq__(self, other):
2665
2469
        return self.__class__ is other.__class__
2699
2503
 
2700
2504
    @classmethod
2701
2505
    def unregister_format(klass, format):
 
2506
        assert klass._formats[format.get_format_string()] is format
2702
2507
        del klass._formats[format.get_format_string()]
2703
2508
 
2704
2509
 
 
2510
 
2705
2511
class WorkingTreeFormat2(WorkingTreeFormat):
2706
2512
    """The second working tree format. 
2707
2513
 
2708
2514
    This format modified the hash cache from the format 1 hash cache.
2709
2515
    """
2710
2516
 
2711
 
    upgrade_recommended = True
2712
 
 
2713
2517
    def get_format_description(self):
2714
2518
        """See WorkingTreeFormat.get_format_description()."""
2715
2519
        return "Working tree format 2"
2716
2520
 
2717
 
    def _stub_initialize_remote(self, branch):
2718
 
        """As a special workaround create critical control files for a remote working tree.
 
2521
    def stub_initialize_remote(self, control_files):
 
2522
        """As a special workaround create critical control files for a remote working tree
2719
2523
        
2720
2524
        This ensures that it can later be updated and dealt with locally,
2721
2525
        since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with 
2723
2527
        """
2724
2528
        sio = StringIO()
2725
2529
        inv = Inventory()
2726
 
        xml5.serializer_v5.write_inventory(inv, sio, working=True)
 
2530
        xml5.serializer_v5.write_inventory(inv, sio)
2727
2531
        sio.seek(0)
2728
 
        branch._transport.put_file('inventory', sio,
2729
 
            mode=branch.control_files._file_mode)
2730
 
        branch._transport.put_bytes('pending-merges', '',
2731
 
            mode=branch.control_files._file_mode)
 
2532
        control_files.put('inventory', sio)
 
2533
 
 
2534
        control_files.put_bytes('pending-merges', '')
2732
2535
        
2733
2536
 
2734
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
2735
 
                   accelerator_tree=None, hardlink=False):
 
2537
    def initialize(self, a_bzrdir, revision_id=None):
2736
2538
        """See WorkingTreeFormat.initialize()."""
2737
2539
        if not isinstance(a_bzrdir.transport, LocalTransport):
2738
2540
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
2739
 
        if from_branch is not None:
2740
 
            branch = from_branch
2741
 
        else:
2742
 
            branch = a_bzrdir.open_branch()
2743
 
        if revision_id is None:
2744
 
            revision_id = _mod_revision.ensure_null(branch.last_revision())
2745
 
        branch.lock_write()
2746
 
        try:
2747
 
            branch.generate_revision_history(revision_id)
2748
 
        finally:
2749
 
            branch.unlock()
 
2541
        branch = a_bzrdir.open_branch()
 
2542
        if revision_id is not None:
 
2543
            revision_id = osutils.safe_revision_id(revision_id)
 
2544
            branch.lock_write()
 
2545
            try:
 
2546
                revision_history = branch.revision_history()
 
2547
                try:
 
2548
                    position = revision_history.index(revision_id)
 
2549
                except ValueError:
 
2550
                    raise errors.NoSuchRevision(branch, revision_id)
 
2551
                branch.set_revision_history(revision_history[:position + 1])
 
2552
            finally:
 
2553
                branch.unlock()
 
2554
        revision = branch.last_revision()
2750
2555
        inv = Inventory()
2751
2556
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
2752
2557
                         branch,
2754
2559
                         _internal=True,
2755
2560
                         _format=self,
2756
2561
                         _bzrdir=a_bzrdir)
2757
 
        basis_tree = branch.repository.revision_tree(revision_id)
 
2562
        basis_tree = branch.repository.revision_tree(revision)
2758
2563
        if basis_tree.inventory.root is not None:
2759
 
            wt.set_root_id(basis_tree.get_root_id())
 
2564
            wt.set_root_id(basis_tree.inventory.root.file_id)
2760
2565
        # set the parent list and cache the basis tree.
2761
 
        if _mod_revision.is_null(revision_id):
2762
 
            parent_trees = []
2763
 
        else:
2764
 
            parent_trees = [(revision_id, basis_tree)]
2765
 
        wt.set_parent_trees(parent_trees)
 
2566
        wt.set_parent_trees([(revision, basis_tree)])
2766
2567
        transform.build_tree(basis_tree, wt)
2767
2568
        return wt
2768
2569
 
2781
2582
            raise NotImplementedError
2782
2583
        if not isinstance(a_bzrdir.transport, LocalTransport):
2783
2584
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
2784
 
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
 
2585
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
2785
2586
                           _internal=True,
2786
2587
                           _format=self,
2787
2588
                           _bzrdir=a_bzrdir)
2788
 
        return wt
 
2589
 
2789
2590
 
2790
2591
class WorkingTreeFormat3(WorkingTreeFormat):
2791
2592
    """The second working tree format updated to record a format marker.
2798
2599
        - is new in bzr 0.8
2799
2600
        - uses a LockDir to guard access for writes.
2800
2601
    """
2801
 
    
2802
 
    upgrade_recommended = True
2803
2602
 
2804
2603
    def get_format_string(self):
2805
2604
        """See WorkingTreeFormat.get_format_string()."""
2824
2623
        return LockableFiles(transport, self._lock_file_name, 
2825
2624
                             self._lock_class)
2826
2625
 
2827
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
2828
 
                   accelerator_tree=None, hardlink=False):
 
2626
    def initialize(self, a_bzrdir, revision_id=None):
2829
2627
        """See WorkingTreeFormat.initialize().
2830
2628
        
2831
 
        :param revision_id: if supplied, create a working tree at a different
2832
 
            revision than the branch is at.
2833
 
        :param accelerator_tree: A tree which can be used for retrieving file
2834
 
            contents more quickly than the revision tree, i.e. a workingtree.
2835
 
            The revision tree will be used for cases where accelerator_tree's
2836
 
            content is different.
2837
 
        :param hardlink: If true, hard-link files from accelerator_tree,
2838
 
            where possible.
 
2629
        revision_id allows creating a working tree at a different
 
2630
        revision than the branch is at.
2839
2631
        """
2840
2632
        if not isinstance(a_bzrdir.transport, LocalTransport):
2841
2633
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
2843
2635
        control_files = self._open_control_files(a_bzrdir)
2844
2636
        control_files.create_lock()
2845
2637
        control_files.lock_write()
2846
 
        transport.put_bytes('format', self.get_format_string(),
2847
 
            mode=control_files._file_mode)
2848
 
        if from_branch is not None:
2849
 
            branch = from_branch
 
2638
        control_files.put_utf8('format', self.get_format_string())
 
2639
        branch = a_bzrdir.open_branch()
 
2640
        if revision_id is None:
 
2641
            revision_id = branch.last_revision()
2850
2642
        else:
2851
 
            branch = a_bzrdir.open_branch()
2852
 
        if revision_id is None:
2853
 
            revision_id = _mod_revision.ensure_null(branch.last_revision())
 
2643
            revision_id = osutils.safe_revision_id(revision_id)
2854
2644
        # WorkingTree3 can handle an inventory which has a unique root id.
2855
2645
        # as of bzr 0.12. However, bzr 0.11 and earlier fail to handle
2856
2646
        # those trees. And because there isn't a format bump inbetween, we
2869
2659
            basis_tree = branch.repository.revision_tree(revision_id)
2870
2660
            # only set an explicit root id if there is one to set.
2871
2661
            if basis_tree.inventory.root is not None:
2872
 
                wt.set_root_id(basis_tree.get_root_id())
 
2662
                wt.set_root_id(basis_tree.inventory.root.file_id)
2873
2663
            if revision_id == NULL_REVISION:
2874
2664
                wt.set_parent_trees([])
2875
2665
            else:
2899
2689
            raise NotImplementedError
2900
2690
        if not isinstance(a_bzrdir.transport, LocalTransport):
2901
2691
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
2902
 
        wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
2903
 
        return wt
 
2692
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
2904
2693
 
2905
2694
    def _open(self, a_bzrdir, control_files):
2906
2695
        """Open the tree itself.
2926
2715
# and not independently creatable, so are not registered.
2927
2716
_legacy_formats = [WorkingTreeFormat2(),
2928
2717
                   ]
 
2718
 
 
2719
 
 
2720
class WorkingTreeTestProviderAdapter(object):
 
2721
    """A tool to generate a suite testing multiple workingtree formats at once.
 
2722
 
 
2723
    This is done by copying the test once for each transport and injecting
 
2724
    the transport_server, transport_readonly_server, and workingtree_format
 
2725
    classes into each copy. Each copy is also given a new id() to make it
 
2726
    easy to identify.
 
2727
    """
 
2728
 
 
2729
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2730
        self._transport_server = transport_server
 
2731
        self._transport_readonly_server = transport_readonly_server
 
2732
        self._formats = formats
 
2733
    
 
2734
    def _clone_test(self, test, bzrdir_format, workingtree_format, variation):
 
2735
        """Clone test for adaption."""
 
2736
        new_test = deepcopy(test)
 
2737
        new_test.transport_server = self._transport_server
 
2738
        new_test.transport_readonly_server = self._transport_readonly_server
 
2739
        new_test.bzrdir_format = bzrdir_format
 
2740
        new_test.workingtree_format = workingtree_format
 
2741
        def make_new_test_id():
 
2742
            new_id = "%s(%s)" % (test.id(), variation)
 
2743
            return lambda: new_id
 
2744
        new_test.id = make_new_test_id()
 
2745
        return new_test
 
2746
    
 
2747
    def adapt(self, test):
 
2748
        from bzrlib.tests import TestSuite
 
2749
        result = TestSuite()
 
2750
        for workingtree_format, bzrdir_format in self._formats:
 
2751
            new_test = self._clone_test(
 
2752
                test,
 
2753
                bzrdir_format,
 
2754
                workingtree_format, workingtree_format.__class__.__name__)
 
2755
            result.addTest(new_test)
 
2756
        return result