232
229
:return: The inventory id in the repository, which can be used with
233
230
repository.get_inventory.
235
if self.new_inventory is None:
236
# an inventory delta was accumulated without creating a new
238
basis_id = self.basis_delta_revision
239
# We ignore the 'inventory' returned by add_inventory_by_delta
240
# because self.new_inventory is used to hint to the rest of the
241
# system what code path was taken
242
self.inv_sha1, _ = self.repository.add_inventory_by_delta(
243
basis_id, self._basis_delta, self._new_revision_id,
246
if self.new_inventory.root is None:
247
raise AssertionError('Root entry should be supplied to'
248
' record_entry_contents, as of bzr 0.10.')
249
self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
250
self.new_inventory.revision_id = self._new_revision_id
251
self.inv_sha1 = self.repository.add_inventory(
252
self._new_revision_id,
232
# an inventory delta was accumulated without creating a new
234
basis_id = self.basis_delta_revision
235
self.inv_sha1, self._new_inventory = self.repository.add_inventory_by_delta(
236
basis_id, self._basis_delta, self._new_revision_id,
256
238
return self._new_revision_id
258
def _check_root(self, ie, parent_invs, tree):
259
"""Helper for record_entry_contents.
261
:param ie: An entry being added.
262
:param parent_invs: The inventories of the parent revisions of the
264
:param tree: The tree that is being committed.
266
# In this revision format, root entries have no knit or weave When
267
# serializing out to disk and back in root.revision is always
269
ie.revision = self._new_revision_id
271
240
def _require_root_change(self, tree):
272
241
"""Enforce an appropriate root object change.
311
280
def get_basis_delta(self):
312
281
"""Return the complete inventory delta versus the basis inventory.
314
This has been built up with the calls to record_delete and
315
record_entry_contents.
317
283
:return: An inventory delta, suitable for use with apply_delta, or
318
284
Repository.add_inventory_by_delta, etc.
320
286
return self._basis_delta
322
def record_delete(self, path, file_id):
323
"""Record that a delete occured against a basis tree.
325
This is an optional API - when used it adds items to the basis_delta
326
being accumulated by the commit builder.
328
:param path: The path of the thing deleted.
329
:param file_id: The file id that was deleted.
331
delta = (path, None, file_id, None)
332
self._basis_delta.append(delta)
333
self._any_changes = True
336
def record_entry_contents(self, ie, parent_invs, path, tree,
338
"""Record the content of ie from tree into the commit if needed.
340
Side effect: sets ie.revision when unchanged
342
:param ie: An inventory entry present in the commit.
343
:param parent_invs: The inventories of the parent revisions of the
345
:param path: The path the entry is at in the tree.
346
:param tree: The tree which contains this entry and should be used to
348
:param content_summary: Summary data from the tree about the paths
349
content - stat, length, exec, sha/link target. This is only
350
accessed when the entry has a revision of None - that is when it is
351
a candidate to commit.
352
:return: A tuple (change_delta, version_recorded, fs_hash).
353
change_delta is an inventory_delta change for this entry against
354
the basis tree of the commit, or None if no change occured against
356
version_recorded is True if a new version of the entry has been
357
recorded. For instance, committing a merge where a file was only
358
changed on the other side will return (delta, False).
359
fs_hash is either None, or the hash details for the path (currently
360
a tuple of the contents sha1 and the statvalue returned by
361
tree.get_file_with_stat()).
363
if self.new_inventory.root is None:
364
if ie.parent_id is not None:
365
raise errors.RootMissing()
366
self._check_root(ie, parent_invs, tree)
367
if ie.revision is None:
368
kind = content_summary[0]
370
# ie is carried over from a prior commit
372
# XXX: repository specific check for nested tree support goes here - if
373
# the repo doesn't want nested trees we skip it ?
374
if (kind == 'tree-reference' and
375
not self.repository._format.supports_tree_reference):
376
# mismatch between commit builder logic and repository:
377
# this needs the entry creation pushed down into the builder.
378
raise NotImplementedError('Missing repository subtree support.')
379
self.new_inventory.add(ie)
381
# TODO: slow, take it out of the inner loop.
383
basis_inv = parent_invs[0]
385
basis_inv = Inventory(root_id=None)
387
# ie.revision is always None if the InventoryEntry is considered
388
# for committing. We may record the previous parents revision if the
389
# content is actually unchanged against a sole head.
390
if ie.revision is not None:
391
if not self._versioned_root and path == '':
392
# repositories that do not version the root set the root's
393
# revision to the new commit even when no change occurs (more
394
# specifically, they do not record a revision on the root; and
395
# the rev id is assigned to the root during deserialisation -
396
# this masks when a change may have occurred against the basis.
397
# To match this we always issue a delta, because the revision
398
# of the root will always be changing.
399
if basis_inv.has_id(ie.file_id):
400
delta = (basis_inv.id2path(ie.file_id), path,
404
delta = (None, path, ie.file_id, ie)
405
self._basis_delta.append(delta)
406
return delta, False, None
408
# we don't need to commit this, because the caller already
409
# determined that an existing revision of this file is
410
# appropriate. If it's not being considered for committing then
411
# it and all its parents to the root must be unaltered so
412
# no-change against the basis.
413
if ie.revision == self._new_revision_id:
414
raise AssertionError("Impossible situation, a skipped "
415
"inventory entry (%r) claims to be modified in this "
416
"commit (%r).", (ie, self._new_revision_id))
417
return None, False, None
418
# XXX: Friction: parent_candidates should return a list not a dict
419
# so that we don't have to walk the inventories again.
420
parent_candidate_entries = ie.parent_candidates(parent_invs)
421
head_set = self._heads(ie.file_id, parent_candidate_entries)
423
for inv in parent_invs:
424
if inv.has_id(ie.file_id):
425
old_rev = inv[ie.file_id].revision
426
if old_rev in head_set:
427
heads.append(inv[ie.file_id].revision)
428
head_set.remove(inv[ie.file_id].revision)
431
# now we check to see if we need to write a new record to the
433
# We write a new entry unless there is one head to the ancestors, and
434
# the kind-derived content is unchanged.
436
# Cheapest check first: no ancestors, or more the one head in the
437
# ancestors, we write a new node.
441
# There is a single head, look it up for comparison
442
parent_entry = parent_candidate_entries[heads[0]]
443
# if the non-content specific data has changed, we'll be writing a
445
if (parent_entry.parent_id != ie.parent_id or
446
parent_entry.name != ie.name):
448
# now we need to do content specific checks:
450
# if the kind changed the content obviously has
451
if kind != parent_entry.kind:
453
# Stat cache fingerprint feedback for the caller - None as we usually
454
# don't generate one.
457
if content_summary[2] is None:
458
raise ValueError("Files must not have executable = None")
460
# We can't trust a check of the file length because of content
462
if (# if the exec bit has changed we have to store:
463
parent_entry.executable != content_summary[2]):
465
elif parent_entry.text_sha1 == content_summary[3]:
466
# all meta and content is unchanged (using a hash cache
467
# hit to check the sha)
468
ie.revision = parent_entry.revision
469
ie.text_size = parent_entry.text_size
470
ie.text_sha1 = parent_entry.text_sha1
471
ie.executable = parent_entry.executable
472
return self._get_delta(ie, basis_inv, path), False, None
474
# Either there is only a hash change(no hash cache entry,
475
# or same size content change), or there is no change on
477
# Provide the parent's hash to the store layer, so that the
478
# content is unchanged we will not store a new node.
479
nostore_sha = parent_entry.text_sha1
481
# We want to record a new node regardless of the presence or
482
# absence of a content change in the file.
484
ie.executable = content_summary[2]
485
file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
487
text = file_obj.read()
491
ie.text_sha1, ie.text_size = self._add_text_to_weave(
492
ie.file_id, text, heads, nostore_sha)
493
# Let the caller know we generated a stat fingerprint.
494
fingerprint = (ie.text_sha1, stat_value)
495
except errors.ExistingContent:
496
# Turns out that the file content was unchanged, and we were
497
# only going to store a new node if it was changed. Carry over
499
ie.revision = parent_entry.revision
500
ie.text_size = parent_entry.text_size
501
ie.text_sha1 = parent_entry.text_sha1
502
ie.executable = parent_entry.executable
503
return self._get_delta(ie, basis_inv, path), False, None
504
elif kind == 'directory':
506
# all data is meta here, nothing specific to directory, so
508
ie.revision = parent_entry.revision
509
return self._get_delta(ie, basis_inv, path), False, None
510
self._add_text_to_weave(ie.file_id, '', heads, None)
511
elif kind == 'symlink':
512
current_link_target = content_summary[3]
514
# symlink target is not generic metadata, check if it has
516
if current_link_target != parent_entry.symlink_target:
519
# unchanged, carry over.
520
ie.revision = parent_entry.revision
521
ie.symlink_target = parent_entry.symlink_target
522
return self._get_delta(ie, basis_inv, path), False, None
523
ie.symlink_target = current_link_target
524
self._add_text_to_weave(ie.file_id, '', heads, None)
525
elif kind == 'tree-reference':
527
if content_summary[3] != parent_entry.reference_revision:
530
# unchanged, carry over.
531
ie.reference_revision = parent_entry.reference_revision
532
ie.revision = parent_entry.revision
533
return self._get_delta(ie, basis_inv, path), False, None
534
ie.reference_revision = content_summary[3]
535
if ie.reference_revision is None:
536
raise AssertionError("invalid content_summary for nested tree: %r"
537
% (content_summary,))
538
self._add_text_to_weave(ie.file_id, '', heads, None)
540
raise NotImplementedError('unknown kind')
541
ie.revision = self._new_revision_id
542
# The initial commit adds a root directory, but this in itself is not
543
# a worthwhile commit.
544
if (self.basis_delta_revision != _mod_revision.NULL_REVISION or
546
self._any_changes = True
547
return self._get_delta(ie, basis_inv, path), True, fingerprint
549
288
def record_iter_changes(self, tree, basis_revision_id, iter_changes,
550
289
_entry_factory=entry_factory):
551
290
"""Record a new tree via iter_changes.