~bzr-pqm/bzr/bzr.dev

1185.65.22 by Robert Collins
lockable_files was extracted from branch.py - give it a copyright statement
1
# Copyright (C) 2005 Canonical Ltd
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 *
26
from bzrlib.symbol_versioning import deprecated_method, zero_seven
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
27
from bzrlib.trace import mutter
28
import bzrlib.transactions as transactions
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
29
1185.65.27 by Robert Collins
Tweak storage towards mergability.
30
1185.66.3 by Aaron Bentley
Renamed ControlFiles to LockableFiles
31
class LockableFiles(object):
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
32
    """Object representing a set of files locked within the same scope
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
33
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
34
    _lock_mode
35
        None, or 'r' or 'w'
36
37
    _lock_count
38
        If _lock_mode is true, a positive count of the number of times the
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
39
        lock has been taken *by this process*.  Others may have compatible 
40
        read locks.
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
41
42
    _lock
43
        Lock object from bzrlib.lock.
44
    """
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
45
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
46
    _lock_mode = None
47
    _lock_count = None
48
    _lock = None
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
49
    # If set to False (by a plugin, etc) BzrBranch will not set the
50
    # mode on created files or directories
51
    _set_file_mode = True
52
    _set_dir_mode = True
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
53
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
54
    def __init__(self, transport, lock_name):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
55
        object.__init__(self)
56
        self._transport = transport
57
        self.lock_name = lock_name
58
        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
59
        self._find_modes()
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
60
61
    def __del__(self):
62
        if self._lock_mode or self._lock:
63
            # XXX: This should show something every time, and be suitable for
64
            # headless operation and embedding
1185.67.9 by Aaron Bentley
Fixed most bugs
65
            from warnings import warn
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
66
            warn("file group %r was not explicitly unlocked" % self)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
67
            self._lock.unlock()
68
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
69
    def _escape(self, file_or_path):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
70
        if not isinstance(file_or_path, basestring):
71
            file_or_path = '/'.join(file_or_path)
72
        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
73
            return u''
1185.65.27 by Robert Collins
Tweak storage towards mergability.
74
        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
75
76
    def _find_modes(self):
77
        """Determine the appropriate modes for files and directories."""
78
        try:
79
            try:
1185.65.27 by Robert Collins
Tweak storage towards mergability.
80
                st = self._transport.stat('.')
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
81
            except errors.NoSuchFile:
82
                # The .bzr/ directory doesn't exist, try to
83
                # inherit the permissions from the parent directory
84
                # but only try 1 level up
85
                temp_transport = self._transport.clone('..')
1185.65.27 by Robert Collins
Tweak storage towards mergability.
86
                st = temp_transport.stat('.')
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
87
        except (errors.TransportNotPossible, errors.NoSuchFile):
88
            self._dir_mode = 0755
89
            self._file_mode = 0644
90
        else:
91
            self._dir_mode = st.st_mode & 07777
92
            # Remove the sticky and execute bits for files
93
            self._file_mode = self._dir_mode & ~07111
94
        if not self._set_dir_mode:
95
            self._dir_mode = None
96
        if not self._set_file_mode:
97
            self._file_mode = None
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
98
99
    def controlfilename(self, file_or_path):
100
        """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
101
        return self._transport.abspath(self._escape(file_or_path))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
102
1185.65.29 by Robert Collins
Implement final review suggestions.
103
    @deprecated_method(zero_seven)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
104
    def controlfile(self, file_or_path, mode='r'):
105
        """Open a control file for this branch.
106
1185.65.27 by Robert Collins
Tweak storage towards mergability.
107
        There are two classes of file in a lockable directory: text
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
108
        and binary.  binary files are untranslated byte streams.  Text
109
        control files are stored with Unix newlines and in UTF-8, even
110
        if the platform or locale defaults are different.
111
1185.65.27 by Robert Collins
Tweak storage towards mergability.
112
        Such files are not openable in write mode : they are managed via
113
        put and put_utf8 which atomically replace old versions using
114
        atomicfile.
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
115
        """
116
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
117
        relpath = self._escape(file_or_path)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
118
        #TODO: codecs.open() buffers linewise, so it was overloaded with
119
        # a much larger buffer, do we need to do the same for getreader/getwriter?
120
        if mode == 'rb': 
1185.65.29 by Robert Collins
Implement final review suggestions.
121
            return self.get(relpath)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
122
        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.
123
            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
124
        elif mode == 'r':
1185.65.29 by Robert Collins
Implement final review suggestions.
125
            return self.get_utf8(relpath)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
126
        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.
127
            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
128
        else:
129
            raise BzrError("invalid controlfile mode %r" % mode)
130
1185.65.29 by Robert Collins
Implement final review suggestions.
131
    @needs_read_lock
132
    def get(self, relpath):
133
        """Get a file as a bytestream."""
134
        relpath = self._escape(relpath)
135
        return self._transport.get(relpath)
136
137
    @needs_read_lock
138
    def get_utf8(self, relpath):
139
        """Get a file as a unicode stream."""
140
        relpath = self._escape(relpath)
141
        # DO NOT introduce an errors=replace here.
142
        return codecs.getreader('utf-8')(self._transport.get(relpath))
143
1185.65.27 by Robert Collins
Tweak storage towards mergability.
144
    @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.
145
    def put(self, path, file):
146
        """Write a file.
147
        
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
148
        :param path: The path to put the file, relative to the .bzr control
149
                     directory
150
        :param f: A file-like or string object whose contents should be copied.
151
        """
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
152
        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.
153
1185.65.27 by Robert Collins
Tweak storage towards mergability.
154
    @needs_write_lock
1185.65.29 by Robert Collins
Implement final review suggestions.
155
    def put_utf8(self, path, a_string):
156
        """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.
157
1185.65.29 by Robert Collins
Implement final review suggestions.
158
        :param path: The path to put the string, relative to the transport root.
159
        :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
160
        """
1185.67.7 by Aaron Bentley
Refactored a bit
161
        # IterableFile would not be needed if Transport.put took iterables
162
        # instead of files.  ADHB 2005-12-25
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
163
        # RBC 20060103 surely its not needed anyway, with codecs transcode
164
        # 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
165
        # JAM 20060103 We definitely don't want encode(..., 'replace')
166
        # these are valuable files which should have exact contents.
1185.65.29 by Robert Collins
Implement final review suggestions.
167
        if not isinstance(a_string, basestring):
168
            raise errors.BzrBadParameterNotString(a_string)
169
        self.put(path, StringIO(a_string.encode('utf-8')))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
170
171
    def lock_write(self):
172
        mutter("lock write: %s (%s)", self, self._lock_count)
173
        # TODO: Upgrade locking to support using a Transport,
174
        # and potentially a remote locking protocol
175
        if self._lock_mode:
176
            if self._lock_mode != 'w':
1185.65.27 by Robert Collins
Tweak storage towards mergability.
177
                raise ReadOnlyError("can't upgrade to a write lock from %r" %
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
178
                                self._lock_mode)
179
            self._lock_count += 1
180
        else:
181
            self._lock = self._transport.lock_write(
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
182
                    self._escape(self.lock_name))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
183
            self._lock_mode = 'w'
184
            self._lock_count = 1
185
            self._set_transaction(transactions.PassThroughTransaction())
186
187
    def lock_read(self):
188
        mutter("lock read: %s (%s)", self, self._lock_count)
189
        if self._lock_mode:
190
            assert self._lock_mode in ('r', 'w'), \
191
                   "invalid lock mode %r" % self._lock_mode
192
            self._lock_count += 1
193
        else:
194
            self._lock = self._transport.lock_read(
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
195
                    self._escape(self.lock_name))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
196
            self._lock_mode = 'r'
197
            self._lock_count = 1
198
            self._set_transaction(transactions.ReadOnlyTransaction())
199
            # 5K may be excessive, but hey, its a knob.
200
            self.get_transaction().set_cache_size(5000)
201
                        
202
    def unlock(self):
203
        mutter("unlock: %s (%s)", self, self._lock_count)
204
        if not self._lock_mode:
205
            raise LockError('branch %r is not locked' % (self))
206
207
        if self._lock_count > 1:
208
            self._lock_count -= 1
209
        else:
210
            self._finish_transaction()
211
            self._lock.unlock()
212
            self._lock = None
213
            self._lock_mode = self._lock_count = None
214
215
    def get_transaction(self):
216
        """Return the current active transaction.
217
218
        If no transaction is active, this returns a passthrough object
219
        for which all data is immediately flushed and no caching happens.
220
        """
221
        if self._transaction is None:
222
            return transactions.PassThroughTransaction()
223
        else:
224
            return self._transaction
225
226
    def _set_transaction(self, new_transaction):
227
        """Set a new active transaction."""
228
        if self._transaction is not None:
229
            raise errors.LockError('Branch %s is in a transaction already.' %
230
                                   self)
231
        self._transaction = new_transaction
232
233
    def _finish_transaction(self):
234
        """Exit the current transaction."""
235
        if self._transaction is None:
236
            raise errors.LockError('Branch %s is not in a transaction' %
237
                                   self)
238
        transaction = self._transaction
239
        self._transaction = None
240
        transaction.finish()