~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] from robert and fix up tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
35
                           DivergedBranches, LockError, UnlistableStore,
36
 
                           UnlistableBranch, NoSuchFile)
 
36
                           UnlistableBranch, NoSuchFile, NotVersionedError)
37
37
from bzrlib.textui import show_status
38
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
39
39
 
44
44
from bzrlib.store.compressed_text import CompressedTextStore
45
45
from bzrlib.store.text import TextStore
46
46
from bzrlib.store.weave import WeaveStore
 
47
from bzrlib.testament import Testament
47
48
import bzrlib.transactions as transactions
48
49
from bzrlib.transport import Transport, get_transport
49
50
import bzrlib.xml5
66
67
    raise NotImplementedError('find_branch() is not supported anymore, '
67
68
                              'please use one of the new branch constructors')
68
69
 
 
70
 
 
71
def needs_read_lock(unbound):
 
72
    """Decorate unbound to take out and release a read lock."""
 
73
    def decorated(self, *args, **kwargs):
 
74
        self.lock_read()
 
75
        try:
 
76
            return unbound(self, *args, **kwargs)
 
77
        finally:
 
78
            self.unlock()
 
79
    return decorated
 
80
 
 
81
 
 
82
def needs_write_lock(unbound):
 
83
    """Decorate unbound to take out and release a write lock."""
 
84
    def decorated(self, *args, **kwargs):
 
85
        self.lock_write()
 
86
        try:
 
87
            return unbound(self, *args, **kwargs)
 
88
        finally:
 
89
            self.unlock()
 
90
    return decorated
 
91
 
69
92
######################################################################
70
93
# branch objects
71
94
 
102
125
 
103
126
        Basically we keep looking up until we find the control directory or
104
127
        run into the root.  If there isn't one, raises NotBranchError.
 
128
        If there is one, it is returned, along with the unused portion of url.
105
129
        """
106
130
        t = get_transport(url)
107
131
        while True:
108
132
            try:
109
 
                return _Branch(t)
 
133
                return _Branch(t), t.relpath(url)
110
134
            except NotBranchError:
111
135
                pass
112
136
            new_t = t.clone('..')
243
267
            self.weave_store = get_weave('weaves', prefixed=True)
244
268
            self.revision_store = get_store('revision-store', compressed=False,
245
269
                                            prefixed=True)
 
270
        self.revision_store.register_suffix('sig')
246
271
        self._transaction = None
247
272
 
248
273
    def __str__(self):
505
530
                entry.parent_id = inv.root.file_id
506
531
        self._write_inventory(inv)
507
532
 
 
533
    @needs_read_lock
508
534
    def read_working_inventory(self):
509
535
        """Read the working inventory."""
510
 
        self.lock_read()
511
 
        try:
512
 
            # ElementTree does its own conversion from UTF-8, so open in
513
 
            # binary.
514
 
            f = self.controlfile('inventory', 'rb')
515
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
516
 
        finally:
517
 
            self.unlock()
518
 
            
 
536
        # ElementTree does its own conversion from UTF-8, so open in
 
537
        # binary.
 
538
        f = self.controlfile('inventory', 'rb')
 
539
        return bzrlib.xml5.serializer_v5.read_inventory(f)
519
540
 
 
541
    @needs_write_lock
520
542
    def _write_inventory(self, inv):
521
543
        """Update the working inventory.
522
544
 
524
546
        will be committed to the next revision.
525
547
        """
526
548
        from cStringIO import StringIO
527
 
        self.lock_write()
528
 
        try:
529
 
            sio = StringIO()
530
 
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
531
 
            sio.seek(0)
532
 
            # Transport handles atomicity
533
 
            self.put_controlfile('inventory', sio)
534
 
        finally:
535
 
            self.unlock()
 
549
        sio = StringIO()
 
550
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
551
        sio.seek(0)
 
552
        # Transport handles atomicity
 
553
        self.put_controlfile('inventory', sio)
536
554
        
537
555
        mutter('wrote working inventory')
538
556
            
539
557
    inventory = property(read_working_inventory, _write_inventory, None,
540
558
                         """Inventory for the working copy.""")
541
559
 
 
560
    @needs_write_lock
542
561
    def add(self, files, ids=None):
543
562
        """Make files versioned.
544
563
 
574
593
        else:
575
594
            assert(len(ids) == len(files))
576
595
 
577
 
        self.lock_write()
578
 
        try:
579
 
            inv = self.read_working_inventory()
580
 
            for f,file_id in zip(files, ids):
581
 
                if is_control_file(f):
582
 
                    raise BzrError("cannot add control file %s" % quotefn(f))
583
 
 
584
 
                fp = splitpath(f)
585
 
 
586
 
                if len(fp) == 0:
587
 
                    raise BzrError("cannot add top-level %r" % f)
588
 
 
589
 
                fullpath = os.path.normpath(self.abspath(f))
590
 
 
591
 
                try:
592
 
                    kind = file_kind(fullpath)
593
 
                except OSError:
594
 
                    # maybe something better?
595
 
                    raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
596
 
 
597
 
                if not InventoryEntry.versionable_kind(kind):
598
 
                    raise BzrError('cannot add: not a versionable file ('
599
 
                                   'i.e. regular file, symlink or directory): %s' % quotefn(f))
600
 
 
601
 
                if file_id is None:
602
 
                    file_id = gen_file_id(f)
603
 
                inv.add_path(f, kind=kind, file_id=file_id)
604
 
 
605
 
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
606
 
 
607
 
            self._write_inventory(inv)
608
 
        finally:
609
 
            self.unlock()
610
 
            
611
 
 
 
596
        inv = self.read_working_inventory()
 
597
        for f,file_id in zip(files, ids):
 
598
            if is_control_file(f):
 
599
                raise BzrError("cannot add control file %s" % quotefn(f))
 
600
 
 
601
            fp = splitpath(f)
 
602
 
 
603
            if len(fp) == 0:
 
604
                raise BzrError("cannot add top-level %r" % f)
 
605
 
 
606
            fullpath = os.path.normpath(self.abspath(f))
 
607
 
 
608
            try:
 
609
                kind = file_kind(fullpath)
 
610
            except OSError:
 
611
                # maybe something better?
 
612
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
613
 
 
614
            if not InventoryEntry.versionable_kind(kind):
 
615
                raise BzrError('cannot add: not a versionable file ('
 
616
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
617
 
 
618
            if file_id is None:
 
619
                file_id = gen_file_id(f)
 
620
            inv.add_path(f, kind=kind, file_id=file_id)
 
621
 
 
622
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
623
 
 
624
        self._write_inventory(inv)
 
625
 
 
626
    @needs_read_lock
612
627
    def print_file(self, file, revno):
613
628
        """Print `file` to stdout."""
614
 
        self.lock_read()
615
 
        try:
616
 
            tree = self.revision_tree(self.get_rev_id(revno))
617
 
            # use inventory as it was in that revision
618
 
            file_id = tree.inventory.path2id(file)
619
 
            if not file_id:
620
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
621
 
            tree.print_file(file_id)
622
 
        finally:
623
 
            self.unlock()
624
 
 
625
 
 
626
 
    def remove(self, files, verbose=False):
627
 
        """Mark nominated files for removal from the inventory.
628
 
 
629
 
        This does not remove their text.  This does not run on 
630
 
 
631
 
        TODO: Refuse to remove modified files unless --force is given?
632
 
 
633
 
        TODO: Do something useful with directories.
634
 
 
635
 
        TODO: Should this remove the text or not?  Tough call; not
636
 
        removing may be useful and the user can just use use rm, and
637
 
        is the opposite of add.  Removing it is consistent with most
638
 
        other tools.  Maybe an option.
639
 
        """
640
 
        ## TODO: Normalize names
641
 
        ## TODO: Remove nested loops; better scalability
642
 
        if isinstance(files, basestring):
643
 
            files = [files]
644
 
 
645
 
        self.lock_write()
646
 
 
647
 
        try:
648
 
            tree = self.working_tree()
649
 
            inv = tree.inventory
650
 
 
651
 
            # do this before any modifications
652
 
            for f in files:
653
 
                fid = inv.path2id(f)
654
 
                if not fid:
655
 
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
656
 
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
657
 
                if verbose:
658
 
                    # having remove it, it must be either ignored or unknown
659
 
                    if tree.is_ignored(f):
660
 
                        new_status = 'I'
661
 
                    else:
662
 
                        new_status = '?'
663
 
                    show_status(new_status, inv[fid].kind, quotefn(f))
664
 
                del inv[fid]
665
 
 
666
 
            self._write_inventory(inv)
667
 
        finally:
668
 
            self.unlock()
 
629
        tree = self.revision_tree(self.get_rev_id(revno))
 
630
        # use inventory as it was in that revision
 
631
        file_id = tree.inventory.path2id(file)
 
632
        if not file_id:
 
633
            raise BzrError("%r is not present in revision %s" % (file, revno))
 
634
        tree.print_file(file_id)
669
635
 
670
636
    # FIXME: this doesn't need to be a branch method
671
637
    def set_inventory(self, new_inventory_list):
692
658
        These are files in the working directory that are not versioned or
693
659
        control files or ignored.
694
660
        
 
661
        >>> from bzrlib.workingtree import WorkingTree
695
662
        >>> b = ScratchBranch(files=['foo', 'foo~'])
696
663
        >>> map(str, b.unknowns())
697
664
        ['foo']
698
665
        >>> b.add('foo')
699
666
        >>> list(b.unknowns())
700
667
        []
701
 
        >>> b.remove('foo')
702
 
        >>> map(str, b.unknowns())
 
668
        >>> WorkingTree(b.base, b).remove('foo')
 
669
        >>> list(b.unknowns())
703
670
        ['foo']
704
671
        """
705
672
        return self.working_tree().unknowns()
706
673
 
707
 
 
 
674
    @needs_write_lock
708
675
    def append_revision(self, *revision_ids):
709
676
        for revision_id in revision_ids:
710
677
            mutter("add {%s} to revision-history" % revision_id)
711
 
        self.lock_write()
712
 
        try:
713
 
            rev_history = self.revision_history()
714
 
            rev_history.extend(revision_ids)
715
 
            self.put_controlfile('revision-history', '\n'.join(rev_history))
716
 
        finally:
717
 
            self.unlock()
 
678
        rev_history = self.revision_history()
 
679
        rev_history.extend(revision_ids)
 
680
        self.put_controlfile('revision-history', '\n'.join(rev_history))
718
681
 
719
682
    def has_revision(self, revision_id):
720
683
        """True if this branch has a copy of the revision.
722
685
        This does not necessarily imply the revision is merge
723
686
        or on the mainline."""
724
687
        return (revision_id is None
725
 
                or revision_id in self.revision_store)
 
688
                or self.revision_store.has_id(revision_id))
726
689
 
 
690
    @needs_read_lock
727
691
    def get_revision_xml_file(self, revision_id):
728
692
        """Return XML file object for revision object."""
729
693
        if not revision_id or not isinstance(revision_id, basestring):
730
694
            raise InvalidRevisionId(revision_id)
731
 
 
732
 
        self.lock_read()
733
695
        try:
734
 
            try:
735
 
                return self.revision_store[revision_id]
736
 
            except (IndexError, KeyError):
737
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
738
 
        finally:
739
 
            self.unlock()
 
696
            return self.revision_store.get(revision_id)
 
697
        except (IndexError, KeyError):
 
698
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
740
699
 
741
700
    #deprecated
742
701
    get_revision_xml = get_revision_xml_file
835
794
        else:
836
795
            return self.get_inventory(revision_id)
837
796
 
 
797
    @needs_read_lock
838
798
    def revision_history(self):
839
799
        """Return sequence of revision hashes on to this branch."""
840
 
        self.lock_read()
841
 
        try:
842
 
            transaction = self.get_transaction()
843
 
            history = transaction.map.find_revision_history()
844
 
            if history is not None:
845
 
                mutter("cache hit for revision-history in %s", self)
846
 
                return list(history)
847
 
            history = [l.rstrip('\r\n') for l in
848
 
                    self.controlfile('revision-history', 'r').readlines()]
849
 
            transaction.map.add_revision_history(history)
850
 
            # this call is disabled because revision_history is 
851
 
            # not really an object yet, and the transaction is for objects.
852
 
            # transaction.register_clean(history, precious=True)
 
800
        transaction = self.get_transaction()
 
801
        history = transaction.map.find_revision_history()
 
802
        if history is not None:
 
803
            mutter("cache hit for revision-history in %s", self)
853
804
            return list(history)
854
 
        finally:
855
 
            self.unlock()
 
805
        history = [l.rstrip('\r\n') for l in
 
806
                self.controlfile('revision-history', 'r').readlines()]
 
807
        transaction.map.add_revision_history(history)
 
808
        # this call is disabled because revision_history is 
 
809
        # not really an object yet, and the transaction is for objects.
 
810
        # transaction.register_clean(history, precious=True)
 
811
        return list(history)
856
812
 
857
813
    def revno(self):
858
814
        """Return current revision number for this branch.
862
818
        """
863
819
        return len(self.revision_history())
864
820
 
865
 
 
866
821
    def last_revision(self):
867
822
        """Return last patch hash, or None if no history.
868
823
        """
872
827
        else:
873
828
            return None
874
829
 
875
 
 
876
830
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
877
831
        """Return a list of new revisions that would perfectly fit.
878
832
        
952
906
                else:
953
907
                    raise e
954
908
        
955
 
 
956
909
    def commit(self, *args, **kw):
957
910
        from bzrlib.commit import Commit
958
911
        Commit().commit(self, *args, **kw)
990
943
            inv = self.get_revision_inventory(revision_id)
991
944
            return RevisionTree(self.weave_store, inv, revision_id)
992
945
 
993
 
 
994
946
    def working_tree(self):
995
947
        """Return a `Tree` for the working copy."""
996
948
        from bzrlib.workingtree import WorkingTree
997
 
        # TODO: In the future, WorkingTree should utilize Transport
 
949
        # TODO: In the future, perhaps WorkingTree should utilize Transport
998
950
        # RobertCollins 20051003 - I don't think it should - working trees are
999
951
        # much more complex to keep consistent than our careful .bzr subset.
1000
952
        # instead, we should say that working trees are local only, and optimise
1009
961
        """
1010
962
        return self.revision_tree(self.last_revision())
1011
963
 
1012
 
 
 
964
    @needs_write_lock
1013
965
    def rename_one(self, from_rel, to_rel):
1014
966
        """Rename one file.
1015
967
 
1016
968
        This can change the directory or the filename or both.
1017
969
        """
1018
 
        self.lock_write()
 
970
        tree = self.working_tree()
 
971
        inv = tree.inventory
 
972
        if not tree.has_filename(from_rel):
 
973
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
974
        if tree.has_filename(to_rel):
 
975
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
976
 
 
977
        file_id = inv.path2id(from_rel)
 
978
        if file_id == None:
 
979
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
980
 
 
981
        if inv.path2id(to_rel):
 
982
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
983
 
 
984
        to_dir, to_tail = os.path.split(to_rel)
 
985
        to_dir_id = inv.path2id(to_dir)
 
986
        if to_dir_id == None and to_dir != '':
 
987
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
988
 
 
989
        mutter("rename_one:")
 
990
        mutter("  file_id    {%s}" % file_id)
 
991
        mutter("  from_rel   %r" % from_rel)
 
992
        mutter("  to_rel     %r" % to_rel)
 
993
        mutter("  to_dir     %r" % to_dir)
 
994
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
995
 
 
996
        inv.rename(file_id, to_dir_id, to_tail)
 
997
 
 
998
        from_abs = self.abspath(from_rel)
 
999
        to_abs = self.abspath(to_rel)
1019
1000
        try:
1020
 
            tree = self.working_tree()
1021
 
            inv = tree.inventory
1022
 
            if not tree.has_filename(from_rel):
1023
 
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1024
 
            if tree.has_filename(to_rel):
1025
 
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
1026
 
 
1027
 
            file_id = inv.path2id(from_rel)
1028
 
            if file_id == None:
1029
 
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1030
 
 
1031
 
            if inv.path2id(to_rel):
1032
 
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1033
 
 
1034
 
            to_dir, to_tail = os.path.split(to_rel)
1035
 
            to_dir_id = inv.path2id(to_dir)
1036
 
            if to_dir_id == None and to_dir != '':
1037
 
                raise BzrError("can't determine destination directory id for %r" % to_dir)
1038
 
 
1039
 
            mutter("rename_one:")
1040
 
            mutter("  file_id    {%s}" % file_id)
1041
 
            mutter("  from_rel   %r" % from_rel)
1042
 
            mutter("  to_rel     %r" % to_rel)
1043
 
            mutter("  to_dir     %r" % to_dir)
1044
 
            mutter("  to_dir_id  {%s}" % to_dir_id)
1045
 
 
1046
 
            inv.rename(file_id, to_dir_id, to_tail)
1047
 
 
1048
 
            from_abs = self.abspath(from_rel)
1049
 
            to_abs = self.abspath(to_rel)
1050
 
            try:
1051
 
                rename(from_abs, to_abs)
1052
 
            except OSError, e:
1053
 
                raise BzrError("failed to rename %r to %r: %s"
1054
 
                        % (from_abs, to_abs, e[1]),
1055
 
                        ["rename rolled back"])
1056
 
 
1057
 
            self._write_inventory(inv)
1058
 
        finally:
1059
 
            self.unlock()
1060
 
 
1061
 
 
 
1001
            rename(from_abs, to_abs)
 
1002
        except OSError, e:
 
1003
            raise BzrError("failed to rename %r to %r: %s"
 
1004
                    % (from_abs, to_abs, e[1]),
 
1005
                    ["rename rolled back"])
 
1006
 
 
1007
        self._write_inventory(inv)
 
1008
 
 
1009
    @needs_write_lock
1062
1010
    def move(self, from_paths, to_name):
1063
1011
        """Rename files.
1064
1012
 
1074
1022
        entry that is moved.
1075
1023
        """
1076
1024
        result = []
1077
 
        self.lock_write()
1078
 
        try:
1079
 
            ## TODO: Option to move IDs only
1080
 
            assert not isinstance(from_paths, basestring)
1081
 
            tree = self.working_tree()
1082
 
            inv = tree.inventory
1083
 
            to_abs = self.abspath(to_name)
1084
 
            if not isdir(to_abs):
1085
 
                raise BzrError("destination %r is not a directory" % to_abs)
1086
 
            if not tree.has_filename(to_name):
1087
 
                raise BzrError("destination %r not in working directory" % to_abs)
1088
 
            to_dir_id = inv.path2id(to_name)
1089
 
            if to_dir_id == None and to_name != '':
1090
 
                raise BzrError("destination %r is not a versioned directory" % to_name)
1091
 
            to_dir_ie = inv[to_dir_id]
1092
 
            if to_dir_ie.kind not in ('directory', 'root_directory'):
1093
 
                raise BzrError("destination %r is not a directory" % to_abs)
1094
 
 
1095
 
            to_idpath = inv.get_idpath(to_dir_id)
1096
 
 
1097
 
            for f in from_paths:
1098
 
                if not tree.has_filename(f):
1099
 
                    raise BzrError("%r does not exist in working tree" % f)
1100
 
                f_id = inv.path2id(f)
1101
 
                if f_id == None:
1102
 
                    raise BzrError("%r is not versioned" % f)
1103
 
                name_tail = splitpath(f)[-1]
1104
 
                dest_path = appendpath(to_name, name_tail)
1105
 
                if tree.has_filename(dest_path):
1106
 
                    raise BzrError("destination %r already exists" % dest_path)
1107
 
                if f_id in to_idpath:
1108
 
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
1109
 
 
1110
 
            # OK, so there's a race here, it's possible that someone will
1111
 
            # create a file in this interval and then the rename might be
1112
 
            # left half-done.  But we should have caught most problems.
1113
 
 
1114
 
            for f in from_paths:
1115
 
                name_tail = splitpath(f)[-1]
1116
 
                dest_path = appendpath(to_name, name_tail)
1117
 
                result.append((f, dest_path))
1118
 
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1119
 
                try:
1120
 
                    rename(self.abspath(f), self.abspath(dest_path))
1121
 
                except OSError, e:
1122
 
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1123
 
                            ["rename rolled back"])
1124
 
 
1125
 
            self._write_inventory(inv)
1126
 
        finally:
1127
 
            self.unlock()
1128
 
 
 
1025
        ## TODO: Option to move IDs only
 
1026
        assert not isinstance(from_paths, basestring)
 
1027
        tree = self.working_tree()
 
1028
        inv = tree.inventory
 
1029
        to_abs = self.abspath(to_name)
 
1030
        if not isdir(to_abs):
 
1031
            raise BzrError("destination %r is not a directory" % to_abs)
 
1032
        if not tree.has_filename(to_name):
 
1033
            raise BzrError("destination %r not in working directory" % to_abs)
 
1034
        to_dir_id = inv.path2id(to_name)
 
1035
        if to_dir_id == None and to_name != '':
 
1036
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
1037
        to_dir_ie = inv[to_dir_id]
 
1038
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
1039
            raise BzrError("destination %r is not a directory" % to_abs)
 
1040
 
 
1041
        to_idpath = inv.get_idpath(to_dir_id)
 
1042
 
 
1043
        for f in from_paths:
 
1044
            if not tree.has_filename(f):
 
1045
                raise BzrError("%r does not exist in working tree" % f)
 
1046
            f_id = inv.path2id(f)
 
1047
            if f_id == None:
 
1048
                raise BzrError("%r is not versioned" % f)
 
1049
            name_tail = splitpath(f)[-1]
 
1050
            dest_path = appendpath(to_name, name_tail)
 
1051
            if tree.has_filename(dest_path):
 
1052
                raise BzrError("destination %r already exists" % dest_path)
 
1053
            if f_id in to_idpath:
 
1054
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
1055
 
 
1056
        # OK, so there's a race here, it's possible that someone will
 
1057
        # create a file in this interval and then the rename might be
 
1058
        # left half-done.  But we should have caught most problems.
 
1059
 
 
1060
        for f in from_paths:
 
1061
            name_tail = splitpath(f)[-1]
 
1062
            dest_path = appendpath(to_name, name_tail)
 
1063
            result.append((f, dest_path))
 
1064
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
1065
            try:
 
1066
                rename(self.abspath(f), self.abspath(dest_path))
 
1067
            except OSError, e:
 
1068
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
 
1069
                        ["rename rolled back"])
 
1070
 
 
1071
        self._write_inventory(inv)
1129
1072
        return result
1130
1073
 
1131
1074
 
1136
1079
            If true (default) backups are made of files before
1137
1080
            they're renamed.
1138
1081
        """
1139
 
        from bzrlib.errors import NotVersionedError, BzrError
1140
1082
        from bzrlib.atomicfile import AtomicFile
1141
1083
        from bzrlib.osutils import backup_file
1142
1084
        
1149
1091
        for fn in filenames:
1150
1092
            file_id = inv.path2id(fn)
1151
1093
            if not file_id:
1152
 
                raise NotVersionedError("not a versioned file", fn)
 
1094
                raise NotVersionedError(path=fn)
1153
1095
            if not old_inv.has_id(file_id):
1154
1096
                raise BzrError("file not present in old tree", fn, file_id)
1155
1097
            nids.append((fn, file_id))
1201
1143
        if updated:
1202
1144
            self.set_pending_merges(p)
1203
1145
 
 
1146
    @needs_write_lock
1204
1147
    def set_pending_merges(self, rev_list):
1205
 
        self.lock_write()
1206
 
        try:
1207
 
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1208
 
        finally:
1209
 
            self.unlock()
1210
 
 
 
1148
        self.put_controlfile('pending-merges', '\n'.join(rev_list))
1211
1149
 
1212
1150
    def get_parent(self):
1213
1151
        """Return the parent location of the branch.
1226
1164
                    raise
1227
1165
        return None
1228
1166
 
1229
 
 
 
1167
    @needs_write_lock
1230
1168
    def set_parent(self, url):
1231
1169
        # TODO: Maybe delete old location files?
1232
1170
        from bzrlib.atomicfile import AtomicFile
1233
 
        self.lock_write()
 
1171
        f = AtomicFile(self.controlfilename('parent'))
1234
1172
        try:
1235
 
            f = AtomicFile(self.controlfilename('parent'))
1236
 
            try:
1237
 
                f.write(url + '\n')
1238
 
                f.commit()
1239
 
            finally:
1240
 
                f.close()
 
1173
            f.write(url + '\n')
 
1174
            f.commit()
1241
1175
        finally:
1242
 
            self.unlock()
 
1176
            f.close()
1243
1177
 
1244
1178
    def check_revno(self, revno):
1245
1179
        """\
1257
1191
        if revno < 1 or revno > self.revno():
1258
1192
            raise InvalidRevisionNumber(revno)
1259
1193
        
1260
 
        
1261
 
        
 
1194
    def sign_revision(self, revision_id, gpg_strategy):
 
1195
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
1196
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1197
 
 
1198
    @needs_write_lock
 
1199
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1200
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
1201
                                revision_id, "sig")
1262
1202
 
1263
1203
 
1264
1204
class ScratchBranch(_Branch):
1268
1208
    >>> isdir(b.base)
1269
1209
    True
1270
1210
    >>> bd = b.base
1271
 
    >>> b.destroy()
 
1211
    >>> b._transport.__del__()
1272
1212
    >>> isdir(bd)
1273
1213
    False
1274
1214
    """
1275
 
    def __init__(self, files=[], dirs=[], base=None):
 
1215
 
 
1216
    def __init__(self, files=[], dirs=[], transport=None):
1276
1217
        """Make a test branch.
1277
1218
 
1278
1219
        This creates a temporary directory and runs init-tree in it.
1279
1220
 
1280
1221
        If any files are listed, they are created in the working copy.
1281
1222
        """
1282
 
        from tempfile import mkdtemp
1283
 
        init = False
1284
 
        if base is None:
1285
 
            base = mkdtemp()
1286
 
            init = True
1287
 
        if isinstance(base, basestring):
1288
 
            base = get_transport(base)
1289
 
        _Branch.__init__(self, base, init=init)
 
1223
        if transport is None:
 
1224
            transport = bzrlib.transport.local.ScratchTransport()
 
1225
            super(ScratchBranch, self).__init__(transport, init=True)
 
1226
        else:
 
1227
            super(ScratchBranch, self).__init__(transport)
 
1228
 
1290
1229
        for d in dirs:
1291
1230
            self._transport.mkdir(d)
1292
1231
            
1312
1251
        base = mkdtemp()
1313
1252
        os.rmdir(base)
1314
1253
        copytree(self.base, base, symlinks=True)
1315
 
        return ScratchBranch(base=base)
1316
 
 
1317
 
    def __del__(self):
1318
 
        self.destroy()
1319
 
 
1320
 
    def destroy(self):
1321
 
        """Destroy the test branch, removing the scratch directory."""
1322
 
        from shutil import rmtree
1323
 
        try:
1324
 
            if self.base:
1325
 
                mutter("delete ScratchBranch %s" % self.base)
1326
 
                rmtree(self.base)
1327
 
        except OSError, e:
1328
 
            # Work around for shutil.rmtree failing on Windows when
1329
 
            # readonly files are encountered
1330
 
            mutter("hit exception in destroying ScratchBranch: %s" % e)
1331
 
            for root, dirs, files in os.walk(self.base, topdown=False):
1332
 
                for name in files:
1333
 
                    os.chmod(os.path.join(root, name), 0700)
1334
 
            rmtree(self.base)
1335
 
        self._transport = None
1336
 
 
 
1254
        return ScratchBranch(
 
1255
            transport=bzrlib.transport.local.ScratchTransport(base))
1337
1256
    
1338
1257
 
1339
1258
######################################################################