~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Erik Bågfors
  • Date: 2006-02-03 19:50:59 UTC
  • mto: (1185.50.77 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: erik@bagfors.nu-20060203195059-1cf8ff5aa68de0ea
Support for plugins to register log formatters and set default formatter
Also, change one command line option for "log"

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