~bzr-pqm/bzr/bzr.dev

1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
1
import bzrlib
2
from bzrlib.trace import mutter
3
import bzrlib.transactions as transactions
4
import errors
5
from errors import LockError
6
7
class ControlFiles(object):
8
    """
9
    Object representing a set of control files
10
    _lock_mode
11
        None, or 'r' or 'w'
12
13
    _lock_count
14
        If _lock_mode is true, a positive count of the number of times the
15
        lock has been taken.
16
17
    _lock
18
        Lock object from bzrlib.lock.
19
    """
20
    _lock_mode = None
21
    _lock_count = None
22
    _lock = None
23
    def __init__(self, transport, lock_name):
24
        object.__init__(self)
25
        self._transport = transport
26
        self.lock_name = lock_name
27
        self._transaction = None
28
29
    def __del__(self):
30
        if self._lock_mode or self._lock:
31
            # XXX: This should show something every time, and be suitable for
32
            # headless operation and embedding
33
            warn("branch %r was not explicitly unlocked" % self)
34
            self._lock.unlock()
35
36
    def _rel_controlfilename(self, file_or_path):
37
        if not isinstance(file_or_path, basestring):
38
            file_or_path = '/'.join(file_or_path)
39
        if file_or_path == '':
40
            return bzrlib.BZRDIR
41
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
42
43
    def controlfilename(self, file_or_path):
44
        """Return location relative to branch."""
45
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
46
47
    def controlfile(self, file_or_path, mode='r'):
48
        """Open a control file for this branch.
49
50
        There are two classes of file in the control directory: text
51
        and binary.  binary files are untranslated byte streams.  Text
52
        control files are stored with Unix newlines and in UTF-8, even
53
        if the platform or locale defaults are different.
54
55
        Controlfiles should almost never be opened in write mode but
56
        rather should be atomically copied and replaced using atomicfile.
57
        """
58
        import codecs
59
60
        relpath = self._rel_controlfilename(file_or_path)
61
        #TODO: codecs.open() buffers linewise, so it was overloaded with
62
        # a much larger buffer, do we need to do the same for getreader/getwriter?
63
        if mode == 'rb': 
64
            return self._transport.get(relpath)
65
        elif mode == 'wb':
66
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
67
        elif mode == 'r':
68
            # XXX: Do we really want errors='replace'?   Perhaps it should be
69
            # an error, or at least reported, if there's incorrectly-encoded
70
            # data inside a file.
71
            # <https://launchpad.net/products/bzr/+bug/3823>
72
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
73
        elif mode == 'w':
74
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
75
        else:
76
            raise BzrError("invalid controlfile mode %r" % mode)
77
78
    def put_controlfile(self, path, f, encode=True):
79
        """Write an entry as a controlfile.
80
81
        :param path: The path to put the file, relative to the .bzr control
82
                     directory
83
        :param f: A file-like or string object whose contents should be copied.
84
        :param encode:  If true, encode the contents as utf-8
85
        """
86
        self.put_controlfiles([(path, f)], encode=encode)
87
88
    def put_controlfiles(self, files, encode=True):
89
        """Write several entries as controlfiles.
90
91
        :param files: A list of [(path, file)] pairs, where the path is the directory
92
                      underneath the bzr control directory
93
        :param encode:  If true, encode the contents as utf-8
94
        """
95
        import codecs
96
        ctrl_files = []
97
        for path, f in files:
98
            if encode:
99
                if isinstance(f, basestring):
100
                    f = f.encode('utf-8', 'replace')
101
                else:
102
                    f = codecs.getwriter('utf-8')(f, errors='replace')
103
            path = self._rel_controlfilename(path)
104
            ctrl_files.append((path, f))
105
        self._transport.put_multi(ctrl_files)
106
107
    def lock_write(self):
108
        mutter("lock write: %s (%s)", self, self._lock_count)
109
        # TODO: Upgrade locking to support using a Transport,
110
        # and potentially a remote locking protocol
111
        if self._lock_mode:
112
            if self._lock_mode != 'w':
113
                raise LockError("can't upgrade to a write lock from %r" %
114
                                self._lock_mode)
115
            self._lock_count += 1
116
        else:
117
            self._lock = self._transport.lock_write(
118
                    self._rel_controlfilename(self.lock_name))
119
            self._lock_mode = 'w'
120
            self._lock_count = 1
121
            self._set_transaction(transactions.PassThroughTransaction())
122
123
    def lock_read(self):
124
        mutter("lock read: %s (%s)", self, self._lock_count)
125
        if self._lock_mode:
126
            assert self._lock_mode in ('r', 'w'), \
127
                   "invalid lock mode %r" % self._lock_mode
128
            self._lock_count += 1
129
        else:
130
            self._lock = self._transport.lock_read(
131
                    self._rel_controlfilename('branch-lock'))
132
            self._lock_mode = 'r'
133
            self._lock_count = 1
134
            self._set_transaction(transactions.ReadOnlyTransaction())
135
            # 5K may be excessive, but hey, its a knob.
136
            self.get_transaction().set_cache_size(5000)
137
                        
138
    def unlock(self):
139
        mutter("unlock: %s (%s)", self, self._lock_count)
140
        if not self._lock_mode:
141
            raise LockError('branch %r is not locked' % (self))
142
143
        if self._lock_count > 1:
144
            self._lock_count -= 1
145
        else:
146
            self._finish_transaction()
147
            self._lock.unlock()
148
            self._lock = None
149
            self._lock_mode = self._lock_count = None
150
151
    def make_transport(self, relpath):
152
        return self._transport.clone(relpath)
153
154
    def get_transaction(self):
155
        """Return the current active transaction.
156
157
        If no transaction is active, this returns a passthrough object
158
        for which all data is immediately flushed and no caching happens.
159
        """
160
        if self._transaction is None:
161
            return transactions.PassThroughTransaction()
162
        else:
163
            return self._transaction
164
165
    def _set_transaction(self, new_transaction):
166
        """Set a new active transaction."""
167
        if self._transaction is not None:
168
            raise errors.LockError('Branch %s is in a transaction already.' %
169
                                   self)
170
        self._transaction = new_transaction
171
172
    def _finish_transaction(self):
173
        """Exit the current transaction."""
174
        if self._transaction is None:
175
            raise errors.LockError('Branch %s is not in a transaction' %
176
                                   self)
177
        transaction = self._transaction
178
        self._transaction = None
179
        transaction.finish()