~bzr-pqm/bzr/bzr.dev

1553.5.28 by Martin Pool
[merge] from bzr.dev before integration
1
# Copyright (C) 2005, 2006 Canonical Ltd
1185.65.22 by Robert Collins
lockable_files was extracted from branch.py - give it a copyright statement
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1185.65.29 by Robert Collins
Implement final review suggestions.
17
from cStringIO import StringIO
18
import codecs
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
19
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
20
import bzrlib
1185.65.27 by Robert Collins
Tweak storage towards mergability.
21
from bzrlib.decorators import *
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
22
import bzrlib.errors as errors
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
23
from bzrlib.errors import LockError, ReadOnlyError
1185.65.27 by Robert Collins
Tweak storage towards mergability.
24
from bzrlib.osutils import file_iterator, safe_unicode
1185.65.29 by Robert Collins
Implement final review suggestions.
25
from bzrlib.symbol_versioning import *
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
26
from bzrlib.trace import mutter
27
import bzrlib.transactions as transactions
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
28
1553.5.41 by Martin Pool
Add new LockableFiles.LockDirStrategy; not used yet
29
# XXX: The tracking here of lock counts and whether the lock is held is
30
# somewhat redundant with what's done in LockDir; the main difference is that
31
# LockableFiles permits reentrancy.
1185.65.27 by Robert Collins
Tweak storage towards mergability.
32
1185.66.3 by Aaron Bentley
Renamed ControlFiles to LockableFiles
33
class LockableFiles(object):
1553.5.38 by Martin Pool
More explanation for LockableFiles
34
    """Object representing a set of related files locked within the same scope.
35
36
    These files are used by a WorkingTree, Repository or Branch, and should
37
    generally only be touched by that object.
38
39
    LockableFiles also provides some policy on top of Transport for encoding
40
    control files as utf-8.
41
1553.5.39 by Martin Pool
More lock docs
42
    LockableFiles manage a lock count and can be locked repeatedly by
43
    a single caller.  (The underlying lock implementation generally does not
44
    support this.)
45
1553.5.38 by Martin Pool
More explanation for LockableFiles
46
    Instances of this class are often called control_files.
47
    
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
48
    This object builds on top of a Transport, which is used to actually write
49
    the files to disk, and an OSLock or LockDir, which controls how access to
50
    the files is controlled.  The particular type of locking used is set when
51
    the object is constructed.  In older formats OSLocks are used everywhere.
52
    in newer formats a LockDir is used for Repositories and Branches, and 
53
    OSLocks for the local filesystem.
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
54
    """
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
55
1553.5.47 by Martin Pool
cleanup LockableFiles
56
    # _lock_mode: None, or 'r' or 'w'
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
57
1553.5.47 by Martin Pool
cleanup LockableFiles
58
    # _lock_count: If _lock_mode is true, a positive count of the number of
59
    # times the lock has been taken *by this process*.   
60
    
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
61
    # If set to False (by a plugin, etc) BzrBranch will not set the
62
    # mode on created files or directories
63
    _set_file_mode = True
64
    _set_dir_mode = True
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
65
1553.5.63 by Martin Pool
Lock type is now mandatory for LockableFiles constructor
66
    def __init__(self, transport, lock_name, lock_class):
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
67
        """Create a LockableFiles group
68
69
        :param transport: Transport pointing to the directory holding the 
70
            control files and lock.
71
        :param lock_name: Name of the lock guarding these files.
1553.5.47 by Martin Pool
cleanup LockableFiles
72
        :param lock_class: Class of lock strategy to use: typically
73
            either LockDir or TransportLock.
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
74
        """
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
75
        object.__init__(self)
76
        self._transport = transport
77
        self.lock_name = lock_name
78
        self._transaction = None
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
79
        self._find_modes()
1553.5.47 by Martin Pool
cleanup LockableFiles
80
        self._lock_mode = None
81
        self._lock_count = 0
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
82
        esc_name = self._escape(lock_name)
1553.5.59 by Martin Pool
Pass file/mode bits through to creation of lock files/dirs
83
        self._lock = lock_class(transport, esc_name, 
84
                                file_modebits=self._file_mode,
85
                                dir_modebits=self._dir_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
86
1553.5.60 by Martin Pool
New LockableFiles.create_lock() method
87
    def create_lock(self):
88
        """Create the lock.
89
90
        This should normally be called only when the LockableFiles directory
91
        is first created on disk.
92
        """
93
        self._lock.create()
94
1553.5.53 by Martin Pool
Add LockableFiles __repr__
95
    def __repr__(self):
96
        return '%s(%r)' % (self.__class__.__name__,
97
                           self._transport)
98
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
99
    def _escape(self, file_or_path):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
100
        if not isinstance(file_or_path, basestring):
101
            file_or_path = '/'.join(file_or_path)
102
        if file_or_path == '':
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
103
            return u''
1185.65.27 by Robert Collins
Tweak storage towards mergability.
104
        return bzrlib.transport.urlescape(safe_unicode(file_or_path))
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
105
106
    def _find_modes(self):
107
        """Determine the appropriate modes for files and directories."""
108
        try:
1534.4.28 by Robert Collins
first cut at merge from integration.
109
            st = self._transport.stat('.')
110
        except errors.TransportNotPossible:
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
111
            self._dir_mode = 0755
112
            self._file_mode = 0644
113
        else:
114
            self._dir_mode = st.st_mode & 07777
115
            # Remove the sticky and execute bits for files
116
            self._file_mode = self._dir_mode & ~07111
117
        if not self._set_dir_mode:
118
            self._dir_mode = None
119
        if not self._set_file_mode:
120
            self._file_mode = None
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
121
122
    def controlfilename(self, file_or_path):
123
        """Return location relative to branch."""
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
124
        return self._transport.abspath(self._escape(file_or_path))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
125
1534.1.15 by Robert Collins
* The internal storage of history, and logical branch identity have now
126
    @deprecated_method(zero_eight)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
127
    def controlfile(self, file_or_path, mode='r'):
128
        """Open a control file for this branch.
129
1185.65.27 by Robert Collins
Tweak storage towards mergability.
130
        There are two classes of file in a lockable directory: text
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
131
        and binary.  binary files are untranslated byte streams.  Text
132
        control files are stored with Unix newlines and in UTF-8, even
133
        if the platform or locale defaults are different.
134
1185.65.27 by Robert Collins
Tweak storage towards mergability.
135
        Such files are not openable in write mode : they are managed via
136
        put and put_utf8 which atomically replace old versions using
137
        atomicfile.
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
138
        """
139
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
140
        relpath = self._escape(file_or_path)
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
141
        # TODO: codecs.open() buffers linewise, so it was overloaded with
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
142
        # a much larger buffer, do we need to do the same for getreader/getwriter?
143
        if mode == 'rb': 
1185.65.29 by Robert Collins
Implement final review suggestions.
144
            return self.get(relpath)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
145
        elif mode == 'wb':
1185.65.12 by Robert Collins
Remove the only-used-once put_controlfiles, and change put_controlfile to put and put_utf8.
146
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
147
        elif mode == 'r':
1185.65.29 by Robert Collins
Implement final review suggestions.
148
            return self.get_utf8(relpath)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
149
        elif mode == 'w':
1185.65.12 by Robert Collins
Remove the only-used-once put_controlfiles, and change put_controlfile to put and put_utf8.
150
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
151
        else:
152
            raise BzrError("invalid controlfile mode %r" % mode)
153
1185.65.29 by Robert Collins
Implement final review suggestions.
154
    @needs_read_lock
155
    def get(self, relpath):
156
        """Get a file as a bytestream."""
157
        relpath = self._escape(relpath)
158
        return self._transport.get(relpath)
159
160
    @needs_read_lock
161
    def get_utf8(self, relpath):
162
        """Get a file as a unicode stream."""
163
        relpath = self._escape(relpath)
164
        # DO NOT introduce an errors=replace here.
165
        return codecs.getreader('utf-8')(self._transport.get(relpath))
166
1185.65.27 by Robert Collins
Tweak storage towards mergability.
167
    @needs_write_lock
1185.65.12 by Robert Collins
Remove the only-used-once put_controlfiles, and change put_controlfile to put and put_utf8.
168
    def put(self, path, file):
169
        """Write a file.
170
        
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
171
        :param path: The path to put the file, relative to the .bzr control
172
                     directory
173
        :param f: A file-like or string object whose contents should be copied.
174
        """
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
175
        self._transport.put(self._escape(path), file, mode=self._file_mode)
1185.65.12 by Robert Collins
Remove the only-used-once put_controlfiles, and change put_controlfile to put and put_utf8.
176
1185.65.27 by Robert Collins
Tweak storage towards mergability.
177
    @needs_write_lock
1185.65.29 by Robert Collins
Implement final review suggestions.
178
    def put_utf8(self, path, a_string):
179
        """Write a string, encoding as utf-8.
1185.65.12 by Robert Collins
Remove the only-used-once put_controlfiles, and change put_controlfile to put and put_utf8.
180
1185.65.29 by Robert Collins
Implement final review suggestions.
181
        :param path: The path to put the string, relative to the transport root.
182
        :param string: A file-like or string object whose contents should be copied.
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
183
        """
1185.67.7 by Aaron Bentley
Refactored a bit
184
        # IterableFile would not be needed if Transport.put took iterables
185
        # instead of files.  ADHB 2005-12-25
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
186
        # RBC 20060103 surely its not needed anyway, with codecs transcode
187
        # file support ?
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
188
        # JAM 20060103 We definitely don't want encode(..., 'replace')
189
        # these are valuable files which should have exact contents.
1185.65.29 by Robert Collins
Implement final review suggestions.
190
        if not isinstance(a_string, basestring):
191
            raise errors.BzrBadParameterNotString(a_string)
192
        self.put(path, StringIO(a_string.encode('utf-8')))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
193
194
    def lock_write(self):
1551.2.3 by Aaron Bentley
Removed lock-related log spam [recommit]
195
        # mutter("lock write: %s (%s)", self, self._lock_count)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
196
        # TODO: Upgrade locking to support using a Transport,
197
        # and potentially a remote locking protocol
198
        if self._lock_mode:
1594.2.22 by Robert Collins
Ensure that lockable files calls finish() on transactions.:
199
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
1553.5.33 by Martin Pool
LockDir review comment fixes
200
                raise ReadOnlyError(self)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
201
            self._lock_count += 1
202
        else:
1553.5.47 by Martin Pool
cleanup LockableFiles
203
            self._lock.lock_write()
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
204
            self._lock_mode = 'w'
205
            self._lock_count = 1
1563.2.34 by Robert Collins
Remove the commit and rollback transaction methods as misleading, and implement a WriteTransaction
206
            self._set_transaction(transactions.WriteTransaction())
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
207
208
    def lock_read(self):
1551.2.3 by Aaron Bentley
Removed lock-related log spam [recommit]
209
        # mutter("lock read: %s (%s)", self, self._lock_count)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
210
        if self._lock_mode:
211
            assert self._lock_mode in ('r', 'w'), \
212
                   "invalid lock mode %r" % self._lock_mode
213
            self._lock_count += 1
214
        else:
1553.5.47 by Martin Pool
cleanup LockableFiles
215
            self._lock.lock_read()
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
216
            self._lock_mode = 'r'
217
            self._lock_count = 1
218
            self._set_transaction(transactions.ReadOnlyTransaction())
219
            # 5K may be excessive, but hey, its a knob.
220
            self.get_transaction().set_cache_size(5000)
221
                        
222
    def unlock(self):
1551.2.3 by Aaron Bentley
Removed lock-related log spam [recommit]
223
        # mutter("unlock: %s (%s)", self, self._lock_count)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
224
        if not self._lock_mode:
1553.5.36 by Martin Pool
Clean up duplicate BranchNotLocked error and rename to ObjectNotLocked
225
            raise errors.LockNotHeld(self)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
226
        if self._lock_count > 1:
227
            self._lock_count -= 1
228
        else:
229
            self._finish_transaction()
1553.5.47 by Martin Pool
cleanup LockableFiles
230
            self._lock.unlock()
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
231
            self._lock_mode = self._lock_count = None
232
1553.5.35 by Martin Pool
Start break-lock --show
233
    def is_locked(self):
234
        """Return true if this LockableFiles group is locked"""
235
        return self._lock_count >= 1
236
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
237
    def get_transaction(self):
238
        """Return the current active transaction.
239
240
        If no transaction is active, this returns a passthrough object
241
        for which all data is immediately flushed and no caching happens.
242
        """
243
        if self._transaction is None:
244
            return transactions.PassThroughTransaction()
245
        else:
246
            return self._transaction
247
248
    def _set_transaction(self, new_transaction):
249
        """Set a new active transaction."""
250
        if self._transaction is not None:
251
            raise errors.LockError('Branch %s is in a transaction already.' %
252
                                   self)
253
        self._transaction = new_transaction
254
255
    def _finish_transaction(self):
256
        """Exit the current transaction."""
257
        if self._transaction is None:
258
            raise errors.LockError('Branch %s is not in a transaction' %
259
                                   self)
260
        transaction = self._transaction
261
        self._transaction = None
262
        transaction.finish()
1553.5.40 by Martin Pool
Factor locking strategy out of LockableFiles so that we can use LockDirs in new formats.
263
264
1553.5.45 by Martin Pool
Clean up Transport-based locks for old branches
265
class TransportLock(object):
266
    """Locking method which uses transport-dependent locks.
267
268
    On the local filesystem these transform into OS-managed locks.
269
270
    These do not guard against concurrent access via different
271
    transports.
272
273
    This is suitable for use only in WorkingTrees (which are at present
274
    always local).
1553.5.40 by Martin Pool
Factor locking strategy out of LockableFiles so that we can use LockDirs in new formats.
275
    """
1553.5.59 by Martin Pool
Pass file/mode bits through to creation of lock files/dirs
276
    def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
1553.5.40 by Martin Pool
Factor locking strategy out of LockableFiles so that we can use LockDirs in new formats.
277
        self._transport = transport
278
        self._escaped_name = escaped_name
1553.5.59 by Martin Pool
Pass file/mode bits through to creation of lock files/dirs
279
        self._file_modebits = file_modebits
280
        self._dir_modebits = dir_modebits
1553.5.40 by Martin Pool
Factor locking strategy out of LockableFiles so that we can use LockDirs in new formats.
281
282
    def lock_write(self):
283
        self._lock = self._transport.lock_write(self._escaped_name)
284
285
    def lock_read(self):
286
        self._lock = self._transport.lock_read(self._escaped_name)
287
288
    def unlock(self):
289
        self._lock.unlock()
290
        self._lock = None
291
1553.5.59 by Martin Pool
Pass file/mode bits through to creation of lock files/dirs
292
    def create(self):
293
        """Create lock mechanism"""
294
        # for old-style locks, create the file now
1553.5.60 by Martin Pool
New LockableFiles.create_lock() method
295
        self._transport.put(self._escaped_name, StringIO(), 
296
                            mode=self._file_modebits)