~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2005-10-19 09:34:39 UTC
  • mfrom: (1185.16.78)
  • mto: (1185.16.102)
  • mto: This revision was merged to the branch mainline in revision 1488.
  • Revision ID: jelmer@samba.org-20051019093439-e1d8e3508d1ba46b
MergeĀ fromĀ Martin

Show diffs side-by-side

added added

removed removed

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