~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-05-09 03:03:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050509030355-ad6ab558d1362959
- Don't give an error if the trace file can't be opened

Show diffs side-by-side

added added

removed removed

Lines of Context:
73
73
class Branch:
74
74
    """Branch holding a history of revisions.
75
75
 
76
 
    TODO: Perhaps use different stores for different classes of object,
77
 
           so that we can keep track of how much space each one uses,
78
 
           or garbage-collect them.
79
 
 
80
 
    TODO: Add a RemoteBranch subclass.  For the basic case of read-only
81
 
           HTTP access this should be very easy by, 
82
 
           just redirecting controlfile access into HTTP requests.
83
 
           We would need a RemoteStore working similarly.
84
 
 
85
 
    TODO: Keep the on-disk branch locked while the object exists.
86
 
 
87
 
    TODO: mkdir() method.
 
76
    base
 
77
        Base directory of the branch.
88
78
    """
89
 
    def __init__(self, base, init=False, find_root=True):
 
79
    _lockmode = None
 
80
    
 
81
    def __init__(self, base, init=False, find_root=True, lock_mode='w'):
90
82
        """Create new branch object at a particular location.
91
83
 
92
84
        base -- Base directory for the branch.
113
105
                        ['use "bzr init" to initialize a new working tree',
114
106
                         'current bzr can only operate from top-of-tree'])
115
107
        self._check_format()
 
108
        self.lock(lock_mode)
116
109
 
117
110
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
118
111
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
126
119
    __repr__ = __str__
127
120
 
128
121
 
 
122
 
 
123
    def lock(self, mode='w'):
 
124
        """Lock the on-disk branch, excluding other processes."""
 
125
        try:
 
126
            import fcntl, errno
 
127
 
 
128
            if mode == 'w':
 
129
                lm = fcntl.LOCK_EX
 
130
                om = os.O_WRONLY | os.O_CREAT
 
131
            elif mode == 'r':
 
132
                lm = fcntl.LOCK_SH
 
133
                om = os.O_RDONLY
 
134
            else:
 
135
                raise BzrError("invalid locking mode %r" % mode)
 
136
 
 
137
            try:
 
138
                lockfile = os.open(self.controlfilename('branch-lock'), om)
 
139
            except OSError, e:
 
140
                if e.errno == errno.ENOENT:
 
141
                    # might not exist on branches from <0.0.4
 
142
                    self.controlfile('branch-lock', 'w').close()
 
143
                    lockfile = os.open(self.controlfilename('branch-lock'), om)
 
144
                else:
 
145
                    raise e
 
146
            
 
147
            fcntl.lockf(lockfile, lm)
 
148
            def unlock():
 
149
                fcntl.lockf(lockfile, fcntl.LOCK_UN)
 
150
                os.close(lockfile)
 
151
                self._lockmode = None
 
152
            self.unlock = unlock
 
153
            self._lockmode = mode
 
154
        except ImportError:
 
155
            warning("please write a locking method for platform %r" % sys.platform)
 
156
            def unlock():
 
157
                self._lockmode = None
 
158
            self.unlock = unlock
 
159
            self._lockmode = mode
 
160
 
 
161
 
 
162
    def _need_readlock(self):
 
163
        if self._lockmode not in ['r', 'w']:
 
164
            raise BzrError('need read lock on branch, only have %r' % self._lockmode)
 
165
 
 
166
    def _need_writelock(self):
 
167
        if self._lockmode not in ['w']:
 
168
            raise BzrError('need write lock on branch, only have %r' % self._lockmode)
 
169
 
 
170
 
129
171
    def abspath(self, name):
130
172
        """Return absolute filename for something in the branch"""
131
173
        return os.path.join(self.base, name)
184
226
        for d in ('text-store', 'inventory-store', 'revision-store'):
185
227
            os.mkdir(self.controlfilename(d))
186
228
        for f in ('revision-history', 'merged-patches',
187
 
                  'pending-merged-patches', 'branch-name'):
 
229
                  'pending-merged-patches', 'branch-name',
 
230
                  'branch-lock'):
188
231
            self.controlfile(f, 'w').write('')
189
232
        mutter('created control directory in ' + self.base)
190
233
        Inventory().write_xml(self.controlfile('inventory','w'))
211
254
 
212
255
    def read_working_inventory(self):
213
256
        """Read the working inventory."""
 
257
        self._need_readlock()
214
258
        before = time.time()
215
259
        # ElementTree does its own conversion from UTF-8, so open in
216
260
        # binary.
226
270
        That is to say, the inventory describing changes underway, that
227
271
        will be committed to the next revision.
228
272
        """
 
273
        self._need_writelock()
229
274
        ## TODO: factor out to atomicfile?  is rename safe on windows?
230
275
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
231
276
        tmpfname = self.controlfilename('inventory.tmp')
285
330
        Traceback (most recent call last):
286
331
        BzrError: ('cannot add: not a regular file or directory: nothere', [])
287
332
        """
 
333
        self._need_writelock()
288
334
 
289
335
        # TODO: Re-adding a file that is removed in the working copy
290
336
        # should probably put it back with the previous ID.
325
371
 
326
372
    def print_file(self, file, revno):
327
373
        """Print `file` to stdout."""
 
374
        self._need_readlock()
328
375
        tree = self.revision_tree(self.lookup_revision(revno))
329
376
        # use inventory as it was in that revision
330
377
        file_id = tree.inventory.path2id(file)
371
418
        """
372
419
        ## TODO: Normalize names
373
420
        ## TODO: Remove nested loops; better scalability
 
421
        self._need_writelock()
374
422
 
375
423
        if isinstance(files, types.StringTypes):
376
424
            files = [files]
437
485
        timestamp -- if not None, seconds-since-epoch for a
438
486
             postdated/predated commit.
439
487
        """
 
488
        self._need_writelock()
440
489
 
441
490
        ## TODO: Show branch names
442
491
 
606
655
 
607
656
    def get_revision(self, revision_id):
608
657
        """Return the Revision object for a named revision"""
 
658
        self._need_readlock()
609
659
        r = Revision.read_xml(self.revision_store[revision_id])
610
660
        assert r.revision_id == revision_id
611
661
        return r
617
667
        TODO: Perhaps for this and similar methods, take a revision
618
668
               parameter which can be either an integer revno or a
619
669
               string hash."""
 
670
        self._need_readlock()
620
671
        i = Inventory.read_xml(self.inventory_store[inventory_id])
621
672
        return i
622
673
 
623
674
 
624
675
    def get_revision_inventory(self, revision_id):
625
676
        """Return inventory of a past revision."""
 
677
        self._need_readlock()
626
678
        if revision_id == None:
627
679
            return Inventory()
628
680
        else:
635
687
        >>> ScratchBranch().revision_history()
636
688
        []
637
689
        """
 
690
        self._need_readlock()
638
691
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
639
692
 
640
693
 
 
694
    def enum_history(self, direction):
 
695
        """Return (revno, revision_id) for history of branch.
 
696
 
 
697
        direction
 
698
            'forward' is from earliest to latest
 
699
            'reverse' is from latest to earliest
 
700
        """
 
701
        rh = self.revision_history()
 
702
        if direction == 'forward':
 
703
            i = 1
 
704
            for rid in rh:
 
705
                yield i, rid
 
706
                i += 1
 
707
        elif direction == 'reverse':
 
708
            i = len(rh)
 
709
            while i > 0:
 
710
                yield i, rh[i-1]
 
711
                i -= 1
 
712
        else:
 
713
            raise BzrError('invalid history direction %r' % direction)
 
714
 
 
715
 
641
716
    def revno(self):
642
717
        """Return current revision number for this branch.
643
718
 
684
759
 
685
760
        `revision_id` may be None for the null revision, in which case
686
761
        an `EmptyTree` is returned."""
687
 
 
 
762
        self._need_readlock()
688
763
        if revision_id == None:
689
764
            return EmptyTree()
690
765
        else:
720
795
 
721
796
 
722
797
 
723
 
    def write_log(self, show_timezone='original', verbose=False):
724
 
        """Write out human-readable log of commits to this branch
725
 
 
726
 
        utc -- If true, show dates in universal time, not local time."""
727
 
        ## TODO: Option to choose either original, utc or local timezone
728
 
        revno = 1
729
 
        precursor = None
730
 
        for p in self.revision_history():
731
 
            print '-' * 40
732
 
            print 'revno:', revno
733
 
            ## TODO: Show hash if --id is given.
734
 
            ##print 'revision-hash:', p
735
 
            rev = self.get_revision(p)
736
 
            print 'committer:', rev.committer
737
 
            print 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
738
 
                                                 show_timezone))
739
 
 
740
 
            ## opportunistic consistency check, same as check_patch_chaining
741
 
            if rev.precursor != precursor:
742
 
                bailout("mismatched precursor!")
743
 
 
744
 
            print 'message:'
745
 
            if not rev.message:
746
 
                print '  (no message)'
747
 
            else:
748
 
                for l in rev.message.split('\n'):
749
 
                    print '  ' + l
750
 
 
751
 
            if verbose == True and precursor != None:
752
 
                print 'changed files:'
753
 
                tree = self.revision_tree(p)
754
 
                prevtree = self.revision_tree(precursor)
755
 
                
756
 
                for file_state, fid, old_name, new_name, kind in \
757
 
                                        diff_trees(prevtree, tree, ):
758
 
                    if file_state == 'A' or file_state == 'M':
759
 
                        show_status(file_state, kind, new_name)
760
 
                    elif file_state == 'D':
761
 
                        show_status(file_state, kind, old_name)
762
 
                    elif file_state == 'R':
763
 
                        show_status(file_state, kind,
764
 
                            old_name + ' => ' + new_name)
765
 
                
766
 
            revno += 1
767
 
            precursor = p
768
 
 
769
 
 
770
798
    def rename_one(self, from_rel, to_rel):
771
799
        """Rename one file.
772
800
 
773
801
        This can change the directory or the filename or both.
774
 
         """
 
802
        """
 
803
        self._need_writelock()
775
804
        tree = self.working_tree()
776
805
        inv = tree.inventory
777
806
        if not tree.has_filename(from_rel):
826
855
        Note that to_name is only the last component of the new name;
827
856
        this doesn't change the directory.
828
857
        """
 
858
        self._need_writelock()
829
859
        ## TODO: Option to move IDs only
830
860
        assert not isinstance(from_paths, basestring)
831
861
        tree = self.working_tree()
895
925
        
896
926
        TODO: Get state for single files.
897
927
        """
 
928
        self._need_readlock()
898
929
 
899
930
        # We have to build everything into a list first so that it can
900
931
        # sorted by name, incorporating all the different sources.
936
967
    >>> isdir(b.base)
937
968
    True
938
969
    >>> bd = b.base
939
 
    >>> del b
 
970
    >>> b.destroy()
940
971
    >>> isdir(bd)
941
972
    False
942
973
    """
956
987
 
957
988
 
958
989
    def __del__(self):
 
990
        self.destroy()
 
991
 
 
992
    def destroy(self):
959
993
        """Destroy the test branch, removing the scratch directory."""
960
994
        try:
 
995
            mutter("delete ScratchBranch %s" % self.base)
961
996
            shutil.rmtree(self.base)
962
 
        except OSError:
 
997
        except OSError, e:
963
998
            # Work around for shutil.rmtree failing on Windows when
964
999
            # readonly files are encountered
 
1000
            mutter("hit exception in destroying ScratchBranch: %s" % e)
965
1001
            for root, dirs, files in os.walk(self.base, topdown=False):
966
1002
                for name in files:
967
1003
                    os.chmod(os.path.join(root, name), 0700)
968
1004
            shutil.rmtree(self.base)
 
1005
        self.base = None
969
1006
 
970
1007
    
971
1008