548
498
raise NotImplementedError('unknown kind')
549
499
ie.revision = self._new_revision_id
550
self._any_changes = True
551
500
return self._get_delta(ie, basis_inv, path), True, fingerprint
553
def record_iter_changes(self, tree, basis_revision_id, iter_changes,
554
_entry_factory=entry_factory):
555
"""Record a new tree via iter_changes.
557
:param tree: The tree to obtain text contents from for changed objects.
558
:param basis_revision_id: The revision id of the tree the iter_changes
559
has been generated against. Currently assumed to be the same
560
as self.parents[0] - if it is not, errors may occur.
561
:param iter_changes: An iter_changes iterator with the changes to apply
562
to basis_revision_id. The iterator must not include any items with
563
a current kind of None - missing items must be either filtered out
564
or errored-on beefore record_iter_changes sees the item.
565
:param _entry_factory: Private method to bind entry_factory locally for
567
:return: A generator of (file_id, relpath, fs_hash) tuples for use with
570
# Create an inventory delta based on deltas between all the parents and
571
# deltas between all the parent inventories. We use inventory delta's
572
# between the inventory objects because iter_changes masks
573
# last-changed-field only changes.
575
# file_id -> change map, change is fileid, paths, changed, versioneds,
576
# parents, names, kinds, executables
578
# {file_id -> revision_id -> inventory entry, for entries in parent
579
# trees that are not parents[0]
583
revtrees = list(self.repository.revision_trees(self.parents))
584
except errors.NoSuchRevision:
585
# one or more ghosts, slow path.
587
for revision_id in self.parents:
589
revtrees.append(self.repository.revision_tree(revision_id))
590
except errors.NoSuchRevision:
592
basis_revision_id = _mod_revision.NULL_REVISION
594
revtrees.append(self.repository.revision_tree(
595
_mod_revision.NULL_REVISION))
596
# The basis inventory from a repository
598
basis_inv = revtrees[0].inventory
600
basis_inv = self.repository.revision_tree(
601
_mod_revision.NULL_REVISION).inventory
602
if len(self.parents) > 0:
603
if basis_revision_id != self.parents[0] and not ghost_basis:
605
"arbitrary basis parents not yet supported with merges")
606
for revtree in revtrees[1:]:
607
for change in revtree.inventory._make_delta(basis_inv):
608
if change[1] is None:
609
# Not present in this parent.
611
if change[2] not in merged_ids:
612
if change[0] is not None:
613
basis_entry = basis_inv[change[2]]
614
merged_ids[change[2]] = [
616
basis_entry.revision,
619
parent_entries[change[2]] = {
621
basis_entry.revision:basis_entry,
623
change[3].revision:change[3],
626
merged_ids[change[2]] = [change[3].revision]
627
parent_entries[change[2]] = {change[3].revision:change[3]}
629
merged_ids[change[2]].append(change[3].revision)
630
parent_entries[change[2]][change[3].revision] = change[3]
633
# Setup the changes from the tree:
634
# changes maps file_id -> (change, [parent revision_ids])
636
for change in iter_changes:
637
# This probably looks up in basis_inv way to much.
638
if change[1][0] is not None:
639
head_candidate = [basis_inv[change[0]].revision]
642
changes[change[0]] = change, merged_ids.get(change[0],
644
unchanged_merged = set(merged_ids) - set(changes)
645
# Extend the changes dict with synthetic changes to record merges of
647
for file_id in unchanged_merged:
648
# Record a merged version of these items that did not change vs the
649
# basis. This can be either identical parallel changes, or a revert
650
# of a specific file after a merge. The recorded content will be
651
# that of the current tree (which is the same as the basis), but
652
# the per-file graph will reflect a merge.
653
# NB:XXX: We are reconstructing path information we had, this
654
# should be preserved instead.
655
# inv delta change: (file_id, (path_in_source, path_in_target),
656
# changed_content, versioned, parent, name, kind,
659
basis_entry = basis_inv[file_id]
660
except errors.NoSuchId:
661
# a change from basis->some_parents but file_id isn't in basis
662
# so was new in the merge, which means it must have changed
663
# from basis -> current, and as it hasn't the add was reverted
664
# by the user. So we discard this change.
668
(basis_inv.id2path(file_id), tree.id2path(file_id)),
670
(basis_entry.parent_id, basis_entry.parent_id),
671
(basis_entry.name, basis_entry.name),
672
(basis_entry.kind, basis_entry.kind),
673
(basis_entry.executable, basis_entry.executable))
674
changes[file_id] = (change, merged_ids[file_id])
675
# changes contains tuples with the change and a set of inventory
676
# candidates for the file.
678
# old_path, new_path, file_id, new_inventory_entry
679
seen_root = False # Is the root in the basis delta?
680
inv_delta = self._basis_delta
681
modified_rev = self._new_revision_id
682
for change, head_candidates in changes.values():
683
if change[3][1]: # versioned in target.
684
# Several things may be happening here:
685
# We may have a fork in the per-file graph
686
# - record a change with the content from tree
687
# We may have a change against < all trees
688
# - carry over the tree that hasn't changed
689
# We may have a change against all trees
690
# - record the change with the content from tree
693
entry = _entry_factory[kind](file_id, change[5][1],
695
head_set = self._heads(change[0], set(head_candidates))
698
for head_candidate in head_candidates:
699
if head_candidate in head_set:
700
heads.append(head_candidate)
701
head_set.remove(head_candidate)
704
# Could be a carry-over situation:
705
parent_entry_revs = parent_entries.get(file_id, None)
706
if parent_entry_revs:
707
parent_entry = parent_entry_revs.get(heads[0], None)
710
if parent_entry is None:
711
# The parent iter_changes was called against is the one
712
# that is the per-file head, so any change is relevant
713
# iter_changes is valid.
714
carry_over_possible = False
716
# could be a carry over situation
717
# A change against the basis may just indicate a merge,
718
# we need to check the content against the source of the
719
# merge to determine if it was changed after the merge
721
if (parent_entry.kind != entry.kind or
722
parent_entry.parent_id != entry.parent_id or
723
parent_entry.name != entry.name):
724
# Metadata common to all entries has changed
725
# against per-file parent
726
carry_over_possible = False
728
carry_over_possible = True
729
# per-type checks for changes against the parent_entry
732
# Cannot be a carry-over situation
733
carry_over_possible = False
734
# Populate the entry in the delta
736
# XXX: There is still a small race here: If someone reverts the content of a file
737
# after iter_changes examines and decides it has changed,
738
# we will unconditionally record a new version even if some
739
# other process reverts it while commit is running (with
740
# the revert happening after iter_changes did it's
743
entry.executable = True
745
entry.executable = False
746
if (carry_over_possible and
747
parent_entry.executable == entry.executable):
748
# Check the file length, content hash after reading
750
nostore_sha = parent_entry.text_sha1
753
file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
755
lines = file_obj.readlines()
759
entry.text_sha1, entry.text_size = self._add_text_to_weave(
760
file_id, lines, heads, nostore_sha)
761
yield file_id, change[1][1], (entry.text_sha1, stat_value)
762
except errors.ExistingContent:
763
# No content change against a carry_over parent
764
# Perhaps this should also yield a fs hash update?
766
entry.text_size = parent_entry.text_size
767
entry.text_sha1 = parent_entry.text_sha1
768
elif kind == 'symlink':
770
entry.symlink_target = tree.get_symlink_target(file_id)
771
if (carry_over_possible and
772
parent_entry.symlink_target == entry.symlink_target):
775
self._add_text_to_weave(change[0], [], heads, None)
776
elif kind == 'directory':
777
if carry_over_possible:
780
# Nothing to set on the entry.
781
# XXX: split into the Root and nonRoot versions.
782
if change[1][1] != '' or self.repository.supports_rich_root():
783
self._add_text_to_weave(change[0], [], heads, None)
784
elif kind == 'tree-reference':
785
if not self.repository._format.supports_tree_reference:
786
# This isn't quite sane as an error, but we shouldn't
787
# ever see this code path in practice: tree's don't
788
# permit references when the repo doesn't support tree
790
raise errors.UnsupportedOperation(tree.add_reference,
792
entry.reference_revision = \
793
tree.get_reference_revision(change[0])
794
if (carry_over_possible and
795
parent_entry.reference_revision == reference_revision):
798
self._add_text_to_weave(change[0], [], heads, None)
800
raise AssertionError('unknown kind %r' % kind)
802
entry.revision = modified_rev
804
entry.revision = parent_entry.revision
807
new_path = change[1][1]
808
inv_delta.append((change[1][0], new_path, change[0], entry))
811
self.new_inventory = None
813
self._any_changes = True
815
# housekeeping root entry changes do not affect no-change commits.
816
self._require_root_change(tree)
817
self.basis_delta_revision = basis_revision_id
819
502
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
820
503
# Note: as we read the content directly from the tree, we know its not
821
504
# been turned into unicode or badly split - but a broken tree
3604
3241
return basis_id, basis_tree
3244
class InterOtherToRemote(InterRepository):
3245
"""An InterRepository that simply delegates to the 'real' InterRepository
3246
calculated for (source, target._real_repository).
3249
_walk_to_common_revisions_batch_size = 50
3251
def __init__(self, source, target):
3252
InterRepository.__init__(self, source, target)
3253
self._real_inter = None
3256
def is_compatible(source, target):
3257
if isinstance(target, remote.RemoteRepository):
3261
def _ensure_real_inter(self):
3262
if self._real_inter is None:
3263
self.target._ensure_real()
3264
real_target = self.target._real_repository
3265
self._real_inter = InterRepository.get(self.source, real_target)
3266
# Make _real_inter use the RemoteRepository for get_parent_map
3267
self._real_inter.target_get_graph = self.target.get_graph
3268
self._real_inter.target_get_parent_map = self.target.get_parent_map
3270
def copy_content(self, revision_id=None):
3271
self._ensure_real_inter()
3272
self._real_inter.copy_content(revision_id=revision_id)
3274
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3275
self._ensure_real_inter()
3276
return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3277
find_ghosts=find_ghosts)
3280
def _get_repo_format_to_test(self):
3284
class InterRemoteToOther(InterRepository):
3286
def __init__(self, source, target):
3287
InterRepository.__init__(self, source, target)
3288
self._real_inter = None
3291
def is_compatible(source, target):
3292
if not isinstance(source, remote.RemoteRepository):
3294
return InterRepository._same_model(source, target)
3296
def _ensure_real_inter(self):
3297
if self._real_inter is None:
3298
self.source._ensure_real()
3299
real_source = self.source._real_repository
3300
self._real_inter = InterRepository.get(real_source, self.target)
3303
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3304
"""See InterRepository.fetch()."""
3305
# Always fetch using the generic streaming fetch code, to allow
3306
# streaming fetching from remote servers.
3307
from bzrlib.fetch import RepoFetcher
3308
fetcher = RepoFetcher(self.target, self.source, revision_id,
3311
def copy_content(self, revision_id=None):
3312
self._ensure_real_inter()
3313
self._real_inter.copy_content(revision_id=revision_id)
3316
def _get_repo_format_to_test(self):
3321
class InterPackToRemotePack(InterPackRepo):
3322
"""A specialisation of InterPackRepo for a target that is a
3325
This will use the get_parent_map RPC rather than plain readvs, and also
3326
uses an RPC for autopacking.
3329
_walk_to_common_revisions_batch_size = 50
3332
def is_compatible(source, target):
3333
from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3334
if isinstance(source._format, RepositoryFormatPack):
3335
if isinstance(target, remote.RemoteRepository):
3336
target._format._ensure_real()
3337
if isinstance(target._format._custom_format,
3338
RepositoryFormatPack):
3339
if InterRepository._same_model(source, target):
3343
def _autopack(self):
3344
self.target.autopack()
3347
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3348
"""See InterRepository.fetch()."""
3349
# Always fetch using the generic streaming fetch code, to allow
3350
# streaming fetching into remote servers.
3351
from bzrlib.fetch import RepoFetcher
3352
fetcher = RepoFetcher(self.target, self.source, revision_id,
3355
def _get_target_pack_collection(self):
3356
return self.target._real_repository._pack_collection
3359
def _get_repo_format_to_test(self):
3607
3363
InterRepository.register_optimiser(InterDifferingSerializer)
3608
3364
InterRepository.register_optimiser(InterSameDataRepository)
3609
3365
InterRepository.register_optimiser(InterWeaveRepo)
3610
3366
InterRepository.register_optimiser(InterKnitRepo)
3611
3367
InterRepository.register_optimiser(InterPackRepo)
3368
InterRepository.register_optimiser(InterOtherToRemote)
3369
InterRepository.register_optimiser(InterRemoteToOther)
3370
InterRepository.register_optimiser(InterPackToRemotePack)
3614
3373
class CopyConverter(object):