176
189
deserializing the inventory, while we already have a copy in
192
if self.new_inventory is None:
193
self.new_inventory = self.repository.get_inventory(
194
self._new_revision_id)
179
195
return RevisionTree(self.repository, self.new_inventory,
180
self._new_revision_id)
196
self._new_revision_id)
182
198
def finish_inventory(self):
183
"""Tell the builder that the inventory is finished."""
184
if self.new_inventory.root is None:
185
raise AssertionError('Root entry should be supplied to'
186
' record_entry_contents, as of bzr 0.10.')
187
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
188
self.new_inventory.revision_id = self._new_revision_id
189
self.inv_sha1 = self.repository.add_inventory(
190
self._new_revision_id,
199
"""Tell the builder that the inventory is finished.
201
:return: The inventory id in the repository, which can be used with
202
repository.get_inventory.
204
if self.new_inventory is None:
205
# an inventory delta was accumulated without creating a new
207
basis_id = self.basis_delta_revision
208
self.inv_sha1 = self.repository.add_inventory_by_delta(
209
basis_id, self._basis_delta, self._new_revision_id,
212
if self.new_inventory.root is None:
213
raise AssertionError('Root entry should be supplied to'
214
' record_entry_contents, as of bzr 0.10.')
215
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
216
self.new_inventory.revision_id = self._new_revision_id
217
self.inv_sha1 = self.repository.add_inventory(
218
self._new_revision_id,
222
return self._new_revision_id
195
224
def _gen_revision_id(self):
196
225
"""Return new revision-id."""
498
547
raise NotImplementedError('unknown kind')
499
548
ie.revision = self._new_revision_id
549
self._any_changes = True
500
550
return self._get_delta(ie, basis_inv, path), True, fingerprint
552
def record_iter_changes(self, tree, basis_revision_id, iter_changes,
553
_entry_factory=entry_factory):
554
"""Record a new tree via iter_changes.
556
:param tree: The tree to obtain text contents from for changed objects.
557
:param basis_revision_id: The revision id of the tree the iter_changes
558
has been generated against. Currently assumed to be the same
559
as self.parents[0] - if it is not, errors may occur.
560
:param iter_changes: An iter_changes iterator with the changes to apply
561
to basis_revision_id.
562
:param _entry_factory: Private method to bind entry_factory locally for
566
# Create an inventory delta based on deltas between all the parents and
567
# deltas between all the parent inventories. We use inventory delta's
568
# between the inventory objects because iter_changes masks
569
# last-changed-field only changes.
571
# file_id -> change map, change is fileid, paths, changed, versioneds,
572
# parents, names, kinds, executables
574
# {file_id -> revision_id -> inventory entry, for entries in parent
575
# trees that are not parents[0]
577
revtrees = list(self.repository.revision_trees(self.parents))
578
# The basis inventory from a repository
580
basis_inv = revtrees[0].inventory
582
basis_inv = self.repository.revision_tree(
583
_mod_revision.NULL_REVISION).inventory
584
if len(self.parents) > 0:
585
if basis_revision_id != self.parents[0]:
587
"arbitrary basis parents not yet supported with merges")
588
for revtree in revtrees[1:]:
589
for change in revtree.inventory._make_delta(basis_inv):
590
if change[1] is None:
591
# Not present in this parent.
593
if change[2] not in merged_ids:
594
if change[0] is not None:
595
merged_ids[change[2]] = [
596
basis_inv[change[2]].revision,
599
merged_ids[change[2]] = [change[3].revision]
600
parent_entries[change[2]] = {change[3].revision:change[3]}
602
merged_ids[change[2]].append(change[3].revision)
603
parent_entries[change[2]][change[3].revision] = change[3]
606
# Setup the changes from the tree:
607
# changes maps file_id -> (change, [parent revision_ids])
609
for change in iter_changes:
610
# This probably looks up in basis_inv way to much.
611
if change[1][0] is not None:
612
head_candidate = [basis_inv[change[0]].revision]
615
changes[change[0]] = change, merged_ids.get(change[0],
617
unchanged_merged = set(merged_ids) - set(changes)
618
# Extend the changes dict with synthetic changes to record merges of
620
for file_id in unchanged_merged:
621
# Record a merged version of these items that did not change vs the
622
# basis. This can be either identical parallel changes, or a revert
623
# of a specific file after a merge. The recorded content will be
624
# that of the current tree (which is the same as the basis), but
625
# the per-file graph will reflect a merge.
626
# NB:XXX: We are reconstructing path information we had, this
627
# should be preserved instead.
628
# inv delta change: (file_id, (path_in_source, path_in_target),
629
# changed_content, versioned, parent, name, kind,
631
basis_entry = basis_inv[file_id]
633
(basis_inv.id2path(file_id), tree.id2path(file_id)),
635
(basis_entry.parent_id, basis_entry.parent_id),
636
(basis_entry.name, basis_entry.name),
637
(basis_entry.kind, basis_entry.kind),
638
(basis_entry.executable, basis_entry.executable))
639
changes[file_id] = (change, merged_ids[file_id])
640
# changes contains tuples with the change and a set of inventory
641
# candidates for the file.
643
# old_path, new_path, file_id, new_inventory_entry
644
seen_root = False # Is the root in the basis delta?
645
inv_delta = self._basis_delta
646
modified_rev = self._new_revision_id
647
for change, head_candidates in changes.values():
648
if change[3][1]: # versioned in target.
649
# Several things may be happening here:
650
# We may have a fork in the per-file graph
651
# - record a change with the content from tree
652
# We may have a change against < all trees
653
# - carry over the tree that hasn't changed
654
# We may have a change against all trees
655
# - record the change with the content from tree
658
entry = _entry_factory[kind](file_id, change[5][1],
660
head_set = self._heads(change[0], set(head_candidates))
663
for head_candidate in head_candidates:
664
if head_candidate in head_set:
665
heads.append(head_candidate)
666
head_set.remove(head_candidate)
669
# Could be a carry-over situation:
670
parent_entry_revs = parent_entries.get(file_id, None)
671
if parent_entry_revs:
672
parent_entry = parent_entry_revs.get(heads[0], None)
675
if parent_entry is None:
676
# The parent iter_changes was called against is the one
677
# that is the per-file head, so any change is relevant
678
# iter_changes is valid.
679
carry_over_possible = False
681
# could be a carry over situation
682
# A change against the basis may just indicate a merge,
683
# we need to check the content against the source of the
684
# merge to determine if it was changed after the merge
686
if (parent_entry.kind != entry.kind or
687
parent_entry.parent_id != entry.parent_id or
688
parent_entry.name != entry.name):
689
# Metadata common to all entries has changed
690
# against per-file parent
691
carry_over_possible = False
693
carry_over_possible = True
694
# per-type checks for changes against the parent_entry
697
# Cannot be a carry-over situation
698
carry_over_possible = False
699
# Populate the entry in the delta
701
# XXX: There is still a small race here: If someone reverts the content of a file
702
# after iter_changes examines and decides it has changed,
703
# we will unconditionally record a new version even if some
704
# other process reverts it while commit is running (with
705
# the revert happening after iter_changes did it's
708
entry.executable = True
710
entry.executable = False
711
if (carry_over_possible and
712
parent_entry.executable == entry.executable):
713
# Check the file length, content hash after reading
715
nostore_sha = parent_entry.text_sha1
718
file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
720
lines = file_obj.readlines()
724
entry.text_sha1, entry.text_size = self._add_text_to_weave(
725
file_id, lines, heads, nostore_sha)
726
except errors.ExistingContent:
727
# No content change against a carry_over parent
729
entry.text_size = parent_entry.text_size
730
entry.text_sha1 = parent_entry.text_sha1
731
elif kind == 'symlink':
733
entry.symlink_target = tree.get_symlink_target(file_id)
734
if (carry_over_possible and
735
parent_entry.symlink_target == entry.symlink_target):
738
self._add_text_to_weave(change[0], [], heads, None)
739
elif kind == 'directory':
740
if carry_over_possible:
743
# Nothing to set on the entry.
744
# XXX: split into the Root and nonRoot versions.
745
if change[1][1] != '' or self.repository.supports_rich_root():
746
self._add_text_to_weave(change[0], [], heads, None)
747
elif kind == 'tree-reference':
748
raise AssertionError('unknown kind %r' % kind)
750
raise AssertionError('unknown kind %r' % kind)
752
entry.revision = modified_rev
754
entry.revision = parent_entry.revision
757
new_path = change[1][1]
758
inv_delta.append((change[1][0], new_path, change[0], entry))
761
self.new_inventory = None
763
self._any_changes = True
765
# housekeeping root entry changes do not affect no-change commits.
766
self._require_root_change(tree)
767
self.basis_delta_revision = basis_revision_id
502
769
def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
503
770
# Note: as we read the content directly from the tree, we know its not
504
771
# been turned into unicode or badly split - but a broken tree
1313
1596
rev_tmp.seek(0)
1314
1597
return rev_tmp.getvalue()
1316
def get_deltas_for_revisions(self, revisions):
1599
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1317
1600
"""Produce a generator of revision deltas.
1319
1602
Note that the input is a sequence of REVISIONS, not revision_ids.
1320
1603
Trees will be held in memory until the generator exits.
1321
1604
Each delta is relative to the revision's lefthand predecessor.
1606
:param specific_fileids: if not None, the result is filtered
1607
so that only those file-ids, their parents and their
1608
children are included.
1610
# Get the revision-ids of interest
1323
1611
required_trees = set()
1324
1612
for revision in revisions:
1325
1613
required_trees.add(revision.revision_id)
1326
1614
required_trees.update(revision.parent_ids[:1])
1327
trees = dict((t.get_revision_id(), t) for
1328
t in self.revision_trees(required_trees))
1616
# Get the matching filtered trees. Note that it's more
1617
# efficient to pass filtered trees to changes_from() rather
1618
# than doing the filtering afterwards. changes_from() could
1619
# arguably do the filtering itself but it's path-based, not
1620
# file-id based, so filtering before or afterwards is
1622
if specific_fileids is None:
1623
trees = dict((t.get_revision_id(), t) for
1624
t in self.revision_trees(required_trees))
1626
trees = dict((t.get_revision_id(), t) for
1627
t in self._filtered_revision_trees(required_trees,
1630
# Calculate the deltas
1329
1631
for revision in revisions:
1330
1632
if not revision.parent_ids:
1331
1633
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
3293
3548
return basis_id, basis_tree
3296
class InterOtherToRemote(InterRepository):
3297
"""An InterRepository that simply delegates to the 'real' InterRepository
3298
calculated for (source, target._real_repository).
3301
_walk_to_common_revisions_batch_size = 50
3303
def __init__(self, source, target):
3304
InterRepository.__init__(self, source, target)
3305
self._real_inter = None
3308
def is_compatible(source, target):
3309
if isinstance(target, remote.RemoteRepository):
3313
def _ensure_real_inter(self):
3314
if self._real_inter is None:
3315
self.target._ensure_real()
3316
real_target = self.target._real_repository
3317
self._real_inter = InterRepository.get(self.source, real_target)
3318
# Make _real_inter use the RemoteRepository for get_parent_map
3319
self._real_inter.target_get_graph = self.target.get_graph
3320
self._real_inter.target_get_parent_map = self.target.get_parent_map
3322
def copy_content(self, revision_id=None):
3323
self._ensure_real_inter()
3324
self._real_inter.copy_content(revision_id=revision_id)
3326
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3328
self._ensure_real_inter()
3329
return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3330
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
3333
def _get_repo_format_to_test(self):
3337
class InterRemoteToOther(InterRepository):
3339
def __init__(self, source, target):
3340
InterRepository.__init__(self, source, target)
3341
self._real_inter = None
3344
def is_compatible(source, target):
3345
if not isinstance(source, remote.RemoteRepository):
3347
return InterRepository._same_model(source, target)
3349
def _ensure_real_inter(self):
3350
if self._real_inter is None:
3351
self.source._ensure_real()
3352
real_source = self.source._real_repository
3353
self._real_inter = InterRepository.get(real_source, self.target)
3356
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3358
"""See InterRepository.fetch()."""
3359
# Always fetch using the generic streaming fetch code, to allow
3360
# streaming fetching from remote servers.
3361
from bzrlib.fetch import RepoFetcher
3362
fetcher = RepoFetcher(self.target, self.source, revision_id,
3363
pb, find_ghosts, fetch_spec=fetch_spec)
3365
def copy_content(self, revision_id=None):
3366
self._ensure_real_inter()
3367
self._real_inter.copy_content(revision_id=revision_id)
3370
def _get_repo_format_to_test(self):
3375
class InterPackToRemotePack(InterPackRepo):
3376
"""A specialisation of InterPackRepo for a target that is a
3379
This will use the get_parent_map RPC rather than plain readvs, and also
3380
uses an RPC for autopacking.
3383
_walk_to_common_revisions_batch_size = 50
3386
def is_compatible(source, target):
3387
from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3388
if isinstance(source._format, RepositoryFormatPack):
3389
if isinstance(target, remote.RemoteRepository):
3390
target._format._ensure_real()
3391
if isinstance(target._format._custom_format,
3392
RepositoryFormatPack):
3393
if InterRepository._same_model(source, target):
3397
def _autopack(self):
3398
self.target.autopack()
3401
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3403
"""See InterRepository.fetch()."""
3404
if self.target._client._medium._is_remote_before((1, 13)):
3405
# The server won't support the insert_stream RPC, so just use
3406
# regular InterPackRepo logic. This avoids a bug that causes many
3407
# round-trips for small append calls.
3408
return InterPackRepo.fetch(self, revision_id=revision_id, pb=pb,
3409
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
3410
# Always fetch using the generic streaming fetch code, to allow
3411
# streaming fetching into remote servers.
3412
from bzrlib.fetch import RepoFetcher
3413
fetcher = RepoFetcher(self.target, self.source, revision_id,
3414
pb, find_ghosts, fetch_spec=fetch_spec)
3416
def _get_target_pack_collection(self):
3417
return self.target._real_repository._pack_collection
3420
def _get_repo_format_to_test(self):
3424
3551
InterRepository.register_optimiser(InterDifferingSerializer)
3425
3552
InterRepository.register_optimiser(InterSameDataRepository)
3426
3553
InterRepository.register_optimiser(InterWeaveRepo)
3427
3554
InterRepository.register_optimiser(InterKnitRepo)
3428
3555
InterRepository.register_optimiser(InterPackRepo)
3429
InterRepository.register_optimiser(InterOtherToRemote)
3430
InterRepository.register_optimiser(InterRemoteToOther)
3431
InterRepository.register_optimiser(InterPackToRemotePack)
3434
3558
class CopyConverter(object):