~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repofmt/knitrepo.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-02-13 11:48:14 UTC
  • mfrom: (2241.1.20 repoformats)
  • Revision ID: pqm@pqm.ubuntu.com-20070213114814-9606106906ac312f
(mbp) split repository formats into repofmt (r=john)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 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 bzrlib import (
 
18
    errors,
 
19
    graph,
 
20
    knit,
 
21
    lockable_files,
 
22
    lockdir,
 
23
    transactions,
 
24
    xml5,
 
25
    xml6,
 
26
    )
 
27
 
 
28
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
29
from bzrlib.repository import (
 
30
    MetaDirRepository,
 
31
    MetaDirRepositoryFormat,
 
32
    RepositoryFormat,
 
33
    RootCommitBuilder,
 
34
    )
 
35
import bzrlib.revision as _mod_revision
 
36
from bzrlib.store.versioned import VersionedFileStore
 
37
from bzrlib.trace import mutter, note, warning
 
38
 
 
39
 
 
40
class KnitRepository(MetaDirRepository):
 
41
    """Knit format repository."""
 
42
 
 
43
 
 
44
    _serializer = xml5.serializer_v5
 
45
 
 
46
    def _warn_if_deprecated(self):
 
47
        # This class isn't deprecated
 
48
        pass
 
49
 
 
50
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
51
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
 
52
 
 
53
    @needs_read_lock
 
54
    def _all_revision_ids(self):
 
55
        """See Repository.all_revision_ids()."""
 
56
        # Knits get the revision graph from the index of the revision knit, so
 
57
        # it's always possible even if they're on an unlistable transport.
 
58
        return self._revision_store.all_revision_ids(self.get_transaction())
 
59
 
 
60
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
61
        """Find file_id(s) which are involved in the changes between revisions.
 
62
 
 
63
        This determines the set of revisions which are involved, and then
 
64
        finds all file ids affected by those revisions.
 
65
        """
 
66
        vf = self._get_revision_vf()
 
67
        from_set = set(vf.get_ancestry(from_revid))
 
68
        to_set = set(vf.get_ancestry(to_revid))
 
69
        changed = to_set.difference(from_set)
 
70
        return self._fileid_involved_by_set(changed)
 
71
 
 
72
    def fileid_involved(self, last_revid=None):
 
73
        """Find all file_ids modified in the ancestry of last_revid.
 
74
 
 
75
        :param last_revid: If None, last_revision() will be used.
 
76
        """
 
77
        if not last_revid:
 
78
            changed = set(self.all_revision_ids())
 
79
        else:
 
80
            changed = set(self.get_ancestry(last_revid))
 
81
        if None in changed:
 
82
            changed.remove(None)
 
83
        return self._fileid_involved_by_set(changed)
 
84
 
 
85
    @needs_read_lock
 
86
    def get_ancestry(self, revision_id):
 
87
        """Return a list of revision-ids integrated by a revision.
 
88
        
 
89
        This is topologically sorted.
 
90
        """
 
91
        if revision_id is None:
 
92
            return [None]
 
93
        vf = self._get_revision_vf()
 
94
        try:
 
95
            return [None] + vf.get_ancestry(revision_id)
 
96
        except errors.RevisionNotPresent:
 
97
            raise errors.NoSuchRevision(self, revision_id)
 
98
 
 
99
    @needs_read_lock
 
100
    def get_revision(self, revision_id):
 
101
        """Return the Revision object for a named revision"""
 
102
        return self.get_revision_reconcile(revision_id)
 
103
 
 
104
    @needs_read_lock
 
105
    def get_revision_graph(self, revision_id=None):
 
106
        """Return a dictionary containing the revision graph.
 
107
 
 
108
        :param revision_id: The revision_id to get a graph from. If None, then
 
109
        the entire revision graph is returned. This is a deprecated mode of
 
110
        operation and will be removed in the future.
 
111
        :return: a dictionary of revision_id->revision_parents_list.
 
112
        """
 
113
        # special case NULL_REVISION
 
114
        if revision_id == _mod_revision.NULL_REVISION:
 
115
            return {}
 
116
        a_weave = self._get_revision_vf()
 
117
        entire_graph = a_weave.get_graph()
 
118
        if revision_id is None:
 
119
            return a_weave.get_graph()
 
120
        elif revision_id not in a_weave:
 
121
            raise errors.NoSuchRevision(self, revision_id)
 
122
        else:
 
123
            # add what can be reached from revision_id
 
124
            result = {}
 
125
            pending = set([revision_id])
 
126
            while len(pending) > 0:
 
127
                node = pending.pop()
 
128
                result[node] = a_weave.get_parents(node)
 
129
                for revision_id in result[node]:
 
130
                    if revision_id not in result:
 
131
                        pending.add(revision_id)
 
132
            return result
 
133
 
 
134
    @needs_read_lock
 
135
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
136
        """Return a graph of the revisions with ghosts marked as applicable.
 
137
 
 
138
        :param revision_ids: an iterable of revisions to graph or None for all.
 
139
        :return: a Graph object with the graph reachable from revision_ids.
 
140
        """
 
141
        result = graph.Graph()
 
142
        vf = self._get_revision_vf()
 
143
        versions = set(vf.versions())
 
144
        if not revision_ids:
 
145
            pending = set(self.all_revision_ids())
 
146
            required = set([])
 
147
        else:
 
148
            pending = set(revision_ids)
 
149
            # special case NULL_REVISION
 
150
            if _mod_revision.NULL_REVISION in pending:
 
151
                pending.remove(_mod_revision.NULL_REVISION)
 
152
            required = set(pending)
 
153
        done = set([])
 
154
        while len(pending):
 
155
            revision_id = pending.pop()
 
156
            if not revision_id in versions:
 
157
                if revision_id in required:
 
158
                    raise errors.NoSuchRevision(self, revision_id)
 
159
                # a ghost
 
160
                result.add_ghost(revision_id)
 
161
                # mark it as done so we don't try for it again.
 
162
                done.add(revision_id)
 
163
                continue
 
164
            parent_ids = vf.get_parents_with_ghosts(revision_id)
 
165
            for parent_id in parent_ids:
 
166
                # is this queued or done ?
 
167
                if (parent_id not in pending and
 
168
                    parent_id not in done):
 
169
                    # no, queue it.
 
170
                    pending.add(parent_id)
 
171
            result.add_node(revision_id, parent_ids)
 
172
            done.add(revision_id)
 
173
        return result
 
174
 
 
175
    def _get_revision_vf(self):
 
176
        """:return: a versioned file containing the revisions."""
 
177
        vf = self._revision_store.get_revision_file(self.get_transaction())
 
178
        return vf
 
179
 
 
180
    @needs_write_lock
 
181
    def reconcile(self, other=None, thorough=False):
 
182
        """Reconcile this repository."""
 
183
        from bzrlib.reconcile import KnitReconciler
 
184
        reconciler = KnitReconciler(self, thorough=thorough)
 
185
        reconciler.reconcile()
 
186
        return reconciler
 
187
    
 
188
    def revision_parents(self, revision_id):
 
189
        return self._get_revision_vf().get_parents(revision_id)
 
190
 
 
191
 
 
192
class KnitRepository2(KnitRepository):
 
193
    """"""
 
194
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
 
195
                 control_store, text_store):
 
196
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
197
                              _revision_store, control_store, text_store)
 
198
        self._serializer = xml6.serializer_v6
 
199
 
 
200
    def deserialise_inventory(self, revision_id, xml):
 
201
        """Transform the xml into an inventory object. 
 
202
 
 
203
        :param revision_id: The expected revision id of the inventory.
 
204
        :param xml: A serialised inventory.
 
205
        """
 
206
        result = self._serializer.read_inventory_from_string(xml)
 
207
        assert result.root.revision is not None
 
208
        return result
 
209
 
 
210
    def serialise_inventory(self, inv):
 
211
        """Transform the inventory object into XML text.
 
212
 
 
213
        :param revision_id: The expected revision id of the inventory.
 
214
        :param xml: A serialised inventory.
 
215
        """
 
216
        assert inv.revision_id is not None
 
217
        assert inv.root.revision is not None
 
218
        return KnitRepository.serialise_inventory(self, inv)
 
219
 
 
220
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
221
                           timezone=None, committer=None, revprops=None,
 
222
                           revision_id=None):
 
223
        """Obtain a CommitBuilder for this repository.
 
224
        
 
225
        :param branch: Branch to commit to.
 
226
        :param parents: Revision ids of the parents of the new revision.
 
227
        :param config: Configuration to use.
 
228
        :param timestamp: Optional timestamp recorded for commit.
 
229
        :param timezone: Optional timezone for timestamp.
 
230
        :param committer: Optional committer to set for commit.
 
231
        :param revprops: Optional dictionary of revision properties.
 
232
        :param revision_id: Optional revision id.
 
233
        """
 
234
        return RootCommitBuilder(self, parents, config, timestamp, timezone,
 
235
                                 committer, revprops, revision_id)
 
236
 
 
237
 
 
238
class RepositoryFormatKnit(MetaDirRepositoryFormat):
 
239
    """Bzr repository knit format (generalized). 
 
240
 
 
241
    This repository format has:
 
242
     - knits for file texts and inventory
 
243
     - hash subdirectory based stores.
 
244
     - knits for revisions and signatures
 
245
     - TextStores for revisions and signatures.
 
246
     - a format marker of its own
 
247
     - an optional 'shared-storage' flag
 
248
     - an optional 'no-working-trees' flag
 
249
     - a LockDir lock
 
250
    """
 
251
 
 
252
    def _get_control_store(self, repo_transport, control_files):
 
253
        """Return the control store for this repository."""
 
254
        return VersionedFileStore(
 
255
            repo_transport,
 
256
            prefixed=False,
 
257
            file_mode=control_files._file_mode,
 
258
            versionedfile_class=knit.KnitVersionedFile,
 
259
            versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
 
260
            )
 
261
 
 
262
    def _get_revision_store(self, repo_transport, control_files):
 
263
        """See RepositoryFormat._get_revision_store()."""
 
264
        from bzrlib.store.revision.knit import KnitRevisionStore
 
265
        versioned_file_store = VersionedFileStore(
 
266
            repo_transport,
 
267
            file_mode=control_files._file_mode,
 
268
            prefixed=False,
 
269
            precious=True,
 
270
            versionedfile_class=knit.KnitVersionedFile,
 
271
            versionedfile_kwargs={'delta':False,
 
272
                                  'factory':knit.KnitPlainFactory(),
 
273
                                 },
 
274
            escaped=True,
 
275
            )
 
276
        return KnitRevisionStore(versioned_file_store)
 
277
 
 
278
    def _get_text_store(self, transport, control_files):
 
279
        """See RepositoryFormat._get_text_store()."""
 
280
        return self._get_versioned_file_store('knits',
 
281
                                  transport,
 
282
                                  control_files,
 
283
                                  versionedfile_class=knit.KnitVersionedFile,
 
284
                                  versionedfile_kwargs={
 
285
                                      'create_parent_dir':True,
 
286
                                      'delay_create':True,
 
287
                                      'dir_mode':control_files._dir_mode,
 
288
                                  },
 
289
                                  escaped=True)
 
290
 
 
291
    def initialize(self, a_bzrdir, shared=False):
 
292
        """Create a knit format 1 repository.
 
293
 
 
294
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
295
            be initialized.
 
296
        :param shared: If true the repository will be initialized as a shared
 
297
                       repository.
 
298
        """
 
299
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
300
        dirs = ['revision-store', 'knits']
 
301
        files = []
 
302
        utf8_files = [('format', self.get_format_string())]
 
303
        
 
304
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
305
        repo_transport = a_bzrdir.get_repository_transport(None)
 
306
        control_files = lockable_files.LockableFiles(repo_transport,
 
307
                                'lock', lockdir.LockDir)
 
308
        control_store = self._get_control_store(repo_transport, control_files)
 
309
        transaction = transactions.WriteTransaction()
 
310
        # trigger a write of the inventory store.
 
311
        control_store.get_weave_or_empty('inventory', transaction)
 
312
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
313
        # the revision id here is irrelevant: it will not be stored, and cannot
 
314
        # already exist.
 
315
        _revision_store.has_revision_id('A', transaction)
 
316
        _revision_store.get_signature_file(transaction)
 
317
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
318
 
 
319
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
320
        """See RepositoryFormat.open().
 
321
        
 
322
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
323
                                    repository at a slightly different url
 
324
                                    than normal. I.e. during 'upgrade'.
 
325
        """
 
326
        if not _found:
 
327
            format = RepositoryFormat.find_format(a_bzrdir)
 
328
            assert format.__class__ ==  self.__class__
 
329
        if _override_transport is not None:
 
330
            repo_transport = _override_transport
 
331
        else:
 
332
            repo_transport = a_bzrdir.get_repository_transport(None)
 
333
        control_files = lockable_files.LockableFiles(repo_transport,
 
334
                                'lock', lockdir.LockDir)
 
335
        text_store = self._get_text_store(repo_transport, control_files)
 
336
        control_store = self._get_control_store(repo_transport, control_files)
 
337
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
338
        return KnitRepository(_format=self,
 
339
                              a_bzrdir=a_bzrdir,
 
340
                              control_files=control_files,
 
341
                              _revision_store=_revision_store,
 
342
                              control_store=control_store,
 
343
                              text_store=text_store)
 
344
 
 
345
 
 
346
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
347
    """Bzr repository knit format 1.
 
348
 
 
349
    This repository format has:
 
350
     - knits for file texts and inventory
 
351
     - hash subdirectory based stores.
 
352
     - knits for revisions and signatures
 
353
     - TextStores for revisions and signatures.
 
354
     - a format marker of its own
 
355
     - an optional 'shared-storage' flag
 
356
     - an optional 'no-working-trees' flag
 
357
     - a LockDir lock
 
358
 
 
359
    This format was introduced in bzr 0.8.
 
360
    """
 
361
 
 
362
    def get_format_string(self):
 
363
        """See RepositoryFormat.get_format_string()."""
 
364
        return "Bazaar-NG Knit Repository Format 1"
 
365
 
 
366
    def get_format_description(self):
 
367
        """See RepositoryFormat.get_format_description()."""
 
368
        return "Knit repository format 1"
 
369
 
 
370
    def check_conversion_target(self, target_format):
 
371
        pass
 
372
 
 
373
 
 
374
class RepositoryFormatKnit2(RepositoryFormatKnit):
 
375
    """Bzr repository knit format 2.
 
376
 
 
377
    THIS FORMAT IS EXPERIMENTAL
 
378
    This repository format has:
 
379
     - knits for file texts and inventory
 
380
     - hash subdirectory based stores.
 
381
     - knits for revisions and signatures
 
382
     - TextStores for revisions and signatures.
 
383
     - a format marker of its own
 
384
     - an optional 'shared-storage' flag
 
385
     - an optional 'no-working-trees' flag
 
386
     - a LockDir lock
 
387
     - Support for recording full info about the tree root
 
388
 
 
389
    """
 
390
    
 
391
    rich_root_data = True
 
392
 
 
393
    def get_format_string(self):
 
394
        """See RepositoryFormat.get_format_string()."""
 
395
        return "Bazaar Knit Repository Format 2\n"
 
396
 
 
397
    def get_format_description(self):
 
398
        """See RepositoryFormat.get_format_description()."""
 
399
        return "Knit repository format 2"
 
400
 
 
401
    def check_conversion_target(self, target_format):
 
402
        if not target_format.rich_root_data:
 
403
            raise errors.BadConversionTarget(
 
404
                'Does not support rich root data.', target_format)
 
405
 
 
406
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
407
        """See RepositoryFormat.open().
 
408
        
 
409
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
410
                                    repository at a slightly different url
 
411
                                    than normal. I.e. during 'upgrade'.
 
412
        """
 
413
        if not _found:
 
414
            format = RepositoryFormat.find_format(a_bzrdir)
 
415
            assert format.__class__ ==  self.__class__
 
416
        if _override_transport is not None:
 
417
            repo_transport = _override_transport
 
418
        else:
 
419
            repo_transport = a_bzrdir.get_repository_transport(None)
 
420
        control_files = lockable_files.LockableFiles(repo_transport, 'lock',
 
421
                                                     lockdir.LockDir)
 
422
        text_store = self._get_text_store(repo_transport, control_files)
 
423
        control_store = self._get_control_store(repo_transport, control_files)
 
424
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
425
        return KnitRepository2(_format=self,
 
426
                               a_bzrdir=a_bzrdir,
 
427
                               control_files=control_files,
 
428
                               _revision_store=_revision_store,
 
429
                               control_store=control_store,
 
430
                               text_store=text_store)