163
145
self._validate_unicode_text(value,
164
146
'revision property (%s)' % (key,))
166
def _ensure_fallback_inventories(self):
167
"""Ensure that appropriate inventories are available.
169
This only applies to repositories that are stacked, and is about
170
enusring the stacking invariants. Namely, that for any revision that is
171
present, we either have all of the file content, or we have the parent
172
inventory and the delta file content.
174
if not self.repository._fallback_repositories:
176
if not self.repository._format.supports_chks:
177
raise errors.BzrError("Cannot commit directly to a stacked branch"
178
" in pre-2a formats. See "
179
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
180
# This is a stacked repo, we need to make sure we have the parent
181
# inventories for the parents.
182
parent_keys = [(p,) for p in self.parents]
183
parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
184
missing_parent_keys = set([pk for pk in parent_keys
185
if pk not in parent_map])
186
fallback_repos = list(reversed(self.repository._fallback_repositories))
187
missing_keys = [('inventories', pk[0])
188
for pk in missing_parent_keys]
190
while missing_keys and fallback_repos:
191
fallback_repo = fallback_repos.pop()
192
source = fallback_repo._get_source(self.repository._format)
193
sink = self.repository._get_sink()
194
stream = source.get_stream_for_missing_keys(missing_keys)
195
missing_keys = sink.insert_stream_without_locking(stream,
196
self.repository._format)
198
raise errors.BzrError('Unable to fill in parent inventories for a'
201
148
def commit(self, message):
202
149
"""Make the actual commit.
204
151
:return: The revision id of the recorded revision.
206
self._validate_unicode_text(message, 'commit message')
207
rev = _mod_revision.Revision(
208
timestamp=self._timestamp,
209
timezone=self._timezone,
210
committer=self._committer,
212
inventory_sha1=self.inv_sha1,
213
revision_id=self._new_revision_id,
214
properties=self._revprops)
215
rev.parent_ids = self.parents
216
self.repository.add_revision(self._new_revision_id, rev,
217
self.new_inventory, self._config)
218
self._ensure_fallback_inventories()
219
self.repository.commit_write_group()
220
return self._new_revision_id
153
raise NotImplementedError(self.commit)
223
156
"""Abort the commit that is being built.
225
self.repository.abort_write_group()
158
raise NotImplementedError(self.abort)
227
160
def revision_tree(self):
228
161
"""Return the tree that was just committed.
288
196
self.random_revid = False
290
def _heads(self, file_id, revision_ids):
291
"""Calculate the graph heads for revision_ids in the graph of file_id.
293
This can use either a per-file graph or a global revision graph as we
294
have an identity relationship between the two graphs.
296
return self.__heads(revision_ids)
298
def _check_root(self, ie, parent_invs, tree):
299
"""Helper for record_entry_contents.
301
:param ie: An entry being added.
302
:param parent_invs: The inventories of the parent revisions of the
304
:param tree: The tree that is being committed.
306
# In this revision format, root entries have no knit or weave When
307
# serializing out to disk and back in root.revision is always
309
ie.revision = self._new_revision_id
311
def _require_root_change(self, tree):
312
"""Enforce an appropriate root object change.
314
This is called once when record_iter_changes is called, if and only if
315
the root was not in the delta calculated by record_iter_changes.
317
:param tree: The tree which is being committed.
319
if len(self.parents) == 0:
320
raise errors.RootMissing()
321
entry = entry_factory['directory'](tree.path2id(''), '',
323
entry.revision = self._new_revision_id
324
self._basis_delta.append(('', '', entry.file_id, entry))
326
def _get_delta(self, ie, basis_inv, path):
327
"""Get a delta against the basis inventory for ie."""
328
if ie.file_id not in basis_inv:
330
result = (None, path, ie.file_id, ie)
331
self._basis_delta.append(result)
333
elif ie != basis_inv[ie.file_id]:
335
# TODO: avoid tis id2path call.
336
result = (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
337
self._basis_delta.append(result)
343
def get_basis_delta(self):
344
"""Return the complete inventory delta versus the basis inventory.
346
This has been built up with the calls to record_delete and
347
record_entry_contents. The client must have already called
348
will_record_deletes() to indicate that they will be generating a
351
:return: An inventory delta, suitable for use with apply_delta, or
352
Repository.add_inventory_by_delta, etc.
354
if not self._recording_deletes:
355
raise AssertionError("recording deletes not activated.")
356
return self._basis_delta
358
def record_delete(self, path, file_id):
359
"""Record that a delete occured against a basis tree.
361
This is an optional API - when used it adds items to the basis_delta
362
being accumulated by the commit builder. It cannot be called unless the
363
method will_record_deletes() has been called to inform the builder that
364
a delta is being supplied.
366
:param path: The path of the thing deleted.
367
:param file_id: The file id that was deleted.
369
if not self._recording_deletes:
370
raise AssertionError("recording deletes not activated.")
371
delta = (path, None, file_id, None)
372
self._basis_delta.append(delta)
373
self._any_changes = True
376
198
def will_record_deletes(self):
377
199
"""Tell the commit builder that deletes are being notified.
380
202
commit to be valid, deletes against the basis MUST be recorded via
381
203
builder.record_delete().
383
self._recording_deletes = True
385
basis_id = self.parents[0]
387
basis_id = _mod_revision.NULL_REVISION
388
self.basis_delta_revision = basis_id
390
def record_entry_contents(self, ie, parent_invs, path, tree,
392
"""Record the content of ie from tree into the commit if needed.
394
Side effect: sets ie.revision when unchanged
396
:param ie: An inventory entry present in the commit.
397
:param parent_invs: The inventories of the parent revisions of the
399
:param path: The path the entry is at in the tree.
400
:param tree: The tree which contains this entry and should be used to
402
:param content_summary: Summary data from the tree about the paths
403
content - stat, length, exec, sha/link target. This is only
404
accessed when the entry has a revision of None - that is when it is
405
a candidate to commit.
406
:return: A tuple (change_delta, version_recorded, fs_hash).
407
change_delta is an inventory_delta change for this entry against
408
the basis tree of the commit, or None if no change occured against
410
version_recorded is True if a new version of the entry has been
411
recorded. For instance, committing a merge where a file was only
412
changed on the other side will return (delta, False).
413
fs_hash is either None, or the hash details for the path (currently
414
a tuple of the contents sha1 and the statvalue returned by
415
tree.get_file_with_stat()).
417
if self.new_inventory.root is None:
418
if ie.parent_id is not None:
419
raise errors.RootMissing()
420
self._check_root(ie, parent_invs, tree)
421
if ie.revision is None:
422
kind = content_summary[0]
424
# ie is carried over from a prior commit
426
# XXX: repository specific check for nested tree support goes here - if
427
# the repo doesn't want nested trees we skip it ?
428
if (kind == 'tree-reference' and
429
not self.repository._format.supports_tree_reference):
430
# mismatch between commit builder logic and repository:
431
# this needs the entry creation pushed down into the builder.
432
raise NotImplementedError('Missing repository subtree support.')
433
self.new_inventory.add(ie)
435
# TODO: slow, take it out of the inner loop.
437
basis_inv = parent_invs[0]
439
basis_inv = Inventory(root_id=None)
441
# ie.revision is always None if the InventoryEntry is considered
442
# for committing. We may record the previous parents revision if the
443
# content is actually unchanged against a sole head.
444
if ie.revision is not None:
445
if not self._versioned_root and path == '':
446
# repositories that do not version the root set the root's
447
# revision to the new commit even when no change occurs (more
448
# specifically, they do not record a revision on the root; and
449
# the rev id is assigned to the root during deserialisation -
450
# this masks when a change may have occurred against the basis.
451
# To match this we always issue a delta, because the revision
452
# of the root will always be changing.
453
if ie.file_id in basis_inv:
454
delta = (basis_inv.id2path(ie.file_id), path,
458
delta = (None, path, ie.file_id, ie)
459
self._basis_delta.append(delta)
460
return delta, False, None
462
# we don't need to commit this, because the caller already
463
# determined that an existing revision of this file is
464
# appropriate. If it's not being considered for committing then
465
# it and all its parents to the root must be unaltered so
466
# no-change against the basis.
467
if ie.revision == self._new_revision_id:
468
raise AssertionError("Impossible situation, a skipped "
469
"inventory entry (%r) claims to be modified in this "
470
"commit (%r).", (ie, self._new_revision_id))
471
return None, False, None
472
# XXX: Friction: parent_candidates should return a list not a dict
473
# so that we don't have to walk the inventories again.
474
parent_candiate_entries = ie.parent_candidates(parent_invs)
475
head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
477
for inv in parent_invs:
478
if ie.file_id in inv:
479
old_rev = inv[ie.file_id].revision
480
if old_rev in head_set:
481
heads.append(inv[ie.file_id].revision)
482
head_set.remove(inv[ie.file_id].revision)
485
# now we check to see if we need to write a new record to the
487
# We write a new entry unless there is one head to the ancestors, and
488
# the kind-derived content is unchanged.
490
# Cheapest check first: no ancestors, or more the one head in the
491
# ancestors, we write a new node.
495
# There is a single head, look it up for comparison
496
parent_entry = parent_candiate_entries[heads[0]]
497
# if the non-content specific data has changed, we'll be writing a
499
if (parent_entry.parent_id != ie.parent_id or
500
parent_entry.name != ie.name):
502
# now we need to do content specific checks:
504
# if the kind changed the content obviously has
505
if kind != parent_entry.kind:
507
# Stat cache fingerprint feedback for the caller - None as we usually
508
# don't generate one.
511
if content_summary[2] is None:
512
raise ValueError("Files must not have executable = None")
514
# We can't trust a check of the file length because of content
516
if (# if the exec bit has changed we have to store:
517
parent_entry.executable != content_summary[2]):
519
elif parent_entry.text_sha1 == content_summary[3]:
520
# all meta and content is unchanged (using a hash cache
521
# hit to check the sha)
522
ie.revision = parent_entry.revision
523
ie.text_size = parent_entry.text_size
524
ie.text_sha1 = parent_entry.text_sha1
525
ie.executable = parent_entry.executable
526
return self._get_delta(ie, basis_inv, path), False, None
528
# Either there is only a hash change(no hash cache entry,
529
# or same size content change), or there is no change on
531
# Provide the parent's hash to the store layer, so that the
532
# content is unchanged we will not store a new node.
533
nostore_sha = parent_entry.text_sha1
535
# We want to record a new node regardless of the presence or
536
# absence of a content change in the file.
538
ie.executable = content_summary[2]
539
file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
541
text = file_obj.read()
545
ie.text_sha1, ie.text_size = self._add_text_to_weave(
546
ie.file_id, text, heads, nostore_sha)
547
# Let the caller know we generated a stat fingerprint.
548
fingerprint = (ie.text_sha1, stat_value)
549
except errors.ExistingContent:
550
# Turns out that the file content was unchanged, and we were
551
# only going to store a new node if it was changed. Carry over
553
ie.revision = parent_entry.revision
554
ie.text_size = parent_entry.text_size
555
ie.text_sha1 = parent_entry.text_sha1
556
ie.executable = parent_entry.executable
557
return self._get_delta(ie, basis_inv, path), False, None
558
elif kind == 'directory':
560
# all data is meta here, nothing specific to directory, so
562
ie.revision = parent_entry.revision
563
return self._get_delta(ie, basis_inv, path), False, None
564
self._add_text_to_weave(ie.file_id, '', heads, None)
565
elif kind == 'symlink':
566
current_link_target = content_summary[3]
568
# symlink target is not generic metadata, check if it has
570
if current_link_target != parent_entry.symlink_target:
573
# unchanged, carry over.
574
ie.revision = parent_entry.revision
575
ie.symlink_target = parent_entry.symlink_target
576
return self._get_delta(ie, basis_inv, path), False, None
577
ie.symlink_target = current_link_target
578
self._add_text_to_weave(ie.file_id, '', heads, None)
579
elif kind == 'tree-reference':
581
if content_summary[3] != parent_entry.reference_revision:
584
# unchanged, carry over.
585
ie.reference_revision = parent_entry.reference_revision
586
ie.revision = parent_entry.revision
587
return self._get_delta(ie, basis_inv, path), False, None
588
ie.reference_revision = content_summary[3]
589
if ie.reference_revision is None:
590
raise AssertionError("invalid content_summary for nested tree: %r"
591
% (content_summary,))
592
self._add_text_to_weave(ie.file_id, '', heads, None)
594
raise NotImplementedError('unknown kind')
595
ie.revision = self._new_revision_id
596
self._any_changes = True
597
return self._get_delta(ie, basis_inv, path), True, fingerprint
599
def record_iter_changes(self, tree, basis_revision_id, iter_changes,
600
_entry_factory=entry_factory):
205
raise NotImplementedError(self.will_record_deletes)
207
def record_iter_changes(self, tree, basis_revision_id, iter_changes):
601
208
"""Record a new tree via iter_changes.
603
210
:param tree: The tree to obtain text contents from for changed objects.
608
215
to basis_revision_id. The iterator must not include any items with
609
216
a current kind of None - missing items must be either filtered out
610
217
or errored-on beefore record_iter_changes sees the item.
611
:param _entry_factory: Private method to bind entry_factory locally for
613
218
:return: A generator of (file_id, relpath, fs_hash) tuples for use with
614
219
tree._observed_sha1.
616
# Create an inventory delta based on deltas between all the parents and
617
# deltas between all the parent inventories. We use inventory delta's
618
# between the inventory objects because iter_changes masks
619
# last-changed-field only changes.
621
# file_id -> change map, change is fileid, paths, changed, versioneds,
622
# parents, names, kinds, executables
624
# {file_id -> revision_id -> inventory entry, for entries in parent
625
# trees that are not parents[0]
629
revtrees = list(self.repository.revision_trees(self.parents))
630
except errors.NoSuchRevision:
631
# one or more ghosts, slow path.
633
for revision_id in self.parents:
635
revtrees.append(self.repository.revision_tree(revision_id))
636
except errors.NoSuchRevision:
638
basis_revision_id = _mod_revision.NULL_REVISION
640
revtrees.append(self.repository.revision_tree(
641
_mod_revision.NULL_REVISION))
642
# The basis inventory from a repository
644
basis_inv = revtrees[0].inventory
646
basis_inv = self.repository.revision_tree(
647
_mod_revision.NULL_REVISION).inventory
648
if len(self.parents) > 0:
649
if basis_revision_id != self.parents[0] and not ghost_basis:
651
"arbitrary basis parents not yet supported with merges")
652
for revtree in revtrees[1:]:
653
for change in revtree.inventory._make_delta(basis_inv):
654
if change[1] is None:
655
# Not present in this parent.
657
if change[2] not in merged_ids:
658
if change[0] is not None:
659
basis_entry = basis_inv[change[2]]
660
merged_ids[change[2]] = [
662
basis_entry.revision,
665
parent_entries[change[2]] = {
667
basis_entry.revision:basis_entry,
669
change[3].revision:change[3],
672
merged_ids[change[2]] = [change[3].revision]
673
parent_entries[change[2]] = {change[3].revision:change[3]}
675
merged_ids[change[2]].append(change[3].revision)
676
parent_entries[change[2]][change[3].revision] = change[3]
679
# Setup the changes from the tree:
680
# changes maps file_id -> (change, [parent revision_ids])
682
for change in iter_changes:
683
# This probably looks up in basis_inv way to much.
684
if change[1][0] is not None:
685
head_candidate = [basis_inv[change[0]].revision]
688
changes[change[0]] = change, merged_ids.get(change[0],
690
unchanged_merged = set(merged_ids) - set(changes)
691
# Extend the changes dict with synthetic changes to record merges of
693
for file_id in unchanged_merged:
694
# Record a merged version of these items that did not change vs the
695
# basis. This can be either identical parallel changes, or a revert
696
# of a specific file after a merge. The recorded content will be
697
# that of the current tree (which is the same as the basis), but
698
# the per-file graph will reflect a merge.
699
# NB:XXX: We are reconstructing path information we had, this
700
# should be preserved instead.
701
# inv delta change: (file_id, (path_in_source, path_in_target),
702
# changed_content, versioned, parent, name, kind,
705
basis_entry = basis_inv[file_id]
706
except errors.NoSuchId:
707
# a change from basis->some_parents but file_id isn't in basis
708
# so was new in the merge, which means it must have changed
709
# from basis -> current, and as it hasn't the add was reverted
710
# by the user. So we discard this change.
714
(basis_inv.id2path(file_id), tree.id2path(file_id)),
716
(basis_entry.parent_id, basis_entry.parent_id),
717
(basis_entry.name, basis_entry.name),
718
(basis_entry.kind, basis_entry.kind),
719
(basis_entry.executable, basis_entry.executable))
720
changes[file_id] = (change, merged_ids[file_id])
721
# changes contains tuples with the change and a set of inventory
722
# candidates for the file.
724
# old_path, new_path, file_id, new_inventory_entry
725
seen_root = False # Is the root in the basis delta?
726
inv_delta = self._basis_delta
727
modified_rev = self._new_revision_id
728
for change, head_candidates in changes.values():
729
if change[3][1]: # versioned in target.
730
# Several things may be happening here:
731
# We may have a fork in the per-file graph
732
# - record a change with the content from tree
733
# We may have a change against < all trees
734
# - carry over the tree that hasn't changed
735
# We may have a change against all trees
736
# - record the change with the content from tree
739
entry = _entry_factory[kind](file_id, change[5][1],
741
head_set = self._heads(change[0], set(head_candidates))
744
for head_candidate in head_candidates:
745
if head_candidate in head_set:
746
heads.append(head_candidate)
747
head_set.remove(head_candidate)
750
# Could be a carry-over situation:
751
parent_entry_revs = parent_entries.get(file_id, None)
752
if parent_entry_revs:
753
parent_entry = parent_entry_revs.get(heads[0], None)
756
if parent_entry is None:
757
# The parent iter_changes was called against is the one
758
# that is the per-file head, so any change is relevant
759
# iter_changes is valid.
760
carry_over_possible = False
762
# could be a carry over situation
763
# A change against the basis may just indicate a merge,
764
# we need to check the content against the source of the
765
# merge to determine if it was changed after the merge
767
if (parent_entry.kind != entry.kind or
768
parent_entry.parent_id != entry.parent_id or
769
parent_entry.name != entry.name):
770
# Metadata common to all entries has changed
771
# against per-file parent
772
carry_over_possible = False
774
carry_over_possible = True
775
# per-type checks for changes against the parent_entry
778
# Cannot be a carry-over situation
779
carry_over_possible = False
780
# Populate the entry in the delta
782
# XXX: There is still a small race here: If someone reverts the content of a file
783
# after iter_changes examines and decides it has changed,
784
# we will unconditionally record a new version even if some
785
# other process reverts it while commit is running (with
786
# the revert happening after iter_changes did its
789
entry.executable = True
791
entry.executable = False
792
if (carry_over_possible and
793
parent_entry.executable == entry.executable):
794
# Check the file length, content hash after reading
796
nostore_sha = parent_entry.text_sha1
799
file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
801
text = file_obj.read()
805
entry.text_sha1, entry.text_size = self._add_text_to_weave(
806
file_id, text, heads, nostore_sha)
807
yield file_id, change[1][1], (entry.text_sha1, stat_value)
808
except errors.ExistingContent:
809
# No content change against a carry_over parent
810
# Perhaps this should also yield a fs hash update?
812
entry.text_size = parent_entry.text_size
813
entry.text_sha1 = parent_entry.text_sha1
814
elif kind == 'symlink':
816
entry.symlink_target = tree.get_symlink_target(file_id)
817
if (carry_over_possible and
818
parent_entry.symlink_target == entry.symlink_target):
821
self._add_text_to_weave(change[0], '', heads, None)
822
elif kind == 'directory':
823
if carry_over_possible:
826
# Nothing to set on the entry.
827
# XXX: split into the Root and nonRoot versions.
828
if change[1][1] != '' or self.repository.supports_rich_root():
829
self._add_text_to_weave(change[0], '', heads, None)
830
elif kind == 'tree-reference':
831
if not self.repository._format.supports_tree_reference:
832
# This isn't quite sane as an error, but we shouldn't
833
# ever see this code path in practice: tree's don't
834
# permit references when the repo doesn't support tree
836
raise errors.UnsupportedOperation(tree.add_reference,
838
reference_revision = tree.get_reference_revision(change[0])
839
entry.reference_revision = reference_revision
840
if (carry_over_possible and
841
parent_entry.reference_revision == reference_revision):
844
self._add_text_to_weave(change[0], '', heads, None)
846
raise AssertionError('unknown kind %r' % kind)
848
entry.revision = modified_rev
850
entry.revision = parent_entry.revision
853
new_path = change[1][1]
854
inv_delta.append((change[1][0], new_path, change[0], entry))
857
self.new_inventory = None
859
# This should perhaps be guarded by a check that the basis we
860
# commit against is the basis for the commit and if not do a delta
862
self._any_changes = True
864
# housekeeping root entry changes do not affect no-change commits.
865
self._require_root_change(tree)
866
self.basis_delta_revision = basis_revision_id
868
def _add_text_to_weave(self, file_id, new_text, parents, nostore_sha):
869
parent_keys = tuple([(file_id, parent) for parent in parents])
870
return self.repository.texts._add_text(
871
(file_id, self._new_revision_id), parent_keys, new_text,
872
nostore_sha=nostore_sha, random_id=self.random_revid)[0:2]
875
class RootCommitBuilder(CommitBuilder):
876
"""This commitbuilder actually records the root id"""
878
# the root entry gets versioned properly by this builder.
879
_versioned_root = True
881
def _check_root(self, ie, parent_invs, tree):
882
"""Helper for record_entry_contents.
884
:param ie: An entry being added.
885
:param parent_invs: The inventories of the parent revisions of the
887
:param tree: The tree that is being committed.
890
def _require_root_change(self, tree):
891
"""Enforce an appropriate root object change.
893
This is called once when record_iter_changes is called, if and only if
894
the root was not in the delta calculated by record_iter_changes.
896
:param tree: The tree which is being committed.
898
# versioned roots do not change unless the tree found a change.
221
raise NotImplementedError(self.record_iter_changes)
901
224
class RepositoryWriteLockResult(LogicalLockResult):