~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 *
1534.1.15 by Robert Collins
* The internal storage of history, and logical branch identity have now
26
from bzrlib.symbol_versioning import deprecated_method, zero_eight
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:
1534.4.28 by Robert Collins
first cut at merge from integration.
79
            st = self._transport.stat('.')
80
        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
81
            self._dir_mode = 0755
82
            self._file_mode = 0644
83
        else:
84
            self._dir_mode = st.st_mode & 07777
85
            # Remove the sticky and execute bits for files
86
            self._file_mode = self._dir_mode & ~07111
87
        if not self._set_dir_mode:
88
            self._dir_mode = None
89
        if not self._set_file_mode:
90
            self._file_mode = None
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
91
92
    def controlfilename(self, file_or_path):
93
        """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
94
        return self._transport.abspath(self._escape(file_or_path))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
95
1534.1.15 by Robert Collins
* The internal storage of history, and logical branch identity have now
96
    @deprecated_method(zero_eight)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
97
    def controlfile(self, file_or_path, mode='r'):
98
        """Open a control file for this branch.
99
1185.65.27 by Robert Collins
Tweak storage towards mergability.
100
        There are two classes of file in a lockable directory: text
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
101
        and binary.  binary files are untranslated byte streams.  Text
102
        control files are stored with Unix newlines and in UTF-8, even
103
        if the platform or locale defaults are different.
104
1185.65.27 by Robert Collins
Tweak storage towards mergability.
105
        Such files are not openable in write mode : they are managed via
106
        put and put_utf8 which atomically replace old versions using
107
        atomicfile.
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
108
        """
109
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
110
        relpath = self._escape(file_or_path)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
111
        #TODO: codecs.open() buffers linewise, so it was overloaded with
112
        # a much larger buffer, do we need to do the same for getreader/getwriter?
113
        if mode == 'rb': 
1185.65.29 by Robert Collins
Implement final review suggestions.
114
            return self.get(relpath)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
115
        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.
116
            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
117
        elif mode == 'r':
1185.65.29 by Robert Collins
Implement final review suggestions.
118
            return self.get_utf8(relpath)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
119
        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.
120
            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
121
        else:
122
            raise BzrError("invalid controlfile mode %r" % mode)
123
1185.65.29 by Robert Collins
Implement final review suggestions.
124
    @needs_read_lock
125
    def get(self, relpath):
126
        """Get a file as a bytestream."""
127
        relpath = self._escape(relpath)
128
        return self._transport.get(relpath)
129
130
    @needs_read_lock
131
    def get_utf8(self, relpath):
132
        """Get a file as a unicode stream."""
133
        relpath = self._escape(relpath)
134
        # DO NOT introduce an errors=replace here.
135
        return codecs.getreader('utf-8')(self._transport.get(relpath))
136
1185.65.27 by Robert Collins
Tweak storage towards mergability.
137
    @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.
138
    def put(self, path, file):
139
        """Write a file.
140
        
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
141
        :param path: The path to put the file, relative to the .bzr control
142
                     directory
143
        :param f: A file-like or string object whose contents should be copied.
144
        """
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
145
        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.
146
1185.65.27 by Robert Collins
Tweak storage towards mergability.
147
    @needs_write_lock
1185.65.29 by Robert Collins
Implement final review suggestions.
148
    def put_utf8(self, path, a_string):
149
        """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.
150
1185.65.29 by Robert Collins
Implement final review suggestions.
151
        :param path: The path to put the string, relative to the transport root.
152
        :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
153
        """
1185.67.7 by Aaron Bentley
Refactored a bit
154
        # IterableFile would not be needed if Transport.put took iterables
155
        # instead of files.  ADHB 2005-12-25
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
156
        # RBC 20060103 surely its not needed anyway, with codecs transcode
157
        # 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
158
        # JAM 20060103 We definitely don't want encode(..., 'replace')
159
        # these are valuable files which should have exact contents.
1185.65.29 by Robert Collins
Implement final review suggestions.
160
        if not isinstance(a_string, basestring):
161
            raise errors.BzrBadParameterNotString(a_string)
162
        self.put(path, StringIO(a_string.encode('utf-8')))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
163
164
    def lock_write(self):
1551.2.3 by Aaron Bentley
Removed lock-related log spam [recommit]
165
        # mutter("lock write: %s (%s)", self, self._lock_count)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
166
        # TODO: Upgrade locking to support using a Transport,
167
        # and potentially a remote locking protocol
168
        if self._lock_mode:
169
            if self._lock_mode != 'w':
1553.5.33 by Martin Pool
LockDir review comment fixes
170
                raise ReadOnlyError(self)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
171
            self._lock_count += 1
172
        else:
173
            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
174
                    self._escape(self.lock_name))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
175
            self._lock_mode = 'w'
176
            self._lock_count = 1
177
            self._set_transaction(transactions.PassThroughTransaction())
178
179
    def lock_read(self):
1551.2.3 by Aaron Bentley
Removed lock-related log spam [recommit]
180
        # mutter("lock read: %s (%s)", self, self._lock_count)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
181
        if self._lock_mode:
182
            assert self._lock_mode in ('r', 'w'), \
183
                   "invalid lock mode %r" % self._lock_mode
184
            self._lock_count += 1
185
        else:
186
            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
187
                    self._escape(self.lock_name))
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
188
            self._lock_mode = 'r'
189
            self._lock_count = 1
190
            self._set_transaction(transactions.ReadOnlyTransaction())
191
            # 5K may be excessive, but hey, its a knob.
192
            self.get_transaction().set_cache_size(5000)
193
                        
194
    def unlock(self):
1551.2.3 by Aaron Bentley
Removed lock-related log spam [recommit]
195
        # mutter("unlock: %s (%s)", self, self._lock_count)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
196
        if not self._lock_mode:
1553.5.28 by Martin Pool
[merge] from bzr.dev before integration
197
            raise errors.BranchNotLocked(self)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
198
        if self._lock_count > 1:
199
            self._lock_count -= 1
200
        else:
201
            self._finish_transaction()
202
            self._lock.unlock()
203
            self._lock = None
204
            self._lock_mode = self._lock_count = None
205
206
    def get_transaction(self):
207
        """Return the current active transaction.
208
209
        If no transaction is active, this returns a passthrough object
210
        for which all data is immediately flushed and no caching happens.
211
        """
212
        if self._transaction is None:
213
            return transactions.PassThroughTransaction()
214
        else:
215
            return self._transaction
216
217
    def _set_transaction(self, new_transaction):
218
        """Set a new active transaction."""
219
        if self._transaction is not None:
220
            raise errors.LockError('Branch %s is in a transaction already.' %
221
                                   self)
222
        self._transaction = new_transaction
223
224
    def _finish_transaction(self):
225
        """Exit the current transaction."""
226
        if self._transaction is None:
227
            raise errors.LockError('Branch %s is not in a transaction' %
228
                                   self)
229
        transaction = self._transaction
230
        self._transaction = None
231
        transaction.finish()