~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
62
62
_deprecation_warning_done = False
63
63
 
64
64
 
 
65
class CommitBuilder(object):
 
66
    """Provides an interface to build up a commit.
 
67
 
 
68
    This allows describing a tree to be committed without needing to 
 
69
    know the internals of the format of the repository.
 
70
    """
 
71
    
 
72
    # all clients should supply tree roots.
 
73
    record_root_entry = True
 
74
    # the default CommitBuilder does not manage trees whose root is versioned.
 
75
    _versioned_root = False
 
76
 
 
77
    def __init__(self, repository, parents, config, timestamp=None, 
 
78
                 timezone=None, committer=None, revprops=None, 
 
79
                 revision_id=None):
 
80
        """Initiate a CommitBuilder.
 
81
 
 
82
        :param repository: Repository to commit to.
 
83
        :param parents: Revision ids of the parents of the new revision.
 
84
        :param config: Configuration to use.
 
85
        :param timestamp: Optional timestamp recorded for commit.
 
86
        :param timezone: Optional timezone for timestamp.
 
87
        :param committer: Optional committer to set for commit.
 
88
        :param revprops: Optional dictionary of revision properties.
 
89
        :param revision_id: Optional revision id.
 
90
        """
 
91
        self._config = config
 
92
 
 
93
        if committer is None:
 
94
            self._committer = self._config.username()
 
95
        else:
 
96
            assert isinstance(committer, basestring), type(committer)
 
97
            self._committer = committer
 
98
 
 
99
        self.new_inventory = Inventory(None)
 
100
        self._new_revision_id = osutils.safe_revision_id(revision_id)
 
101
        self.parents = parents
 
102
        self.repository = repository
 
103
 
 
104
        self._revprops = {}
 
105
        if revprops is not None:
 
106
            self._revprops.update(revprops)
 
107
 
 
108
        if timestamp is None:
 
109
            timestamp = time.time()
 
110
        # Restrict resolution to 1ms
 
111
        self._timestamp = round(timestamp, 3)
 
112
 
 
113
        if timezone is None:
 
114
            self._timezone = osutils.local_time_offset()
 
115
        else:
 
116
            self._timezone = int(timezone)
 
117
 
 
118
        self._generate_revision_if_needed()
 
119
        self._repo_graph = repository.get_graph()
 
120
 
 
121
    def commit(self, message):
 
122
        """Make the actual commit.
 
123
 
 
124
        :return: The revision id of the recorded revision.
 
125
        """
 
126
        rev = _mod_revision.Revision(
 
127
                       timestamp=self._timestamp,
 
128
                       timezone=self._timezone,
 
129
                       committer=self._committer,
 
130
                       message=message,
 
131
                       inventory_sha1=self.inv_sha1,
 
132
                       revision_id=self._new_revision_id,
 
133
                       properties=self._revprops)
 
134
        rev.parent_ids = self.parents
 
135
        self.repository.add_revision(self._new_revision_id, rev,
 
136
            self.new_inventory, self._config)
 
137
        self.repository.commit_write_group()
 
138
        return self._new_revision_id
 
139
 
 
140
    def abort(self):
 
141
        """Abort the commit that is being built.
 
142
        """
 
143
        self.repository.abort_write_group()
 
144
 
 
145
    def revision_tree(self):
 
146
        """Return the tree that was just committed.
 
147
 
 
148
        After calling commit() this can be called to get a RevisionTree
 
149
        representing the newly committed tree. This is preferred to
 
150
        calling Repository.revision_tree() because that may require
 
151
        deserializing the inventory, while we already have a copy in
 
152
        memory.
 
153
        """
 
154
        return RevisionTree(self.repository, self.new_inventory,
 
155
                            self._new_revision_id)
 
156
 
 
157
    def finish_inventory(self):
 
158
        """Tell the builder that the inventory is finished."""
 
159
        if self.new_inventory.root is None:
 
160
            symbol_versioning.warn('Root entry should be supplied to'
 
161
                ' record_entry_contents, as of bzr 0.10.',
 
162
                 DeprecationWarning, stacklevel=2)
 
163
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
164
        self.new_inventory.revision_id = self._new_revision_id
 
165
        self.inv_sha1 = self.repository.add_inventory(
 
166
            self._new_revision_id,
 
167
            self.new_inventory,
 
168
            self.parents
 
169
            )
 
170
 
 
171
    def _gen_revision_id(self):
 
172
        """Return new revision-id."""
 
173
        return generate_ids.gen_revision_id(self._config.username(),
 
174
                                            self._timestamp)
 
175
 
 
176
    def _generate_revision_if_needed(self):
 
177
        """Create a revision id if None was supplied.
 
178
        
 
179
        If the repository can not support user-specified revision ids
 
180
        they should override this function and raise CannotSetRevisionId
 
181
        if _new_revision_id is not None.
 
182
 
 
183
        :raises: CannotSetRevisionId
 
184
        """
 
185
        if self._new_revision_id is None:
 
186
            self._new_revision_id = self._gen_revision_id()
 
187
            self.random_revid = True
 
188
        else:
 
189
            self.random_revid = False
 
190
 
 
191
    def _check_root(self, ie, parent_invs, tree):
 
192
        """Helper for record_entry_contents.
 
193
 
 
194
        :param ie: An entry being added.
 
195
        :param parent_invs: The inventories of the parent revisions of the
 
196
            commit.
 
197
        :param tree: The tree that is being committed.
 
198
        """
 
199
        # In this revision format, root entries have no knit or weave When
 
200
        # serializing out to disk and back in root.revision is always
 
201
        # _new_revision_id
 
202
        ie.revision = self._new_revision_id
 
203
 
 
204
    def _get_delta(self, ie, basis_inv, path):
 
205
        """Get a delta against the basis inventory for ie."""
 
206
        if ie.file_id not in basis_inv:
 
207
            # add
 
208
            return (None, path, ie.file_id, ie)
 
209
        elif ie != basis_inv[ie.file_id]:
 
210
            # common but altered
 
211
            # TODO: avoid tis id2path call.
 
212
            return (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
 
213
        else:
 
214
            # common, unaltered
 
215
            return None
 
216
 
 
217
    def record_entry_contents(self, ie, parent_invs, path, tree,
 
218
        content_summary):
 
219
        """Record the content of ie from tree into the commit if needed.
 
220
 
 
221
        Side effect: sets ie.revision when unchanged
 
222
 
 
223
        :param ie: An inventory entry present in the commit.
 
224
        :param parent_invs: The inventories of the parent revisions of the
 
225
            commit.
 
226
        :param path: The path the entry is at in the tree.
 
227
        :param tree: The tree which contains this entry and should be used to 
 
228
            obtain content.
 
229
        :param content_summary: Summary data from the tree about the paths
 
230
            content - stat, length, exec, sha/link target. This is only
 
231
            accessed when the entry has a revision of None - that is when it is
 
232
            a candidate to commit.
 
233
        :return: A tuple (change_delta, version_recorded). change_delta is 
 
234
            an inventory_delta change for this entry against the basis tree of
 
235
            the commit, or None if no change occured against the basis tree.
 
236
            version_recorded is True if a new version of the entry has been
 
237
            recorded. For instance, committing a merge where a file was only
 
238
            changed on the other side will return (delta, False).
 
239
        """
 
240
        if self.new_inventory.root is None:
 
241
            if ie.parent_id is not None:
 
242
                raise errors.RootMissing()
 
243
            self._check_root(ie, parent_invs, tree)
 
244
        if ie.revision is None:
 
245
            kind = content_summary[0]
 
246
        else:
 
247
            # ie is carried over from a prior commit
 
248
            kind = ie.kind
 
249
        # XXX: repository specific check for nested tree support goes here - if
 
250
        # the repo doesn't want nested trees we skip it ?
 
251
        if (kind == 'tree-reference' and
 
252
            not self.repository._format.supports_tree_reference):
 
253
            # mismatch between commit builder logic and repository:
 
254
            # this needs the entry creation pushed down into the builder.
 
255
            raise NotImplementedError('Missing repository subtree support.')
 
256
        # transitional assert only, will remove before release.
 
257
        assert ie.kind == kind
 
258
        self.new_inventory.add(ie)
 
259
 
 
260
        # TODO: slow, take it out of the inner loop.
 
261
        try:
 
262
            basis_inv = parent_invs[0]
 
263
        except IndexError:
 
264
            basis_inv = Inventory(root_id=None)
 
265
 
 
266
        # ie.revision is always None if the InventoryEntry is considered
 
267
        # for committing. We may record the previous parents revision if the
 
268
        # content is actually unchanged against a sole head.
 
269
        if ie.revision is not None:
 
270
            if self._versioned_root or path != '':
 
271
                # not considered for commit
 
272
                delta = None
 
273
            else:
 
274
                # repositories that do not version the root set the root's
 
275
                # revision to the new commit even when no change occurs, and
 
276
                # this masks when a change may have occurred against the basis,
 
277
                # so calculate if one happened.
 
278
                if ie.file_id not in basis_inv:
 
279
                    # add
 
280
                    delta = (None, path, ie.file_id, ie)
 
281
                else:
 
282
                    basis_id = basis_inv[ie.file_id]
 
283
                    if basis_id.name != '':
 
284
                        # not the root
 
285
                        delta = (basis_inv.id2path(ie.file_id), path,
 
286
                            ie.file_id, ie)
 
287
                    else:
 
288
                        # common, unaltered
 
289
                        delta = None
 
290
            # not considered for commit, OR, for non-rich-root 
 
291
            return delta, ie.revision == self._new_revision_id and (path != '' or
 
292
                self._versioned_root)
 
293
 
 
294
        # XXX: Friction: parent_candidates should return a list not a dict
 
295
        #      so that we don't have to walk the inventories again.
 
296
        parent_candiate_entries = ie.parent_candidates(parent_invs)
 
297
        head_set = self._repo_graph.heads(parent_candiate_entries.keys())
 
298
        heads = []
 
299
        for inv in parent_invs:
 
300
            if ie.file_id in inv:
 
301
                old_rev = inv[ie.file_id].revision
 
302
                if old_rev in head_set:
 
303
                    heads.append(inv[ie.file_id].revision)
 
304
                    head_set.remove(inv[ie.file_id].revision)
 
305
 
 
306
        store = False
 
307
        # now we check to see if we need to write a new record to the
 
308
        # file-graph.
 
309
        # We write a new entry unless there is one head to the ancestors, and
 
310
        # the kind-derived content is unchanged.
 
311
 
 
312
        # Cheapest check first: no ancestors, or more the one head in the
 
313
        # ancestors, we write a new node.
 
314
        if len(heads) != 1:
 
315
            store = True
 
316
        if not store:
 
317
            # There is a single head, look it up for comparison
 
318
            parent_entry = parent_candiate_entries[heads[0]]
 
319
            # if the non-content specific data has changed, we'll be writing a
 
320
            # node:
 
321
            if (parent_entry.parent_id != ie.parent_id or
 
322
                parent_entry.name != ie.name):
 
323
                store = True
 
324
        # now we need to do content specific checks:
 
325
        if not store:
 
326
            # if the kind changed the content obviously has
 
327
            if kind != parent_entry.kind:
 
328
                store = True
 
329
        if kind == 'file':
 
330
            if not store:
 
331
                if (# if the file length changed we have to store:
 
332
                    parent_entry.text_size != content_summary[1] or
 
333
                    # if the exec bit has changed we have to store:
 
334
                    parent_entry.executable != content_summary[2]):
 
335
                    store = True
 
336
                elif parent_entry.text_sha1 == content_summary[3]:
 
337
                    # all meta and content is unchanged (using a hash cache
 
338
                    # hit to check the sha)
 
339
                    ie.revision = parent_entry.revision
 
340
                    ie.text_size = parent_entry.text_size
 
341
                    ie.text_sha1 = parent_entry.text_sha1
 
342
                    ie.executable = parent_entry.executable
 
343
                    return self._get_delta(ie, basis_inv, path), False
 
344
                else:
 
345
                    # Either there is only a hash change(no hash cache entry,
 
346
                    # or same size content change), or there is no change on
 
347
                    # this file at all.
 
348
                    # Provide the parent's hash to the store layer, so that the
 
349
                    # content is unchanged we will not store a new node.
 
350
                    nostore_sha = parent_entry.text_sha1
 
351
            if store:
 
352
                # We want to record a new node regardless of the presence or
 
353
                # absence of a content change in the file.
 
354
                nostore_sha = None
 
355
            ie.executable = content_summary[2]
 
356
            lines = tree.get_file(ie.file_id, path).readlines()
 
357
            try:
 
358
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
 
359
                    ie.file_id, lines, heads, nostore_sha)
 
360
            except errors.ExistingContent:
 
361
                # Turns out that the file content was unchanged, and we were
 
362
                # only going to store a new node if it was changed. Carry over
 
363
                # the entry.
 
364
                ie.revision = parent_entry.revision
 
365
                ie.text_size = parent_entry.text_size
 
366
                ie.text_sha1 = parent_entry.text_sha1
 
367
                ie.executable = parent_entry.executable
 
368
                return self._get_delta(ie, basis_inv, path), False
 
369
        elif kind == 'directory':
 
370
            if not store:
 
371
                # all data is meta here, nothing specific to directory, so
 
372
                # carry over:
 
373
                ie.revision = parent_entry.revision
 
374
                return self._get_delta(ie, basis_inv, path), False
 
375
            lines = []
 
376
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
377
        elif kind == 'symlink':
 
378
            current_link_target = content_summary[3]
 
379
            if not store:
 
380
                # symlink target is not generic metadata, check if it has
 
381
                # changed.
 
382
                if current_link_target != parent_entry.symlink_target:
 
383
                    store = True
 
384
            if not store:
 
385
                # unchanged, carry over.
 
386
                ie.revision = parent_entry.revision
 
387
                ie.symlink_target = parent_entry.symlink_target
 
388
                return self._get_delta(ie, basis_inv, path), False
 
389
            ie.symlink_target = current_link_target
 
390
            lines = []
 
391
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
392
        elif kind == 'tree-reference':
 
393
            if not store:
 
394
                if content_summary[3] != parent_entry.reference_revision:
 
395
                    store = True
 
396
            if not store:
 
397
                # unchanged, carry over.
 
398
                ie.reference_revision = parent_entry.reference_revision
 
399
                ie.revision = parent_entry.revision
 
400
                return self._get_delta(ie, basis_inv, path), False
 
401
            ie.reference_revision = content_summary[3]
 
402
            lines = []
 
403
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
404
        else:
 
405
            raise NotImplementedError('unknown kind')
 
406
        ie.revision = self._new_revision_id
 
407
        return self._get_delta(ie, basis_inv, path), True
 
408
 
 
409
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
410
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
411
            file_id, self.repository.get_transaction())
 
412
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
 
413
        # than add_lines, and allows committing when a parent is ghosted for
 
414
        # some reason.
 
415
        # Note: as we read the content directly from the tree, we know its not
 
416
        # been turned into unicode or badly split - but a broken tree
 
417
        # implementation could give us bad output from readlines() so this is
 
418
        # not a guarantee of safety. What would be better is always checking
 
419
        # the content during test suite execution. RBC 20070912
 
420
        try:
 
421
            return versionedfile.add_lines_with_ghosts(
 
422
                self._new_revision_id, parents, new_lines,
 
423
                nostore_sha=nostore_sha, random_id=self.random_revid,
 
424
                check_content=False)[0:2]
 
425
        finally:
 
426
            versionedfile.clear_cache()
 
427
 
 
428
 
 
429
class RootCommitBuilder(CommitBuilder):
 
430
    """This commitbuilder actually records the root id"""
 
431
    
 
432
    # the root entry gets versioned properly by this builder.
 
433
    _versioned_root = True
 
434
 
 
435
    def _check_root(self, ie, parent_invs, tree):
 
436
        """Helper for record_entry_contents.
 
437
 
 
438
        :param ie: An entry being added.
 
439
        :param parent_invs: The inventories of the parent revisions of the
 
440
            commit.
 
441
        :param tree: The tree that is being committed.
 
442
        """
 
443
 
 
444
 
65
445
######################################################################
66
446
# Repositories
67
447
 
77
457
    remote) disk.
78
458
    """
79
459
 
 
460
    # What class to use for a CommitBuilder. Often its simpler to change this
 
461
    # in a Repository class subclass rather than to override
 
462
    # get_commit_builder.
 
463
    _commit_builder_class = CommitBuilder
 
464
    # The search regex used by xml based repositories to determine what things
 
465
    # where changed in a single commit.
80
466
    _file_ids_altered_regex = lazy_regex.lazy_compile(
81
467
        r'file_id="(?P<file_id>[^"]+)"'
82
 
        r'.*revision="(?P<revision_id>[^"]+)"'
 
468
        r'.* revision="(?P<revision_id>[^"]+)"'
83
469
        )
84
470
 
85
471
    def abort_write_group(self):
121
507
            "Mismatch between inventory revision" \
122
508
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
123
509
        assert inv.root is not None
124
 
        inv_text = self.serialise_inventory(inv)
125
 
        inv_sha1 = osutils.sha_string(inv_text)
126
 
        inv_vf = self.control_weaves.get_weave('inventory',
127
 
                                               self.get_transaction())
128
 
        self._inventory_add_lines(inv_vf, revision_id, parents,
129
 
                                  osutils.split_lines(inv_text))
130
 
        return inv_sha1
 
510
        inv_lines = self._serialise_inventory_to_lines(inv)
 
511
        inv_vf = self.get_inventory_weave()
 
512
        return self._inventory_add_lines(inv_vf, revision_id, parents,
 
513
            inv_lines, check_content=False)
131
514
 
132
 
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
 
515
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
 
516
        check_content=True):
 
517
        """Store lines in inv_vf and return the sha1 of the inventory."""
133
518
        final_parents = []
134
519
        for parent in parents:
135
520
            if parent in inv_vf:
136
521
                final_parents.append(parent)
137
 
 
138
 
        inv_vf.add_lines(revision_id, final_parents, lines)
 
522
        return inv_vf.add_lines(revision_id, final_parents, lines,
 
523
            check_content=check_content)[0]
139
524
 
140
525
    @needs_write_lock
141
526
    def add_revision(self, revision_id, rev, inv=None, config=None):
174
559
        self._revision_store._add_revision(revision, StringIO(text),
175
560
                                           self.get_transaction())
176
561
 
177
 
    @needs_read_lock
178
 
    def _all_possible_ids(self):
179
 
        """Return all the possible revisions that we could find."""
180
 
        return self.get_inventory_weave().versions()
181
 
 
182
562
    def all_revision_ids(self):
183
563
        """Returns a list of all the revision ids in the repository. 
184
564
 
186
566
        reachable from a particular revision, and ignore any other revisions
187
567
        that might be present.  There is no direct replacement method.
188
568
        """
 
569
        if 'evil' in debug.debug_flags:
 
570
            mutter_callsite(2, "all_revision_ids is linear with history.")
189
571
        return self._all_revision_ids()
190
572
 
191
 
    @needs_read_lock
192
573
    def _all_revision_ids(self):
193
574
        """Returns a list of all the revision ids in the repository. 
194
575
 
195
576
        These are in as much topological order as the underlying store can 
196
 
        present: for weaves ghosts may lead to a lack of correctness until
197
 
        the reweave updates the parents list.
 
577
        present.
198
578
        """
199
 
        if self._revision_store.text_store.listable():
200
 
            return self._revision_store.all_revision_ids(self.get_transaction())
201
 
        result = self._all_possible_ids()
202
 
        # TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
203
 
        #       ids. (It should, since _revision_store's API should change to
204
 
        #       return utf8 revision_ids)
205
 
        return self._eliminate_revisions_not_present(result)
 
579
        raise NotImplementedError(self._all_revision_ids)
206
580
 
207
581
    def break_lock(self):
208
582
        """Break a lock if one is present from another instance.
257
631
        # on whether escaping is required.
258
632
        self._warn_if_deprecated()
259
633
        self._write_group = None
 
634
        self.base = control_files._transport.base
260
635
 
261
636
    def __repr__(self):
262
 
        return '%s(%r)' % (self.__class__.__name__, 
263
 
                           self.bzrdir.transport.base)
 
637
        return '%s(%r)' % (self.__class__.__name__,
 
638
                           self.base)
264
639
 
265
640
    def has_same_location(self, other):
266
641
        """Returns a boolean indicating if this repository is at the same
450
825
        """
451
826
        if self._write_group is not self.get_transaction():
452
827
            # has an unlock or relock occured ?
453
 
            raise errors.BzrError('mismatched lock context and write group.')
 
828
            raise errors.BzrError('mismatched lock context %r and '
 
829
                'write group %r.' %
 
830
                (self.get_transaction(), self._write_group))
454
831
        self._commit_write_group()
455
832
        self._write_group = None
456
833
 
469
846
        If revision_id is None all content is copied.
470
847
        """
471
848
        revision_id = osutils.safe_revision_id(revision_id)
 
849
        # fast path same-url fetch operations
 
850
        if self.has_same_location(source):
 
851
            # check that last_revision is in 'from' and then return a
 
852
            # no-operation.
 
853
            if (revision_id is not None and
 
854
                not _mod_revision.is_null(revision_id)):
 
855
                self.get_revision(revision_id)
 
856
            return 0, []
472
857
        inter = InterRepository.get(source, self)
473
858
        try:
474
859
            return inter.fetch(revision_id=revision_id, pb=pb)
478
863
    def create_bundle(self, target, base, fileobj, format=None):
479
864
        return serializer.write_bundle(self, target, base, fileobj, format)
480
865
 
481
 
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
482
 
                           timezone=None, committer=None, revprops=None, 
 
866
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
867
                           timezone=None, committer=None, revprops=None,
483
868
                           revision_id=None):
484
869
        """Obtain a CommitBuilder for this repository.
485
870
        
493
878
        :param revision_id: Optional revision id.
494
879
        """
495
880
        revision_id = osutils.safe_revision_id(revision_id)
496
 
        result =_CommitBuilder(self, parents, config, timestamp, timezone,
497
 
                              committer, revprops, revision_id)
 
881
        result = self._commit_builder_class(self, parents, config,
 
882
            timestamp, timezone, committer, revprops, revision_id)
498
883
        self.start_write_group()
499
884
        return result
500
885
 
577
962
    def has_revision(self, revision_id):
578
963
        """True if this repository has a copy of the revision."""
579
964
        if 'evil' in debug.debug_flags:
580
 
            mutter_callsite(2, "has_revision is a LBYL symptom.")
 
965
            mutter_callsite(3, "has_revision is a LBYL symptom.")
581
966
        revision_id = osutils.safe_revision_id(revision_id)
582
967
        return self._revision_store.has_revision_id(revision_id,
583
968
                                                    self.get_transaction())
584
969
 
585
970
    @needs_read_lock
 
971
    def get_revision(self, revision_id):
 
972
        """Return the Revision object for a named revision."""
 
973
        return self.get_revisions([revision_id])[0]
 
974
 
 
975
    @needs_read_lock
586
976
    def get_revision_reconcile(self, revision_id):
587
977
        """'reconcile' helper routine that allows access to a revision always.
588
978
        
591
981
        be used by reconcile, or reconcile-alike commands that are correcting
592
982
        or testing the revision graph.
593
983
        """
594
 
        if not revision_id or not isinstance(revision_id, basestring):
595
 
            raise errors.InvalidRevisionId(revision_id=revision_id,
596
 
                                           branch=self)
597
 
        return self.get_revisions([revision_id])[0]
 
984
        return self._get_revisions([revision_id])[0]
598
985
 
599
986
    @needs_read_lock
600
987
    def get_revisions(self, revision_ids):
 
988
        """Get many revisions at once."""
 
989
        return self._get_revisions(revision_ids)
 
990
 
 
991
    @needs_read_lock
 
992
    def _get_revisions(self, revision_ids):
 
993
        """Core work logic to get many revisions without sanity checks."""
601
994
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
 
995
        for rev_id in revision_ids:
 
996
            if not rev_id or not isinstance(rev_id, basestring):
 
997
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
602
998
        revs = self._revision_store.get_revisions(revision_ids,
603
999
                                                  self.get_transaction())
604
1000
        for rev in revs:
621
1017
        return rev_tmp.getvalue()
622
1018
 
623
1019
    @needs_read_lock
624
 
    def get_revision(self, revision_id):
625
 
        """Return the Revision object for a named revision"""
626
 
        # TODO: jam 20070210 get_revision_reconcile should do this for us
627
 
        revision_id = osutils.safe_revision_id(revision_id)
628
 
        r = self.get_revision_reconcile(revision_id)
629
 
        # weave corruption can lead to absent revision markers that should be
630
 
        # present.
631
 
        # the following test is reasonably cheap (it needs a single weave read)
632
 
        # and the weave is cached in read transactions. In write transactions
633
 
        # it is not cached but typically we only read a small number of
634
 
        # revisions. For knits when they are introduced we will probably want
635
 
        # to ensure that caching write transactions are in use.
636
 
        inv = self.get_inventory_weave()
637
 
        self._check_revision_parents(r, inv)
638
 
        return r
639
 
 
640
 
    @needs_read_lock
641
1020
    def get_deltas_for_revisions(self, revisions):
642
1021
        """Produce a generator of revision deltas.
643
1022
        
668
1047
        r = self.get_revision(revision_id)
669
1048
        return list(self.get_deltas_for_revisions([r]))[0]
670
1049
 
671
 
    def _check_revision_parents(self, revision, inventory):
672
 
        """Private to Repository and Fetch.
673
 
        
674
 
        This checks the parentage of revision in an inventory weave for 
675
 
        consistency and is only applicable to inventory-weave-for-ancestry
676
 
        using repository formats & fetchers.
677
 
        """
678
 
        weave_parents = inventory.get_parents(revision.revision_id)
679
 
        weave_names = inventory.versions()
680
 
        for parent_id in revision.parent_ids:
681
 
            if parent_id in weave_names:
682
 
                # this parent must not be a ghost.
683
 
                if not parent_id in weave_parents:
684
 
                    # but it is a ghost
685
 
                    raise errors.CorruptRepository(self)
686
 
 
687
1050
    @needs_write_lock
688
1051
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
689
1052
        revision_id = osutils.safe_revision_id(revision_id)
874
1237
    def serialise_inventory(self, inv):
875
1238
        return self._serializer.write_inventory_to_string(inv)
876
1239
 
 
1240
    def _serialise_inventory_to_lines(self, inv):
 
1241
        return self._serializer.write_inventory_to_lines(inv)
 
1242
 
877
1243
    def get_serializer_format(self):
878
1244
        return self._serializer.format_num
879
1245
 
899
1265
    @needs_read_lock
900
1266
    def get_revision_graph(self, revision_id=None):
901
1267
        """Return a dictionary containing the revision graph.
902
 
        
 
1268
 
 
1269
        NB: This method should not be used as it accesses the entire graph all
 
1270
        at once, which is much more data than most operations should require.
 
1271
 
903
1272
        :param revision_id: The revision_id to get a graph from. If None, then
904
1273
        the entire revision graph is returned. This is a deprecated mode of
905
1274
        operation and will be removed in the future.
906
1275
        :return: a dictionary of revision_id->revision_parents_list.
907
1276
        """
908
 
        if 'evil' in debug.debug_flags:
909
 
            mutter_callsite(2,
910
 
                "get_revision_graph scales with size of history.")
911
 
        # special case NULL_REVISION
912
 
        if revision_id == _mod_revision.NULL_REVISION:
913
 
            return {}
914
 
        revision_id = osutils.safe_revision_id(revision_id)
915
 
        a_weave = self.get_inventory_weave()
916
 
        all_revisions = self._eliminate_revisions_not_present(
917
 
                                a_weave.versions())
918
 
        entire_graph = dict([(node, tuple(a_weave.get_parents(node))) for 
919
 
                             node in all_revisions])
920
 
        if revision_id is None:
921
 
            return entire_graph
922
 
        elif revision_id not in entire_graph:
923
 
            raise errors.NoSuchRevision(self, revision_id)
924
 
        else:
925
 
            # add what can be reached from revision_id
926
 
            result = {}
927
 
            pending = set([revision_id])
928
 
            while len(pending) > 0:
929
 
                node = pending.pop()
930
 
                result[node] = entire_graph[node]
931
 
                for revision_id in result[node]:
932
 
                    if revision_id not in result:
933
 
                        pending.add(revision_id)
934
 
            return result
 
1277
        raise NotImplementedError(self.get_revision_graph)
935
1278
 
936
1279
    @needs_read_lock
937
1280
    def get_revision_graph_with_ghosts(self, revision_ids=None):
941
1284
        :return: a Graph object with the graph reachable from revision_ids.
942
1285
        """
943
1286
        if 'evil' in debug.debug_flags:
944
 
            mutter_callsite(2,
 
1287
            mutter_callsite(3,
945
1288
                "get_revision_graph_with_ghosts scales with size of history.")
946
1289
        result = deprecated_graph.Graph()
947
1290
        if not revision_ids:
1593
1936
    'bzrlib.repofmt.weaverepo',
1594
1937
    'RepositoryFormat7'
1595
1938
    )
 
1939
 
1596
1940
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1597
1941
# default control directory format
1598
 
 
1599
1942
format_registry.register_lazy(
1600
1943
    'Bazaar-NG Knit Repository Format 1',
1601
1944
    'bzrlib.repofmt.knitrepo',
1669
2012
        # that we've decided we need.
1670
2013
        return [rev_id for rev_id in source_ids if rev_id in result_set]
1671
2014
 
 
2015
    @staticmethod
 
2016
    def _same_model(source, target):
 
2017
        """True if source and target have the same data representation."""
 
2018
        if source.supports_rich_root() != target.supports_rich_root():
 
2019
            return False
 
2020
        if source._serializer != target._serializer:
 
2021
            return False
 
2022
        return True
 
2023
 
1672
2024
 
1673
2025
class InterSameDataRepository(InterRepository):
1674
2026
    """Code for converting between repositories that represent the same data.
1678
2030
 
1679
2031
    @classmethod
1680
2032
    def _get_repo_format_to_test(self):
1681
 
        """Repository format for testing with."""
1682
 
        return RepositoryFormat.get_default_format()
 
2033
        """Repository format for testing with.
 
2034
        
 
2035
        InterSameData can pull from subtree to subtree and from non-subtree to
 
2036
        non-subtree, so we test this with the richest repository format.
 
2037
        """
 
2038
        from bzrlib.repofmt import knitrepo
 
2039
        return knitrepo.RepositoryFormatKnit3()
1683
2040
 
1684
2041
    @staticmethod
1685
2042
    def is_compatible(source, target):
1686
 
        if source.supports_rich_root() != target.supports_rich_root():
1687
 
            return False
1688
 
        if source._serializer != target._serializer:
1689
 
            return False
1690
 
        return True
 
2043
        return InterRepository._same_model(source, target)
1691
2044
 
1692
2045
    @needs_write_lock
1693
2046
    def copy_content(self, revision_id=None):
1732
2085
 
1733
2086
 
1734
2087
class InterWeaveRepo(InterSameDataRepository):
1735
 
    """Optimised code paths between Weave based repositories."""
 
2088
    """Optimised code paths between Weave based repositories.
 
2089
    
 
2090
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
 
2091
    implemented lazy inter-object optimisation.
 
2092
    """
1736
2093
 
1737
2094
    @classmethod
1738
2095
    def _get_repo_format_to_test(self):
1866
2223
        could lead to confusing results, and there is no need to be 
1867
2224
        overly general.
1868
2225
        """
1869
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
 
2226
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
1870
2227
        try:
1871
 
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1872
 
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
2228
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
2229
                isinstance(target._format, RepositoryFormatKnit))
1873
2230
        except AttributeError:
1874
2231
            return False
 
2232
        return are_knits and InterRepository._same_model(source, target)
1875
2233
 
1876
2234
    @needs_write_lock
1877
2235
    def fetch(self, revision_id=None, pb=None):
1895
2253
            assert source_ids[0] is None
1896
2254
            source_ids.pop(0)
1897
2255
        else:
1898
 
            source_ids = self.source._all_possible_ids()
 
2256
            source_ids = self.source.all_revision_ids()
1899
2257
        source_ids_set = set(source_ids)
1900
2258
        # source_ids is the worst possible case we may need to pull.
1901
2259
        # now we want to filter source_ids against what we actually
1902
2260
        # have in target, but don't try to check for existence where we know
1903
2261
        # we do not have a revision as that would be pointless.
1904
 
        target_ids = set(self.target._all_possible_ids())
 
2262
        target_ids = set(self.target.all_revision_ids())
1905
2263
        possibly_present_revisions = target_ids.intersection(source_ids_set)
1906
2264
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1907
2265
        required_revisions = source_ids_set.difference(actually_present_revisions)
2130
2488
        self.pb.update(message, self.count, self.total)
2131
2489
 
2132
2490
 
2133
 
class CommitBuilder(object):
2134
 
    """Provides an interface to build up a commit.
2135
 
 
2136
 
    This allows describing a tree to be committed without needing to 
2137
 
    know the internals of the format of the repository.
2138
 
    """
2139
 
    
2140
 
    record_root_entry = False
2141
 
    def __init__(self, repository, parents, config, timestamp=None, 
2142
 
                 timezone=None, committer=None, revprops=None, 
2143
 
                 revision_id=None):
2144
 
        """Initiate a CommitBuilder.
2145
 
 
2146
 
        :param repository: Repository to commit to.
2147
 
        :param parents: Revision ids of the parents of the new revision.
2148
 
        :param config: Configuration to use.
2149
 
        :param timestamp: Optional timestamp recorded for commit.
2150
 
        :param timezone: Optional timezone for timestamp.
2151
 
        :param committer: Optional committer to set for commit.
2152
 
        :param revprops: Optional dictionary of revision properties.
2153
 
        :param revision_id: Optional revision id.
2154
 
        """
2155
 
        self._config = config
2156
 
 
2157
 
        if committer is None:
2158
 
            self._committer = self._config.username()
2159
 
        else:
2160
 
            assert isinstance(committer, basestring), type(committer)
2161
 
            self._committer = committer
2162
 
 
2163
 
        self.new_inventory = Inventory(None)
2164
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
2165
 
        self.parents = parents
2166
 
        self.repository = repository
2167
 
 
2168
 
        self._revprops = {}
2169
 
        if revprops is not None:
2170
 
            self._revprops.update(revprops)
2171
 
 
2172
 
        if timestamp is None:
2173
 
            timestamp = time.time()
2174
 
        # Restrict resolution to 1ms
2175
 
        self._timestamp = round(timestamp, 3)
2176
 
 
2177
 
        if timezone is None:
2178
 
            self._timezone = osutils.local_time_offset()
2179
 
        else:
2180
 
            self._timezone = int(timezone)
2181
 
 
2182
 
        self._generate_revision_if_needed()
2183
 
 
2184
 
    def commit(self, message):
2185
 
        """Make the actual commit.
2186
 
 
2187
 
        :return: The revision id of the recorded revision.
2188
 
        """
2189
 
        rev = _mod_revision.Revision(
2190
 
                       timestamp=self._timestamp,
2191
 
                       timezone=self._timezone,
2192
 
                       committer=self._committer,
2193
 
                       message=message,
2194
 
                       inventory_sha1=self.inv_sha1,
2195
 
                       revision_id=self._new_revision_id,
2196
 
                       properties=self._revprops)
2197
 
        rev.parent_ids = self.parents
2198
 
        self.repository.add_revision(self._new_revision_id, rev,
2199
 
            self.new_inventory, self._config)
2200
 
        self.repository.commit_write_group()
2201
 
        return self._new_revision_id
2202
 
 
2203
 
    def abort(self):
2204
 
        """Abort the commit that is being built.
2205
 
        """
2206
 
        self.repository.abort_write_group()
2207
 
 
2208
 
    def revision_tree(self):
2209
 
        """Return the tree that was just committed.
2210
 
 
2211
 
        After calling commit() this can be called to get a RevisionTree
2212
 
        representing the newly committed tree. This is preferred to
2213
 
        calling Repository.revision_tree() because that may require
2214
 
        deserializing the inventory, while we already have a copy in
2215
 
        memory.
2216
 
        """
2217
 
        return RevisionTree(self.repository, self.new_inventory,
2218
 
                            self._new_revision_id)
2219
 
 
2220
 
    def finish_inventory(self):
2221
 
        """Tell the builder that the inventory is finished."""
2222
 
        if self.new_inventory.root is None:
2223
 
            symbol_versioning.warn('Root entry should be supplied to'
2224
 
                ' record_entry_contents, as of bzr 0.10.',
2225
 
                 DeprecationWarning, stacklevel=2)
2226
 
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
2227
 
        self.new_inventory.revision_id = self._new_revision_id
2228
 
        self.inv_sha1 = self.repository.add_inventory(
2229
 
            self._new_revision_id,
2230
 
            self.new_inventory,
2231
 
            self.parents
2232
 
            )
2233
 
 
2234
 
    def _gen_revision_id(self):
2235
 
        """Return new revision-id."""
2236
 
        return generate_ids.gen_revision_id(self._config.username(),
2237
 
                                            self._timestamp)
2238
 
 
2239
 
    def _generate_revision_if_needed(self):
2240
 
        """Create a revision id if None was supplied.
2241
 
        
2242
 
        If the repository can not support user-specified revision ids
2243
 
        they should override this function and raise CannotSetRevisionId
2244
 
        if _new_revision_id is not None.
2245
 
 
2246
 
        :raises: CannotSetRevisionId
2247
 
        """
2248
 
        if self._new_revision_id is None:
2249
 
            self._new_revision_id = self._gen_revision_id()
2250
 
 
2251
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
2252
 
        """Record the content of ie from tree into the commit if needed.
2253
 
 
2254
 
        Side effect: sets ie.revision when unchanged
2255
 
 
2256
 
        :param ie: An inventory entry present in the commit.
2257
 
        :param parent_invs: The inventories of the parent revisions of the
2258
 
            commit.
2259
 
        :param path: The path the entry is at in the tree.
2260
 
        :param tree: The tree which contains this entry and should be used to 
2261
 
        obtain content.
2262
 
        """
2263
 
        if self.new_inventory.root is None and ie.parent_id is not None:
2264
 
            symbol_versioning.warn('Root entry should be supplied to'
2265
 
                ' record_entry_contents, as of bzr 0.10.',
2266
 
                 DeprecationWarning, stacklevel=2)
2267
 
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2268
 
                                       '', tree)
2269
 
        self.new_inventory.add(ie)
2270
 
 
2271
 
        # ie.revision is always None if the InventoryEntry is considered
2272
 
        # for committing. ie.snapshot will record the correct revision 
2273
 
        # which may be the sole parent if it is untouched.
2274
 
        if ie.revision is not None:
2275
 
            return
2276
 
 
2277
 
        # In this revision format, root entries have no knit or weave
2278
 
        if ie is self.new_inventory.root:
2279
 
            # When serializing out to disk and back in
2280
 
            # root.revision is always _new_revision_id
2281
 
            ie.revision = self._new_revision_id
2282
 
            return
2283
 
        previous_entries = ie.find_previous_heads(
2284
 
            parent_invs,
2285
 
            self.repository.weave_store,
2286
 
            self.repository.get_transaction())
2287
 
        # we are creating a new revision for ie in the history store
2288
 
        # and inventory.
2289
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2290
 
 
2291
 
    def modified_directory(self, file_id, file_parents):
2292
 
        """Record the presence of a symbolic link.
2293
 
 
2294
 
        :param file_id: The file_id of the link to record.
2295
 
        :param file_parents: The per-file parent revision ids.
2296
 
        """
2297
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2298
 
 
2299
 
    def modified_reference(self, file_id, file_parents):
2300
 
        """Record the modification of a reference.
2301
 
 
2302
 
        :param file_id: The file_id of the link to record.
2303
 
        :param file_parents: The per-file parent revision ids.
2304
 
        """
2305
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2306
 
    
2307
 
    def modified_file_text(self, file_id, file_parents,
2308
 
                           get_content_byte_lines, text_sha1=None,
2309
 
                           text_size=None):
2310
 
        """Record the text of file file_id
2311
 
 
2312
 
        :param file_id: The file_id of the file to record the text of.
2313
 
        :param file_parents: The per-file parent revision ids.
2314
 
        :param get_content_byte_lines: A callable which will return the byte
2315
 
            lines for the file.
2316
 
        :param text_sha1: Optional SHA1 of the file contents.
2317
 
        :param text_size: Optional size of the file contents.
2318
 
        """
2319
 
        # mutter('storing text of file {%s} in revision {%s} into %r',
2320
 
        #        file_id, self._new_revision_id, self.repository.weave_store)
2321
 
        # special case to avoid diffing on renames or 
2322
 
        # reparenting
2323
 
        if (len(file_parents) == 1
2324
 
            and text_sha1 == file_parents.values()[0].text_sha1
2325
 
            and text_size == file_parents.values()[0].text_size):
2326
 
            previous_ie = file_parents.values()[0]
2327
 
            versionedfile = self.repository.weave_store.get_weave(file_id, 
2328
 
                self.repository.get_transaction())
2329
 
            versionedfile.clone_text(self._new_revision_id, 
2330
 
                previous_ie.revision, file_parents.keys())
2331
 
            return text_sha1, text_size
2332
 
        else:
2333
 
            new_lines = get_content_byte_lines()
2334
 
            # TODO: Rather than invoking sha_strings here, _add_text_to_weave
2335
 
            # should return the SHA1 and size
2336
 
            self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2337
 
            return osutils.sha_strings(new_lines), \
2338
 
                sum(map(len, new_lines))
2339
 
 
2340
 
    def modified_link(self, file_id, file_parents, link_target):
2341
 
        """Record the presence of a symbolic link.
2342
 
 
2343
 
        :param file_id: The file_id of the link to record.
2344
 
        :param file_parents: The per-file parent revision ids.
2345
 
        :param link_target: Target location of this link.
2346
 
        """
2347
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2348
 
 
2349
 
    def _add_text_to_weave(self, file_id, new_lines, parents):
2350
 
        versionedfile = self.repository.weave_store.get_weave_or_empty(
2351
 
            file_id, self.repository.get_transaction())
2352
 
        versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2353
 
        versionedfile.clear_cache()
2354
 
 
2355
 
 
2356
 
class _CommitBuilder(CommitBuilder):
2357
 
    """Temporary class so old CommitBuilders are detected properly
2358
 
    
2359
 
    Note: CommitBuilder works whether or not root entry is recorded.
2360
 
    """
2361
 
 
2362
 
    record_root_entry = True
2363
 
 
2364
 
 
2365
 
class RootCommitBuilder(CommitBuilder):
2366
 
    """This commitbuilder actually records the root id"""
2367
 
    
2368
 
    record_root_entry = True
2369
 
 
2370
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
2371
 
        """Record the content of ie from tree into the commit if needed.
2372
 
 
2373
 
        Side effect: sets ie.revision when unchanged
2374
 
 
2375
 
        :param ie: An inventory entry present in the commit.
2376
 
        :param parent_invs: The inventories of the parent revisions of the
2377
 
            commit.
2378
 
        :param path: The path the entry is at in the tree.
2379
 
        :param tree: The tree which contains this entry and should be used to 
2380
 
        obtain content.
2381
 
        """
2382
 
        assert self.new_inventory.root is not None or ie.parent_id is None
2383
 
        self.new_inventory.add(ie)
2384
 
 
2385
 
        # ie.revision is always None if the InventoryEntry is considered
2386
 
        # for committing. ie.snapshot will record the correct revision 
2387
 
        # which may be the sole parent if it is untouched.
2388
 
        if ie.revision is not None:
2389
 
            return
2390
 
 
2391
 
        previous_entries = ie.find_previous_heads(
2392
 
            parent_invs,
2393
 
            self.repository.weave_store,
2394
 
            self.repository.get_transaction())
2395
 
        # we are creating a new revision for ie in the history store
2396
 
        # and inventory.
2397
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2398
 
 
2399
 
 
2400
2491
_unescape_map = {
2401
2492
    'apos':"'",
2402
2493
    'quot':'"',