~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-05-05 03:34:34 UTC
  • Revision ID: mbp@sourcefrog.net-20050505033434-5fff60df09cb61c6
- Per-branch locks in read and write modes.
  (Not on Windows yet.)

Show diffs side-by-side

added added

removed removed

Lines of Context:
76
76
    base
77
77
        Base directory of the branch.
78
78
    """
79
 
    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'):
80
82
        """Create new branch object at a particular location.
81
83
 
82
84
        base -- Base directory for the branch.
103
105
                        ['use "bzr init" to initialize a new working tree',
104
106
                         'current bzr can only operate from top-of-tree'])
105
107
        self._check_format()
 
108
        self.lock(lock_mode)
106
109
 
107
110
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
108
111
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
116
119
    __repr__ = __str__
117
120
 
118
121
 
 
122
 
 
123
    def lock(self, mode='w'):
 
124
        """Lock the on-disk branch, excluding other processes."""
 
125
        try:
 
126
            import fcntl
 
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
            lockfile = os.open(self.controlfilename('branch-lock'), om)
 
138
            fcntl.lockf(lockfile, lm)
 
139
            def unlock(self):
 
140
                fcntl.lockf(lockfile, fcntl.LOCK_UN)
 
141
                os.close(lockfile)
 
142
                self._lockmode = None
 
143
            self.unlock = unlock
 
144
            self._lockmode = mode
 
145
        except ImportError:
 
146
            warning("please write a locking method for platform %r" % sys.platform)
 
147
            def unlock(self):
 
148
                self._lockmode = None
 
149
            self.unlock = unlock
 
150
            self._lockmode = mode
 
151
 
 
152
 
 
153
    def _need_readlock(self):
 
154
        if self._lockmode not in ['r', 'w']:
 
155
            raise BzrError('need read lock on branch, only have %r' % self._lockmode)
 
156
 
 
157
    def _need_writelock(self):
 
158
        if self._lockmode not in ['w']:
 
159
            raise BzrError('need write lock on branch, only have %r' % self._lockmode)
 
160
 
 
161
 
119
162
    def abspath(self, name):
120
163
        """Return absolute filename for something in the branch"""
121
164
        return os.path.join(self.base, name)
174
217
        for d in ('text-store', 'inventory-store', 'revision-store'):
175
218
            os.mkdir(self.controlfilename(d))
176
219
        for f in ('revision-history', 'merged-patches',
177
 
                  'pending-merged-patches', 'branch-name'):
 
220
                  'pending-merged-patches', 'branch-name',
 
221
                  'branch-lock'):
178
222
            self.controlfile(f, 'w').write('')
179
223
        mutter('created control directory in ' + self.base)
180
224
        Inventory().write_xml(self.controlfile('inventory','w'))
201
245
 
202
246
    def read_working_inventory(self):
203
247
        """Read the working inventory."""
 
248
        self._need_readlock()
204
249
        before = time.time()
205
250
        # ElementTree does its own conversion from UTF-8, so open in
206
251
        # binary.
216
261
        That is to say, the inventory describing changes underway, that
217
262
        will be committed to the next revision.
218
263
        """
 
264
        self._need_writelock()
219
265
        ## TODO: factor out to atomicfile?  is rename safe on windows?
220
266
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
221
267
        tmpfname = self.controlfilename('inventory.tmp')
275
321
        Traceback (most recent call last):
276
322
        BzrError: ('cannot add: not a regular file or directory: nothere', [])
277
323
        """
 
324
        self._need_writelock()
278
325
 
279
326
        # TODO: Re-adding a file that is removed in the working copy
280
327
        # should probably put it back with the previous ID.
315
362
 
316
363
    def print_file(self, file, revno):
317
364
        """Print `file` to stdout."""
 
365
        self._need_readlock()
318
366
        tree = self.revision_tree(self.lookup_revision(revno))
319
367
        # use inventory as it was in that revision
320
368
        file_id = tree.inventory.path2id(file)
361
409
        """
362
410
        ## TODO: Normalize names
363
411
        ## TODO: Remove nested loops; better scalability
 
412
        self._need_writelock()
364
413
 
365
414
        if isinstance(files, types.StringTypes):
366
415
            files = [files]
427
476
        timestamp -- if not None, seconds-since-epoch for a
428
477
             postdated/predated commit.
429
478
        """
 
479
        self._need_writelock()
430
480
 
431
481
        ## TODO: Show branch names
432
482
 
596
646
 
597
647
    def get_revision(self, revision_id):
598
648
        """Return the Revision object for a named revision"""
 
649
        self._need_readlock()
599
650
        r = Revision.read_xml(self.revision_store[revision_id])
600
651
        assert r.revision_id == revision_id
601
652
        return r
607
658
        TODO: Perhaps for this and similar methods, take a revision
608
659
               parameter which can be either an integer revno or a
609
660
               string hash."""
 
661
        self._need_readlock()
610
662
        i = Inventory.read_xml(self.inventory_store[inventory_id])
611
663
        return i
612
664
 
613
665
 
614
666
    def get_revision_inventory(self, revision_id):
615
667
        """Return inventory of a past revision."""
 
668
        self._need_readlock()
616
669
        if revision_id == None:
617
670
            return Inventory()
618
671
        else:
625
678
        >>> ScratchBranch().revision_history()
626
679
        []
627
680
        """
 
681
        self._need_readlock()
628
682
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
629
683
 
630
684
 
674
728
 
675
729
        `revision_id` may be None for the null revision, in which case
676
730
        an `EmptyTree` is returned."""
677
 
 
 
731
        self._need_readlock()
678
732
        if revision_id == None:
679
733
            return EmptyTree()
680
734
        else:
714
768
        """Write out human-readable log of commits to this branch
715
769
 
716
770
        utc -- If true, show dates in universal time, not local time."""
 
771
        self._need_readlock()
717
772
        ## TODO: Option to choose either original, utc or local timezone
718
773
        revno = 1
719
774
        precursor = None
761
816
        """Rename one file.
762
817
 
763
818
        This can change the directory or the filename or both.
764
 
         """
 
819
        """
 
820
        self._need_writelock()
765
821
        tree = self.working_tree()
766
822
        inv = tree.inventory
767
823
        if not tree.has_filename(from_rel):
816
872
        Note that to_name is only the last component of the new name;
817
873
        this doesn't change the directory.
818
874
        """
 
875
        self._need_writelock()
819
876
        ## TODO: Option to move IDs only
820
877
        assert not isinstance(from_paths, basestring)
821
878
        tree = self.working_tree()
885
942
        
886
943
        TODO: Get state for single files.
887
944
        """
 
945
        self._need_readlock()
888
946
 
889
947
        # We have to build everything into a list first so that it can
890
948
        # sorted by name, incorporating all the different sources.