2
import bzrlib.errors as errors
3
from bzrlib.errors import LockError
4
from bzrlib.trace import mutter
5
import bzrlib.transactions as transactions
7
class LockableFiles(object):
8
"""Object representing a set of lockable files
14
If _lock_mode is true, a positive count of the number of times the
18
Lock object from bzrlib.lock.
25
def __init__(self, transport, lock_name):
27
self._transport = transport
28
self.lock_name = lock_name
29
self._transaction = None
32
if self._lock_mode or self._lock:
33
# XXX: This should show something every time, and be suitable for
34
# headless operation and embedding
35
warn("file group %r was not explicitly unlocked" % self)
38
def _rel_controlfilename(self, file_or_path):
39
if not isinstance(file_or_path, basestring):
40
file_or_path = '/'.join(file_or_path)
41
if file_or_path == '':
42
return unicode(bzrlib.BZRDIR)
43
return bzrlib.transport.urlescape(unicode(bzrlib.BZRDIR + '/' + file_or_path))
45
def controlfilename(self, file_or_path):
46
"""Return location relative to branch."""
47
return self._transport.abspath(self._rel_controlfilename(file_or_path))
49
def controlfile(self, file_or_path, mode='r'):
50
"""Open a control file for this branch.
52
There are two classes of file in the control directory: text
53
and binary. binary files are untranslated byte streams. Text
54
control files are stored with Unix newlines and in UTF-8, even
55
if the platform or locale defaults are different.
57
Controlfiles should almost never be opened in write mode but
58
rather should be atomically copied and replaced using atomicfile.
62
relpath = self._rel_controlfilename(file_or_path)
63
#TODO: codecs.open() buffers linewise, so it was overloaded with
64
# a much larger buffer, do we need to do the same for getreader/getwriter?
66
return self._transport.get(relpath)
68
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
70
# XXX: Do we really want errors='replace'? Perhaps it should be
71
# an error, or at least reported, if there's incorrectly-encoded
73
# <https://launchpad.net/products/bzr/+bug/3823>
74
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
76
raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
78
raise BzrError("invalid controlfile mode %r" % mode)
80
def put(self, path, file):
83
:param path: The path to put the file, relative to the .bzr control
85
:param f: A file-like or string object whose contents should be copied.
87
self._transport.put(self._rel_controlfilename(path), file)
89
def put_utf8(self, path, file):
90
"""Write a file, encoding as utf-8.
92
:param path: The path to put the file, relative to the .bzr control
94
:param f: A file-like or string object whose contents should be copied.
98
if isinstance(file, basestring):
99
file = file.encode('utf-8', 'replace')
101
file = codecs.getwriter('utf-8')(file, errors='replace')
104
def lock_write(self):
105
mutter("lock write: %s (%s)", self, self._lock_count)
106
# TODO: Upgrade locking to support using a Transport,
107
# and potentially a remote locking protocol
109
if self._lock_mode != 'w':
110
raise LockError("can't upgrade to a write lock from %r" %
112
self._lock_count += 1
114
self._lock = self._transport.lock_write(
115
self._rel_controlfilename(self.lock_name))
116
self._lock_mode = 'w'
118
self._set_transaction(transactions.PassThroughTransaction())
121
mutter("lock read: %s (%s)", self, self._lock_count)
123
assert self._lock_mode in ('r', 'w'), \
124
"invalid lock mode %r" % self._lock_mode
125
self._lock_count += 1
127
self._lock = self._transport.lock_read(
128
self._rel_controlfilename('branch-lock'))
129
self._lock_mode = 'r'
131
self._set_transaction(transactions.ReadOnlyTransaction())
132
# 5K may be excessive, but hey, its a knob.
133
self.get_transaction().set_cache_size(5000)
136
mutter("unlock: %s (%s)", self, self._lock_count)
137
if not self._lock_mode:
138
raise LockError('branch %r is not locked' % (self))
140
if self._lock_count > 1:
141
self._lock_count -= 1
143
self._finish_transaction()
146
self._lock_mode = self._lock_count = None
148
def make_transport(self, relpath):
149
return self._transport.clone(relpath)
151
def get_transaction(self):
152
"""Return the current active transaction.
154
If no transaction is active, this returns a passthrough object
155
for which all data is immediately flushed and no caching happens.
157
if self._transaction is None:
158
return transactions.PassThroughTransaction()
160
return self._transaction
162
def _set_transaction(self, new_transaction):
163
"""Set a new active transaction."""
164
if self._transaction is not None:
165
raise errors.LockError('Branch %s is in a transaction already.' %
167
self._transaction = new_transaction
169
def _finish_transaction(self):
170
"""Exit the current transaction."""
171
if self._transaction is None:
172
raise errors.LockError('Branch %s is not in a transaction' %
174
transaction = self._transaction
175
self._transaction = None