~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 11:56:54 UTC
  • mfrom: (1185.16.59)
  • Revision ID: robertc@robertcollins.net-20051017115654-662239e1587524a8
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, abspath)
 
31
                            file_kind)
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, NotVersionedError)
 
36
                           UnlistableBranch, NoSuchFile)
37
37
from bzrlib.textui import show_status
38
 
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
39
 
                             NULL_REVISION)
 
38
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
40
39
 
41
40
from bzrlib.delta import compare_trees
42
41
from bzrlib.tree import EmptyTree, RevisionTree
68
67
    raise NotImplementedError('find_branch() is not supported anymore, '
69
68
                              'please use one of the new branch constructors')
70
69
 
71
 
 
72
 
def needs_read_lock(unbound):
73
 
    """Decorate unbound to take out and release a read lock."""
74
 
    def decorated(self, *args, **kwargs):
75
 
        self.lock_read()
76
 
        try:
77
 
            return unbound(self, *args, **kwargs)
78
 
        finally:
79
 
            self.unlock()
80
 
    return decorated
81
 
 
82
 
 
83
 
def needs_write_lock(unbound):
84
 
    """Decorate unbound to take out and release a write lock."""
85
 
    def decorated(self, *args, **kwargs):
86
 
        self.lock_write()
87
 
        try:
88
 
            return unbound(self, *args, **kwargs)
89
 
        finally:
90
 
            self.unlock()
91
 
    return decorated
92
 
 
93
70
######################################################################
94
71
# branch objects
95
72
 
126
103
 
127
104
        Basically we keep looking up until we find the control directory or
128
105
        run into the root.  If there isn't one, raises NotBranchError.
129
 
        If there is one, it is returned, along with the unused portion of url.
130
106
        """
131
107
        t = get_transport(url)
132
108
        while True:
133
109
            try:
134
 
                return _Branch(t), t.relpath(url)
 
110
                return _Branch(t)
135
111
            except NotBranchError:
136
112
                pass
137
113
            new_t = t.clone('..')
138
114
            if new_t.base == t.base:
139
115
                # reached the root, whatever that may be
140
 
                raise NotBranchError(path=url)
 
116
                raise NotBranchError('%s is not in a branch' % url)
141
117
            t = new_t
142
118
 
143
119
    @staticmethod
209
185
        """Create new branch object at a particular location.
210
186
 
211
187
        transport -- A Transport object, defining how to access files.
 
188
                (If a string, transport.transport() will be used to
 
189
                create a Transport object)
212
190
        
213
191
        init -- If True, create new control files in a previously
214
192
             unversioned directory.  If False, the branch must already
258
236
            self.text_store = get_store('text-store')
259
237
            self.revision_store = get_store('revision-store')
260
238
        elif self._branch_format == 5:
261
 
            self.control_weaves = get_weave('')
 
239
            self.control_weaves = get_weave([])
262
240
            self.weave_store = get_weave('weaves')
263
241
            self.revision_store = get_store('revision-store', compressed=False)
264
242
        elif self._branch_format == 6:
265
 
            self.control_weaves = get_weave('')
 
243
            self.control_weaves = get_weave([])
266
244
            self.weave_store = get_weave('weaves', prefixed=True)
267
245
            self.revision_store = get_store('revision-store', compressed=False,
268
246
                                            prefixed=True)
317
295
        """Return the current active transaction.
318
296
 
319
297
        If no transaction is active, this returns a passthrough object
320
 
        for which all data is immediately flushed and no caching happens.
 
298
        for which all data is immedaitely flushed and no caching happens.
321
299
        """
322
300
        if self._transaction is None:
323
301
            return transactions.PassThroughTransaction()
384
362
        return self._transport.abspath(name)
385
363
 
386
364
    def _rel_controlfilename(self, file_or_path):
387
 
        if not isinstance(file_or_path, basestring):
388
 
            file_or_path = '/'.join(file_or_path)
389
 
        if file_or_path == '':
390
 
            return bzrlib.BZRDIR
391
 
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
 
365
        if isinstance(file_or_path, basestring):
 
366
            file_or_path = [file_or_path]
 
367
        return [bzrlib.BZRDIR] + file_or_path
392
368
 
393
369
    def controlfilename(self, file_or_path):
394
370
        """Return location relative to branch."""
395
371
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
396
372
 
 
373
 
397
374
    def controlfile(self, file_or_path, mode='r'):
398
375
        """Open a control file for this branch.
399
376
 
496
473
        try:
497
474
            fmt = self.controlfile('branch-format', 'r').read()
498
475
        except NoSuchFile:
499
 
            raise NotBranchError(path=self.base)
 
476
            raise NotBranchError(self.base)
500
477
        mutter("got branch format %r", fmt)
501
478
        if fmt == BZR_BRANCH_FORMAT_6:
502
479
            self._branch_format = 6
530
507
                entry.parent_id = inv.root.file_id
531
508
        self._write_inventory(inv)
532
509
 
533
 
    @needs_read_lock
534
510
    def read_working_inventory(self):
535
511
        """Read the working inventory."""
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)
 
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
            
540
521
 
541
 
    @needs_write_lock
542
522
    def _write_inventory(self, inv):
543
523
        """Update the working inventory.
544
524
 
546
526
        will be committed to the next revision.
547
527
        """
548
528
        from cStringIO import StringIO
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)
 
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()
554
538
        
555
539
        mutter('wrote working inventory')
556
540
            
557
541
    inventory = property(read_working_inventory, _write_inventory, None,
558
542
                         """Inventory for the working copy.""")
559
543
 
560
 
    @needs_write_lock
561
544
    def add(self, files, ids=None):
562
545
        """Make files versioned.
563
546
 
593
576
        else:
594
577
            assert(len(ids) == len(files))
595
578
 
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
 
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
 
627
614
    def print_file(self, file, revno):
628
615
        """Print `file` to stdout."""
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)
 
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()
635
671
 
636
672
    # FIXME: this doesn't need to be a branch method
637
673
    def set_inventory(self, new_inventory_list):
658
694
        These are files in the working directory that are not versioned or
659
695
        control files or ignored.
660
696
        
661
 
        >>> from bzrlib.workingtree import WorkingTree
662
697
        >>> b = ScratchBranch(files=['foo', 'foo~'])
663
 
        >>> map(str, b.unknowns())
 
698
        >>> list(b.unknowns())
664
699
        ['foo']
665
700
        >>> b.add('foo')
666
701
        >>> list(b.unknowns())
667
702
        []
668
 
        >>> WorkingTree(b.base, b).remove('foo')
 
703
        >>> b.remove('foo')
669
704
        >>> list(b.unknowns())
670
 
        [u'foo']
 
705
        ['foo']
671
706
        """
672
707
        return self.working_tree().unknowns()
673
708
 
674
 
    @needs_write_lock
 
709
 
675
710
    def append_revision(self, *revision_ids):
676
711
        for revision_id in revision_ids:
677
712
            mutter("add {%s} to revision-history" % revision_id)
678
 
        rev_history = self.revision_history()
679
 
        rev_history.extend(revision_ids)
680
 
        self.set_revision_history(rev_history)
681
 
 
682
 
    @needs_write_lock
683
 
    def set_revision_history(self, rev_history):
684
 
        self.put_controlfile('revision-history', '\n'.join(rev_history))
 
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()
685
720
 
686
721
    def has_revision(self, revision_id):
687
722
        """True if this branch has a copy of the revision.
691
726
        return (revision_id is None
692
727
                or self.revision_store.has_id(revision_id))
693
728
 
694
 
    @needs_read_lock
695
729
    def get_revision_xml_file(self, revision_id):
696
730
        """Return XML file object for revision object."""
697
731
        if not revision_id or not isinstance(revision_id, basestring):
698
 
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
732
            raise InvalidRevisionId(revision_id)
 
733
 
 
734
        self.lock_read()
699
735
        try:
700
 
            return self.revision_store.get(revision_id)
701
 
        except (IndexError, KeyError):
702
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
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()
703
742
 
704
743
    #deprecated
705
744
    get_revision_xml = get_revision_xml_file
798
837
        else:
799
838
            return self.get_inventory(revision_id)
800
839
 
801
 
    @needs_read_lock
802
840
    def revision_history(self):
803
841
        """Return sequence of revision hashes on to this branch."""
804
 
        transaction = self.get_transaction()
805
 
        history = transaction.map.find_revision_history()
806
 
        if history is not None:
807
 
            mutter("cache hit for revision-history in %s", self)
 
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)
808
855
            return list(history)
809
 
        history = [l.rstrip('\r\n') for l in
810
 
                self.controlfile('revision-history', 'r').readlines()]
811
 
        transaction.map.add_revision_history(history)
812
 
        # this call is disabled because revision_history is 
813
 
        # not really an object yet, and the transaction is for objects.
814
 
        # transaction.register_clean(history, precious=True)
815
 
        return list(history)
 
856
        finally:
 
857
            self.unlock()
816
858
 
817
859
    def revno(self):
818
860
        """Return current revision number for this branch.
822
864
        """
823
865
        return len(self.revision_history())
824
866
 
 
867
 
825
868
    def last_revision(self):
826
869
        """Return last patch hash, or None if no history.
827
870
        """
831
874
        else:
832
875
            return None
833
876
 
 
877
 
834
878
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
835
879
        """Return a list of new revisions that would perfectly fit.
836
880
        
878
922
 
879
923
    def update_revisions(self, other, stop_revision=None):
880
924
        """Pull in new perfect-fit revisions."""
 
925
        # FIXME: If the branches have diverged, but the latest
 
926
        # revision in this branch is completely merged into the other,
 
927
        # then we should still be able to pull.
881
928
        from bzrlib.fetch import greedy_fetch
882
929
        if stop_revision is None:
883
930
            stop_revision = other.last_revision()
938
985
        an `EmptyTree` is returned."""
939
986
        # TODO: refactor this to use an existing revision object
940
987
        # so we don't need to read it in twice.
941
 
        if revision_id == None or revision_id == NULL_REVISION:
 
988
        if revision_id == None:
942
989
            return EmptyTree()
943
990
        else:
944
991
            inv = self.get_revision_inventory(revision_id)
947
994
    def working_tree(self):
948
995
        """Return a `Tree` for the working copy."""
949
996
        from bzrlib.workingtree import WorkingTree
950
 
        # TODO: In the future, perhaps WorkingTree should utilize Transport
 
997
        # TODO: In the future, WorkingTree should utilize Transport
951
998
        # RobertCollins 20051003 - I don't think it should - working trees are
952
999
        # much more complex to keep consistent than our careful .bzr subset.
953
1000
        # instead, we should say that working trees are local only, and optimise
954
1001
        # for that.
955
1002
        return WorkingTree(self.base, branch=self)
956
1003
 
957
 
    @needs_write_lock
958
 
    def pull(self, source, overwrite=False):
959
 
        source.lock_read()
960
 
        try:
961
 
            try:
962
 
                self.update_revisions(source)
963
 
            except DivergedBranches:
964
 
                if not overwrite:
965
 
                    raise
966
 
                self.set_revision_history(source.revision_history())
967
 
        finally:
968
 
            source.unlock()
969
1004
 
970
1005
    def basis_tree(self):
971
1006
        """Return `Tree` object for last revision.
974
1009
        """
975
1010
        return self.revision_tree(self.last_revision())
976
1011
 
977
 
    @needs_write_lock
 
1012
 
978
1013
    def rename_one(self, from_rel, to_rel):
979
1014
        """Rename one file.
980
1015
 
981
1016
        This can change the directory or the filename or both.
982
1017
        """
983
 
        tree = self.working_tree()
984
 
        inv = tree.inventory
985
 
        if not tree.has_filename(from_rel):
986
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
987
 
        if tree.has_filename(to_rel):
988
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
989
 
 
990
 
        file_id = inv.path2id(from_rel)
991
 
        if file_id == None:
992
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
993
 
 
994
 
        if inv.path2id(to_rel):
995
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
996
 
 
997
 
        to_dir, to_tail = os.path.split(to_rel)
998
 
        to_dir_id = inv.path2id(to_dir)
999
 
        if to_dir_id == None and to_dir != '':
1000
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
1001
 
 
1002
 
        mutter("rename_one:")
1003
 
        mutter("  file_id    {%s}" % file_id)
1004
 
        mutter("  from_rel   %r" % from_rel)
1005
 
        mutter("  to_rel     %r" % to_rel)
1006
 
        mutter("  to_dir     %r" % to_dir)
1007
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
1008
 
 
1009
 
        inv.rename(file_id, to_dir_id, to_tail)
1010
 
 
1011
 
        from_abs = self.abspath(from_rel)
1012
 
        to_abs = self.abspath(to_rel)
 
1018
        self.lock_write()
1013
1019
        try:
1014
 
            rename(from_abs, to_abs)
1015
 
        except OSError, e:
1016
 
            raise BzrError("failed to rename %r to %r: %s"
1017
 
                    % (from_abs, to_abs, e[1]),
1018
 
                    ["rename rolled back"])
1019
 
 
1020
 
        self._write_inventory(inv)
1021
 
 
1022
 
    @needs_write_lock
 
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
 
1023
1062
    def move(self, from_paths, to_name):
1024
1063
        """Rename files.
1025
1064
 
1035
1074
        entry that is moved.
1036
1075
        """
1037
1076
        result = []
1038
 
        ## TODO: Option to move IDs only
1039
 
        assert not isinstance(from_paths, basestring)
1040
 
        tree = self.working_tree()
1041
 
        inv = tree.inventory
1042
 
        to_abs = self.abspath(to_name)
1043
 
        if not isdir(to_abs):
1044
 
            raise BzrError("destination %r is not a directory" % to_abs)
1045
 
        if not tree.has_filename(to_name):
1046
 
            raise BzrError("destination %r not in working directory" % to_abs)
1047
 
        to_dir_id = inv.path2id(to_name)
1048
 
        if to_dir_id == None and to_name != '':
1049
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
1050
 
        to_dir_ie = inv[to_dir_id]
1051
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
1052
 
            raise BzrError("destination %r is not a directory" % to_abs)
1053
 
 
1054
 
        to_idpath = inv.get_idpath(to_dir_id)
1055
 
 
1056
 
        for f in from_paths:
1057
 
            if not tree.has_filename(f):
1058
 
                raise BzrError("%r does not exist in working tree" % f)
1059
 
            f_id = inv.path2id(f)
1060
 
            if f_id == None:
1061
 
                raise BzrError("%r is not versioned" % f)
1062
 
            name_tail = splitpath(f)[-1]
1063
 
            dest_path = appendpath(to_name, name_tail)
1064
 
            if tree.has_filename(dest_path):
1065
 
                raise BzrError("destination %r already exists" % dest_path)
1066
 
            if f_id in to_idpath:
1067
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
1068
 
 
1069
 
        # OK, so there's a race here, it's possible that someone will
1070
 
        # create a file in this interval and then the rename might be
1071
 
        # left half-done.  But we should have caught most problems.
1072
 
 
1073
 
        for f in from_paths:
1074
 
            name_tail = splitpath(f)[-1]
1075
 
            dest_path = appendpath(to_name, name_tail)
1076
 
            result.append((f, dest_path))
1077
 
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
1078
 
            try:
1079
 
                rename(self.abspath(f), self.abspath(dest_path))
1080
 
            except OSError, e:
1081
 
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1082
 
                        ["rename rolled back"])
1083
 
 
1084
 
        self._write_inventory(inv)
 
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
 
1085
1129
        return result
1086
1130
 
1087
1131
 
1092
1136
            If true (default) backups are made of files before
1093
1137
            they're renamed.
1094
1138
        """
 
1139
        from bzrlib.errors import NotVersionedError, BzrError
1095
1140
        from bzrlib.atomicfile import AtomicFile
1096
1141
        from bzrlib.osutils import backup_file
1097
1142
        
1104
1149
        for fn in filenames:
1105
1150
            file_id = inv.path2id(fn)
1106
1151
            if not file_id:
1107
 
                raise NotVersionedError(path=fn)
 
1152
                raise NotVersionedError("not a versioned file", fn)
1108
1153
            if not old_inv.has_id(file_id):
1109
1154
                raise BzrError("file not present in old tree", fn, file_id)
1110
1155
            nids.append((fn, file_id))
1156
1201
        if updated:
1157
1202
            self.set_pending_merges(p)
1158
1203
 
1159
 
    @needs_write_lock
1160
1204
    def set_pending_merges(self, rev_list):
1161
 
        self.put_controlfile('pending-merges', '\n'.join(rev_list))
 
1205
        self.lock_write()
 
1206
        try:
 
1207
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
 
1208
        finally:
 
1209
            self.unlock()
 
1210
 
1162
1211
 
1163
1212
    def get_parent(self):
1164
1213
        """Return the parent location of the branch.
1177
1226
                    raise
1178
1227
        return None
1179
1228
 
1180
 
    def get_push_location(self):
1181
 
        """Return the None or the location to push this branch to."""
1182
 
        config = bzrlib.config.BranchConfig(self)
1183
 
        push_loc = config.get_user_option('push_location')
1184
 
        return push_loc
1185
 
 
1186
 
    def set_push_location(self, location):
1187
 
        """Set a new push location for this branch."""
1188
 
        config = bzrlib.config.LocationConfig(self.base)
1189
 
        config.set_user_option('push_location', location)
1190
 
 
1191
 
    @needs_write_lock
 
1229
 
1192
1230
    def set_parent(self, url):
1193
1231
        # TODO: Maybe delete old location files?
1194
1232
        from bzrlib.atomicfile import AtomicFile
1195
 
        f = AtomicFile(self.controlfilename('parent'))
 
1233
        self.lock_write()
1196
1234
        try:
1197
 
            f.write(url + '\n')
1198
 
            f.commit()
 
1235
            f = AtomicFile(self.controlfilename('parent'))
 
1236
            try:
 
1237
                f.write(url + '\n')
 
1238
                f.commit()
 
1239
            finally:
 
1240
                f.close()
1199
1241
        finally:
1200
 
            f.close()
 
1242
            self.unlock()
1201
1243
 
1202
1244
    def check_revno(self, revno):
1203
1245
        """\
1216
1258
            raise InvalidRevisionNumber(revno)
1217
1259
        
1218
1260
    def sign_revision(self, revision_id, gpg_strategy):
1219
 
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1220
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1221
 
 
1222
 
    @needs_write_lock
1223
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1224
 
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1225
 
                                revision_id, "sig")
 
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()
1226
1268
 
1227
1269
 
1228
1270
class ScratchBranch(_Branch):