~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-10-17 23:35:18 UTC
  • mfrom: (1442.1.65)
  • Revision ID: robertc@robertcollins.net-20051017233518-6746654be564edde
Merge in more GPG work, and more Branch-api-shrinkage.

* Branch.remove has been moved to WorkingTree, which has also gained
  lock_read, lock_write and unlock methods for convenience. (Robert
  Collins)

* Two decorators, needs_read_lock and needs_write_lock have been added
  to the branch module. Use these to cause a function to run in a
  read or write lock respectively. (Robert Collins)

* Branch.open_containing now returns a tuple (Branch, relative-path),
  which allows direct access to the common case of 'get me this file
  from its branch'. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
67
67
    raise NotImplementedError('find_branch() is not supported anymore, '
68
68
                              'please use one of the new branch constructors')
69
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
 
70
92
######################################################################
71
93
# branch objects
72
94
 
103
125
 
104
126
        Basically we keep looking up until we find the control directory or
105
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.
106
129
        """
107
130
        t = get_transport(url)
108
131
        while True:
109
132
            try:
110
 
                return _Branch(t)
 
133
                return _Branch(t), t.relpath(url)
111
134
            except NotBranchError:
112
135
                pass
113
136
            new_t = t.clone('..')
507
530
                entry.parent_id = inv.root.file_id
508
531
        self._write_inventory(inv)
509
532
 
 
533
    @needs_read_lock
510
534
    def read_working_inventory(self):
511
535
        """Read the working inventory."""
512
 
        self.lock_read()
513
 
        try:
514
 
            # ElementTree does its own conversion from UTF-8, so open in
515
 
            # binary.
516
 
            f = self.controlfile('inventory', 'rb')
517
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
518
 
        finally:
519
 
            self.unlock()
520
 
            
 
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)
521
540
 
 
541
    @needs_write_lock
522
542
    def _write_inventory(self, inv):
523
543
        """Update the working inventory.
524
544
 
526
546
        will be committed to the next revision.
527
547
        """
528
548
        from cStringIO import StringIO
529
 
        self.lock_write()
530
 
        try:
531
 
            sio = StringIO()
532
 
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
533
 
            sio.seek(0)
534
 
            # Transport handles atomicity
535
 
            self.put_controlfile('inventory', sio)
536
 
        finally:
537
 
            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)
538
554
        
539
555
        mutter('wrote working inventory')
540
556
            
541
557
    inventory = property(read_working_inventory, _write_inventory, None,
542
558
                         """Inventory for the working copy.""")
543
559
 
 
560
    @needs_write_lock
544
561
    def add(self, files, ids=None):
545
562
        """Make files versioned.
546
563
 
576
593
        else:
577
594
            assert(len(ids) == len(files))
578
595
 
579
 
        self.lock_write()
580
 
        try:
581
 
            inv = self.read_working_inventory()
582
 
            for f,file_id in zip(files, ids):
583
 
                if is_control_file(f):
584
 
                    raise BzrError("cannot add control file %s" % quotefn(f))
585
 
 
586
 
                fp = splitpath(f)
587
 
 
588
 
                if len(fp) == 0:
589
 
                    raise BzrError("cannot add top-level %r" % f)
590
 
 
591
 
                fullpath = os.path.normpath(self.abspath(f))
592
 
 
593
 
                try:
594
 
                    kind = file_kind(fullpath)
595
 
                except OSError:
596
 
                    # maybe something better?
597
 
                    raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
598
 
 
599
 
                if not InventoryEntry.versionable_kind(kind):
600
 
                    raise BzrError('cannot add: not a versionable file ('
601
 
                                   'i.e. regular file, symlink or directory): %s' % quotefn(f))
602
 
 
603
 
                if file_id is None:
604
 
                    file_id = gen_file_id(f)
605
 
                inv.add_path(f, kind=kind, file_id=file_id)
606
 
 
607
 
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
608
 
 
609
 
            self._write_inventory(inv)
610
 
        finally:
611
 
            self.unlock()
612
 
            
613
 
 
 
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
614
627
    def print_file(self, file, revno):
615
628
        """Print `file` to stdout."""
616
 
        self.lock_read()
617
 
        try:
618
 
            tree = self.revision_tree(self.get_rev_id(revno))
619
 
            # use inventory as it was in that revision
620
 
            file_id = tree.inventory.path2id(file)
621
 
            if not file_id:
622
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
623
 
            tree.print_file(file_id)
624
 
        finally:
625
 
            self.unlock()
626
 
 
627
 
 
628
 
    def remove(self, files, verbose=False):
629
 
        """Mark nominated files for removal from the inventory.
630
 
 
631
 
        This does not remove their text.  This does not run on 
632
 
 
633
 
        TODO: Refuse to remove modified files unless --force is given?
634
 
 
635
 
        TODO: Do something useful with directories.
636
 
 
637
 
        TODO: Should this remove the text or not?  Tough call; not
638
 
        removing may be useful and the user can just use use rm, and
639
 
        is the opposite of add.  Removing it is consistent with most
640
 
        other tools.  Maybe an option.
641
 
        """
642
 
        ## TODO: Normalize names
643
 
        ## TODO: Remove nested loops; better scalability
644
 
        if isinstance(files, basestring):
645
 
            files = [files]
646
 
 
647
 
        self.lock_write()
648
 
 
649
 
        try:
650
 
            tree = self.working_tree()
651
 
            inv = tree.inventory
652
 
 
653
 
            # do this before any modifications
654
 
            for f in files:
655
 
                fid = inv.path2id(f)
656
 
                if not fid:
657
 
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
658
 
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
659
 
                if verbose:
660
 
                    # having remove it, it must be either ignored or unknown
661
 
                    if tree.is_ignored(f):
662
 
                        new_status = 'I'
663
 
                    else:
664
 
                        new_status = '?'
665
 
                    show_status(new_status, inv[fid].kind, quotefn(f))
666
 
                del inv[fid]
667
 
 
668
 
            self._write_inventory(inv)
669
 
        finally:
670
 
            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)
671
635
 
672
636
    # FIXME: this doesn't need to be a branch method
673
637
    def set_inventory(self, new_inventory_list):
700
664
        >>> b.add('foo')
701
665
        >>> list(b.unknowns())
702
666
        []
703
 
        >>> b.remove('foo')
 
667
        >>> WorkingTree(b.base, b).remove('foo')
704
668
        >>> list(b.unknowns())
705
669
        ['foo']
706
670
        """
707
671
        return self.working_tree().unknowns()
708
672
 
709
 
 
 
673
    @needs_write_lock
710
674
    def append_revision(self, *revision_ids):
711
675
        for revision_id in revision_ids:
712
676
            mutter("add {%s} to revision-history" % revision_id)
713
 
        self.lock_write()
714
 
        try:
715
 
            rev_history = self.revision_history()
716
 
            rev_history.extend(revision_ids)
717
 
            self.put_controlfile('revision-history', '\n'.join(rev_history))
718
 
        finally:
719
 
            self.unlock()
 
677
        rev_history = self.revision_history()
 
678
        rev_history.extend(revision_ids)
 
679
        self.put_controlfile('revision-history', '\n'.join(rev_history))
720
680
 
721
681
    def has_revision(self, revision_id):
722
682
        """True if this branch has a copy of the revision.
726
686
        return (revision_id is None
727
687
                or self.revision_store.has_id(revision_id))
728
688
 
 
689
    @needs_read_lock
729
690
    def get_revision_xml_file(self, revision_id):
730
691
        """Return XML file object for revision object."""
731
692
        if not revision_id or not isinstance(revision_id, basestring):
732
693
            raise InvalidRevisionId(revision_id)
733
 
 
734
 
        self.lock_read()
735
694
        try:
736
 
            try:
737
 
                return self.revision_store.get(revision_id)
738
 
            except (IndexError, KeyError):
739
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
740
 
        finally:
741
 
            self.unlock()
 
695
            return self.revision_store.get(revision_id)
 
696
        except (IndexError, KeyError):
 
697
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
742
698
 
743
699
    #deprecated
744
700
    get_revision_xml = get_revision_xml_file
837
793
        else:
838
794
            return self.get_inventory(revision_id)
839
795
 
 
796
    @needs_read_lock
840
797
    def revision_history(self):
841
798
        """Return sequence of revision hashes on to this branch."""
842
 
        self.lock_read()
843
 
        try:
844
 
            transaction = self.get_transaction()
845
 
            history = transaction.map.find_revision_history()
846
 
            if history is not None:
847
 
                mutter("cache hit for revision-history in %s", self)
848
 
                return list(history)
849
 
            history = [l.rstrip('\r\n') for l in
850
 
                    self.controlfile('revision-history', 'r').readlines()]
851
 
            transaction.map.add_revision_history(history)
852
 
            # this call is disabled because revision_history is 
853
 
            # not really an object yet, and the transaction is for objects.
854
 
            # transaction.register_clean(history, precious=True)
 
799
        transaction = self.get_transaction()
 
800
        history = transaction.map.find_revision_history()
 
801
        if history is not None:
 
802
            mutter("cache hit for revision-history in %s", self)
855
803
            return list(history)
856
 
        finally:
857
 
            self.unlock()
 
804
        history = [l.rstrip('\r\n') for l in
 
805
                self.controlfile('revision-history', 'r').readlines()]
 
806
        transaction.map.add_revision_history(history)
 
807
        # this call is disabled because revision_history is 
 
808
        # not really an object yet, and the transaction is for objects.
 
809
        # transaction.register_clean(history, precious=True)
 
810
        return list(history)
858
811
 
859
812
    def revno(self):
860
813
        """Return current revision number for this branch.
864
817
        """
865
818
        return len(self.revision_history())
866
819
 
867
 
 
868
820
    def last_revision(self):
869
821
        """Return last patch hash, or None if no history.
870
822
        """
874
826
        else:
875
827
            return None
876
828
 
877
 
 
878
829
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
879
830
        """Return a list of new revisions that would perfectly fit.
880
831
        
1009
960
        """
1010
961
        return self.revision_tree(self.last_revision())
1011
962
 
1012
 
 
 
963
    @needs_write_lock
1013
964
    def rename_one(self, from_rel, to_rel):
1014
965
        """Rename one file.
1015
966
 
1016
967
        This can change the directory or the filename or both.
1017
968
        """
1018
 
        self.lock_write()
 
969
        tree = self.working_tree()
 
970
        inv = tree.inventory
 
971
        if not tree.has_filename(from_rel):
 
972
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
973
        if tree.has_filename(to_rel):
 
974
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
975
 
 
976
        file_id = inv.path2id(from_rel)
 
977
        if file_id == None:
 
978
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
979
 
 
980
        if inv.path2id(to_rel):
 
981
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
982
 
 
983
        to_dir, to_tail = os.path.split(to_rel)
 
984
        to_dir_id = inv.path2id(to_dir)
 
985
        if to_dir_id == None and to_dir != '':
 
986
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
987
 
 
988
        mutter("rename_one:")
 
989
        mutter("  file_id    {%s}" % file_id)
 
990
        mutter("  from_rel   %r" % from_rel)
 
991
        mutter("  to_rel     %r" % to_rel)
 
992
        mutter("  to_dir     %r" % to_dir)
 
993
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
994
 
 
995
        inv.rename(file_id, to_dir_id, to_tail)
 
996
 
 
997
        from_abs = self.abspath(from_rel)
 
998
        to_abs = self.abspath(to_rel)
1019
999
        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
 
 
 
1000
            rename(from_abs, to_abs)
 
1001
        except OSError, e:
 
1002
            raise BzrError("failed to rename %r to %r: %s"
 
1003
                    % (from_abs, to_abs, e[1]),
 
1004
                    ["rename rolled back"])
 
1005
 
 
1006
        self._write_inventory(inv)
 
1007
 
 
1008
    @needs_write_lock
1062
1009
    def move(self, from_paths, to_name):
1063
1010
        """Rename files.
1064
1011
 
1074
1021
        entry that is moved.
1075
1022
        """
1076
1023
        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
 
 
 
1024
        ## TODO: Option to move IDs only
 
1025
        assert not isinstance(from_paths, basestring)
 
1026
        tree = self.working_tree()
 
1027
        inv = tree.inventory
 
1028
        to_abs = self.abspath(to_name)
 
1029
        if not isdir(to_abs):
 
1030
            raise BzrError("destination %r is not a directory" % to_abs)
 
1031
        if not tree.has_filename(to_name):
 
1032
            raise BzrError("destination %r not in working directory" % to_abs)
 
1033
        to_dir_id = inv.path2id(to_name)
 
1034
        if to_dir_id == None and to_name != '':
 
1035
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
1036
        to_dir_ie = inv[to_dir_id]
 
1037
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
1038
            raise BzrError("destination %r is not a directory" % to_abs)
 
1039
 
 
1040
        to_idpath = inv.get_idpath(to_dir_id)
 
1041
 
 
1042
        for f in from_paths:
 
1043
            if not tree.has_filename(f):
 
1044
                raise BzrError("%r does not exist in working tree" % f)
 
1045
            f_id = inv.path2id(f)
 
1046
            if f_id == None:
 
1047
                raise BzrError("%r is not versioned" % f)
 
1048
            name_tail = splitpath(f)[-1]
 
1049
            dest_path = appendpath(to_name, name_tail)
 
1050
            if tree.has_filename(dest_path):
 
1051
                raise BzrError("destination %r already exists" % dest_path)
 
1052
            if f_id in to_idpath:
 
1053
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
1054
 
 
1055
        # OK, so there's a race here, it's possible that someone will
 
1056
        # create a file in this interval and then the rename might be
 
1057
        # left half-done.  But we should have caught most problems.
 
1058
 
 
1059
        for f in from_paths:
 
1060
            name_tail = splitpath(f)[-1]
 
1061
            dest_path = appendpath(to_name, name_tail)
 
1062
            result.append((f, dest_path))
 
1063
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
1064
            try:
 
1065
                rename(self.abspath(f), self.abspath(dest_path))
 
1066
            except OSError, e:
 
1067
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
 
1068
                        ["rename rolled back"])
 
1069
 
 
1070
        self._write_inventory(inv)
1129
1071
        return result
1130
1072
 
1131
1073
 
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
        """\
1258
1192
            raise InvalidRevisionNumber(revno)
1259
1193
        
1260
1194
    def sign_revision(self, revision_id, gpg_strategy):
1261
 
        self.lock_write()
1262
 
        try:
1263
 
            plaintext = Testament.from_revision(self, revision_id).as_short_text()
1264
 
            self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1265
 
                                    revision_id, "sig")
1266
 
        finally:
1267
 
            self.unlock()
 
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")
1268
1202
 
1269
1203
 
1270
1204
class ScratchBranch(_Branch):