~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2005-08-12 15:37:01 UTC
  • Revision ID: mbp@sourcefrog.net-20050812153701-ee26769ddc584e66
- merge john's "missing" command

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
17
 
from cStringIO import StringIO
18
 
 
19
 
from bzrlib.lockable_files import LockableFiles
20
 
from bzrlib.tree import EmptyTree
21
 
from bzrlib.revision import NULL_REVISION
22
 
from bzrlib.store import copy_all
23
 
from bzrlib.store.weave import WeaveStore
24
 
from bzrlib.store.text import TextStore
25
 
import bzrlib.xml5
26
 
from bzrlib.tree import RevisionTree
27
 
from bzrlib.errors import InvalidRevisionId
28
 
from bzrlib.testament import Testament
29
 
 
30
 
 
31
 
def needs_read_lock(unbound):
32
 
    """Decorate unbound to take out and release a read lock."""
33
 
    def decorated(self, *args, **kwargs):
34
 
        self.control_files.lock_read()
35
 
        try:
36
 
            return unbound(self, *args, **kwargs)
37
 
        finally:
38
 
            self.control_files.unlock()
39
 
    return decorated
40
 
 
41
 
 
42
 
def needs_write_lock(unbound):
43
 
    """Decorate unbound to take out and release a write lock."""
44
 
    def decorated(self, *args, **kwargs):
45
 
        self.control_files.lock_write()
46
 
        try:
47
 
            return unbound(self, *args, **kwargs)
48
 
        finally:
49
 
            self.control_files.unlock()
50
 
    return decorated
51
 
 
52
 
 
53
 
class Repository(object):
54
 
 
55
 
    def __init__(self, transport, branch_format):
56
 
        object.__init__(self)
57
 
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
58
 
 
59
 
        dir_mode = self.control_files._dir_mode
60
 
        file_mode = self.control_files._file_mode
61
 
 
62
 
        def get_weave(name, prefixed=False):
63
 
            if name:
64
 
                name = bzrlib.BZRDIR + '/' + unicode(name)
65
 
            else:
66
 
                name = bzrlib.BZRDIR
67
 
            relpath = self.control_files._escape(name)
68
 
            weave_transport = transport.clone(relpath)
69
 
            ws = WeaveStore(weave_transport, prefixed=prefixed,
70
 
                            dir_mode=dir_mode,
71
 
                            file_mode=file_mode)
72
 
            if self.control_files._transport.should_cache():
73
 
                ws.enable_cache = True
74
 
            return ws
75
 
 
76
 
        def get_store(name, compressed=True, prefixed=False):
77
 
            # FIXME: This approach of assuming stores are all entirely compressed
78
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
79
 
            # some existing branches where there's a mixture; we probably 
80
 
            # still want the option to look for both.
81
 
            if name:
82
 
                name = bzrlib.BZRDIR + '/' + unicode(name)
83
 
            else:
84
 
                name = bzrlib.BZRDIR
85
 
            relpath = self.control_files._escape(name)
86
 
            store = TextStore(transport.clone(relpath),
87
 
                              prefixed=prefixed, compressed=compressed,
88
 
                              dir_mode=dir_mode,
89
 
                              file_mode=file_mode)
90
 
            #if self._transport.should_cache():
91
 
            #    cache_path = os.path.join(self.cache_root, name)
92
 
            #    os.mkdir(cache_path)
93
 
            #    store = bzrlib.store.CachedStore(store, cache_path)
94
 
            return store
95
 
 
96
 
        if branch_format == 4:
97
 
            self.inventory_store = get_store('inventory-store')
98
 
            self.text_store = get_store('text-store')
99
 
            self.revision_store = get_store('revision-store')
100
 
        elif branch_format == 5:
101
 
            self.control_weaves = get_weave('')
102
 
            self.weave_store = get_weave('weaves')
103
 
            self.revision_store = get_store('revision-store', compressed=False)
104
 
        elif branch_format == 6:
105
 
            self.control_weaves = get_weave('')
106
 
            self.weave_store = get_weave('weaves', prefixed=True)
107
 
            self.revision_store = get_store('revision-store', compressed=False,
108
 
                                            prefixed=True)
109
 
        self.revision_store.register_suffix('sig')
110
 
 
111
 
    def lock_write(self):
112
 
        self.control_files.lock_write()
113
 
 
114
 
    def lock_read(self):
115
 
        self.control_files.lock_read()
116
 
 
117
 
    def unlock(self):
118
 
        self.control_files.unlock()
119
 
 
120
 
    def copy(self, destination):
121
 
        destination.control_weaves.copy_multi(self.control_weaves, 
122
 
                ['inventory'])
123
 
        copy_all(self.weave_store, destination.weave_store)
124
 
        copy_all(self.revision_store, destination.revision_store)
125
 
 
126
 
    def has_revision(self, revision_id):
127
 
        """True if this branch has a copy of the revision.
128
 
 
129
 
        This does not necessarily imply the revision is merge
130
 
        or on the mainline."""
131
 
        return (revision_id is None
132
 
                or self.revision_store.has_id(revision_id))
133
 
 
134
 
    @needs_read_lock
135
 
    def get_revision_xml_file(self, revision_id):
136
 
        """Return XML file object for revision object."""
137
 
        if not revision_id or not isinstance(revision_id, basestring):
138
 
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
139
 
        try:
140
 
            return self.revision_store.get(revision_id)
141
 
        except (IndexError, KeyError):
142
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
143
 
 
144
 
    def get_revision_xml(self, revision_id):
145
 
        return self.get_revision_xml_file(revision_id).read()
146
 
 
147
 
    def get_revision(self, revision_id):
148
 
        """Return the Revision object for a named revision"""
149
 
        xml_file = self.get_revision_xml_file(revision_id)
150
 
 
151
 
        try:
152
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
153
 
        except SyntaxError, e:
154
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
155
 
                                         [revision_id,
156
 
                                          str(e)])
157
 
            
158
 
        assert r.revision_id == revision_id
159
 
        return r
160
 
 
161
 
    def get_revision_sha1(self, revision_id):
162
 
        """Hash the stored value of a revision, and return it."""
163
 
        # In the future, revision entries will be signed. At that
164
 
        # point, it is probably best *not* to include the signature
165
 
        # in the revision hash. Because that lets you re-sign
166
 
        # the revision, (add signatures/remove signatures) and still
167
 
        # have all hash pointers stay consistent.
168
 
        # But for now, just hash the contents.
169
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
170
 
 
171
 
    @needs_write_lock
172
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
173
 
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
174
 
                                revision_id, "sig")
175
 
 
176
 
    def get_inventory_weave(self):
177
 
        return self.control_weaves.get_weave('inventory',
178
 
            self.get_transaction())
179
 
 
180
 
    def get_inventory(self, revision_id):
181
 
        """Get Inventory object by hash."""
182
 
        xml = self.get_inventory_xml(revision_id)
183
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
184
 
 
185
 
    def get_inventory_xml(self, revision_id):
186
 
        """Get inventory XML as a file object."""
187
 
        try:
188
 
            assert isinstance(revision_id, basestring), type(revision_id)
189
 
            iw = self.get_inventory_weave()
190
 
            return iw.get_text(iw.lookup(revision_id))
191
 
        except IndexError:
192
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
193
 
 
194
 
    def get_inventory_sha1(self, revision_id):
195
 
        """Return the sha1 hash of the inventory entry
196
 
        """
197
 
        return self.get_revision(revision_id).inventory_sha1
198
 
 
199
 
    def get_revision_inventory(self, revision_id):
200
 
        """Return inventory of a past revision."""
201
 
        # TODO: Unify this with get_inventory()
202
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
203
 
        # must be the same as its revision, so this is trivial.
204
 
        if revision_id == None:
205
 
            # This does not make sense: if there is no revision,
206
 
            # then it is the current tree inventory surely ?!
207
 
            # and thus get_root_id() is something that looks at the last
208
 
            # commit on the branch, and the get_root_id is an inventory check.
209
 
            raise NotImplementedError
210
 
            # return Inventory(self.get_root_id())
211
 
        else:
212
 
            return self.get_inventory(revision_id)
213
 
 
214
 
    def revision_tree(self, revision_id):
215
 
        """Return Tree for a revision on this branch.
216
 
 
217
 
        `revision_id` may be None for the null revision, in which case
218
 
        an `EmptyTree` is returned."""
219
 
        # TODO: refactor this to use an existing revision object
220
 
        # so we don't need to read it in twice.
221
 
        if revision_id == None or revision_id == NULL_REVISION:
222
 
            return EmptyTree()
223
 
        else:
224
 
            inv = self.get_revision_inventory(revision_id)
225
 
            return RevisionTree(self, inv, revision_id)
226
 
 
227
 
    def get_ancestry(self, revision_id):
228
 
        """Return a list of revision-ids integrated by a revision.
229
 
        
230
 
        This is topologically sorted.
231
 
        """
232
 
        if revision_id is None:
233
 
            return [None]
234
 
        w = self.get_inventory_weave()
235
 
        return [None] + map(w.idx_to_name,
236
 
                            w.inclusions([w.lookup(revision_id)]))
237
 
 
238
 
    @needs_read_lock
239
 
    def print_file(self, file, revision_id):
240
 
        """Print `file` to stdout."""
241
 
        tree = self.revision_tree(revision_id)
242
 
        # use inventory as it was in that revision
243
 
        file_id = tree.inventory.path2id(file)
244
 
        if not file_id:
245
 
            raise BzrError("%r is not present in revision %s" % (file, revno))
246
 
            try:
247
 
                revno = self.revision_id_to_revno(revision_id)
248
 
            except errors.NoSuchRevision:
249
 
                # TODO: This should not be BzrError,
250
 
                # but NoSuchFile doesn't fit either
251
 
                raise BzrError('%r is not present in revision %s' 
252
 
                                % (file, revision_id))
253
 
            else:
254
 
                raise BzrError('%r is not present in revision %s'
255
 
                                % (file, revno))
256
 
        tree.print_file(file_id)
257
 
 
258
 
    def get_transaction(self):
259
 
        return self.control_files.get_transaction()
260
 
 
261
 
    def sign_revision(self, revision_id, gpg_strategy):
262
 
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
263
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)