71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
72
72
is_inside_or_parent_of_any,
73
minimum_path_selection,
73
74
quotefn, sha_file, split_lines)
74
75
from bzrlib.testament import Testament
75
76
from bzrlib.trace import mutter, note, warning, is_quiet
241
242
" parameter is required for commit().")
243
244
self.bound_branch = None
245
self.any_entries_changed = False
246
self.any_entries_deleted = False
244
247
self.local = local
245
248
self.master_branch = None
246
249
self.master_locked = False
250
self.recursive = recursive
247
251
self.rev_id = None
248
self.specific_files = specific_files
252
if specific_files is not None:
253
self.specific_files = sorted(
254
minimum_path_selection(specific_files))
256
self.specific_files = None
257
self.specific_file_ids = None
249
258
self.allow_pointless = allow_pointless
250
self.recursive = recursive
251
259
self.revprops = revprops
252
260
self.message_callback = message_callback
253
261
self.timestamp = timestamp
280
288
self.config = self.branch.get_config()
282
290
# If provided, ensure the specified files are versioned
283
if specific_files is not None:
284
# Note: We don't actually need the IDs here. This routine
285
# is being called because it raises PathNotVerisonedError
286
# as a side effect of finding the IDs.
291
if self.specific_files is not None:
292
# Note: This routine is being called because it raises
293
# PathNotVersionedError as a side effect of finding the IDs. We
294
# later use the ids we found as input to the working tree
295
# inventory iterator, so we only consider those ids rather than
296
# examining the whole tree again.
287
297
# XXX: Dont we have filter_unversioned to do this more
289
tree.find_ids_across_trees(specific_files,
290
[self.basis_tree, self.work_tree])
299
self.specific_file_ids = tree.find_ids_across_trees(
300
specific_files, [self.basis_tree, self.work_tree])
292
302
# Setup the progress bar. As the number of files that need to be
293
303
# committed in unknown, progress is reported as stages.
383
393
return NullCommitReporter()
384
394
return ReportCommitToLog()
386
def _any_real_changes(self):
387
"""Are there real changes between new_inventory and basis?
389
For trees without rich roots, inv.root.revision changes every commit.
390
But if that is the only change, we want to treat it as though there
393
new_entries = self.builder.new_inventory.iter_entries()
394
basis_entries = self.basis_inv.iter_entries()
395
new_path, new_root_ie = new_entries.next()
396
basis_path, basis_root_ie = basis_entries.next()
398
# This is a copy of InventoryEntry.__eq__ only leaving out .revision
399
def ie_equal_no_revision(this, other):
400
return ((this.file_id == other.file_id)
401
and (this.name == other.name)
402
and (this.symlink_target == other.symlink_target)
403
and (this.text_sha1 == other.text_sha1)
404
and (this.text_size == other.text_size)
405
and (this.text_id == other.text_id)
406
and (this.parent_id == other.parent_id)
407
and (this.kind == other.kind)
408
and (this.executable == other.executable)
409
and (this.reference_revision == other.reference_revision)
411
if not ie_equal_no_revision(new_root_ie, basis_root_ie):
414
for new_ie, basis_ie in zip(new_entries, basis_entries):
415
if new_ie != basis_ie:
418
# No actual changes present
421
396
def _check_pointless(self):
422
397
if self.allow_pointless:
671
647
# recorded in their previous state. For more details, see
672
648
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
673
649
if specific_files:
674
for path, new_ie in self.basis_inv.iter_entries():
675
if new_ie.file_id in self.builder.new_inventory:
650
for path, old_ie in self.basis_inv.iter_entries():
651
if old_ie.file_id in self.builder.new_inventory:
652
# already added - skip.
677
654
if is_inside_any(specific_files, path):
655
# was inside the selected path, if not present it has been
681
self.builder.record_entry_contents(ie, self.parent_invs, path,
658
if old_ie.kind == 'directory':
659
self._next_progress_entry()
660
# not in final inv yet, was not in the selected files, so is an
661
# entry to be preserved unaltered.
663
# Note: specific file commits after a merge are currently
664
# prohibited. This test is for sanity/safety in case it's
665
# required after that changes.
666
if len(self.parents) > 1:
668
delta, version_recorded = self.builder.record_entry_contents(
669
ie, self.parent_invs, path, self.basis_tree, None)
671
self.any_entries_changed = True
673
# note that deletes have occurred
674
if set(self.basis_inv._byid.keys()) - set(self.builder.new_inventory._byid.keys()):
675
self.any_entries_deleted = True
684
676
# Report what was deleted.
685
if self.reporter.is_verbose():
677
if self.any_entries_deleted and self.reporter.is_verbose():
686
678
for path, ie in self.basis_inv.iter_entries():
687
679
if ie.file_id not in self.builder.new_inventory:
688
680
self.reporter.deleted(path)
699
691
deleted_paths = set()
700
692
work_inv = self.work_tree.inventory
701
693
assert work_inv.root is not None
702
entries = work_inv.iter_entries_by_dir()
694
# XXX: Note that entries may have the wrong kind.
695
entries = work_inv.iter_entries_by_dir(
696
specific_file_ids=self.specific_file_ids, yield_parents=True)
703
697
if not self.builder.record_root_entry:
705
699
for path, existing_ie in entries:
717
710
# deleted files matching that filter.
718
711
if is_inside_any(deleted_paths, path):
713
content_summary = self.work_tree.path_content_summary(path)
720
714
if not specific_files or is_inside_any(specific_files, path):
721
if not self.work_tree.has_filename(path):
715
if content_summary[0] == 'missing':
722
716
deleted_paths.add(path)
723
717
self.reporter.missing(path)
724
718
deleted_ids.append(file_id)
727
kind = self.work_tree.kind(file_id)
728
# TODO: specific_files filtering before nested tree processing
729
if kind == 'tree-reference' and self.recursive == 'down':
730
self._commit_nested_tree(file_id, path)
731
except errors.NoSuchFile:
720
# TODO: have the builder do the nested commit just-in-time IF and
722
if content_summary[0] == 'tree-reference':
723
# enforce repository nested tree policy.
724
if (not self.work_tree.supports_tree_reference() or
725
# repository does not support it either.
726
not self.branch.repository._format.supports_tree_reference):
727
content_summary = ('directory',) + content_summary[1:]
728
kind = content_summary[0]
729
# TODO: specific_files filtering before nested tree processing
730
if kind == 'tree-reference':
731
if self.recursive == 'down':
732
nested_revision_id = self._commit_nested_tree(
734
content_summary = content_summary[:3] + (
737
content_summary = content_summary[:3] + (
738
self.work_tree.get_reference_revision(file_id),)
734
740
# Record an entry for this item
735
741
# Note: I don't particularly want to have the existing_ie
736
742
# parameter but the test suite currently (28-Jun-07) breaks
737
743
# without it thanks to a unicode normalisation issue. :-(
738
definitely_changed = kind != existing_ie.kind
744
definitely_changed = kind != existing_ie.kind
739
745
self._record_entry(path, file_id, specific_files, kind, name,
740
parent_id, definitely_changed, existing_ie, report_changes)
746
parent_id, definitely_changed, existing_ie, report_changes,
742
749
# Unversion IDs that were found to be deleted
743
750
self.work_tree.unversion(deleted_ids)
756
763
sub_tree.branch.repository = \
757
764
self.work_tree.branch.repository
759
sub_tree.commit(message=None, revprops=self.revprops,
766
return sub_tree.commit(message=None, revprops=self.revprops,
760
767
recursive=self.recursive,
761
768
message_callback=self.message_callback,
762
769
timestamp=self.timestamp, timezone=self.timezone,
765
772
strict=self.strict, verbose=self.verbose,
766
773
local=self.local, reporter=self.reporter)
767
774
except errors.PointlessCommit:
775
return self.work_tree.get_reference_revision(file_id)
770
777
def _record_entry(self, path, file_id, specific_files, kind, name,
771
parent_id, definitely_changed, existing_ie=None,
772
report_changes=True):
778
parent_id, definitely_changed, existing_ie, report_changes,
773
780
"Record the new inventory entry for a path if any."
774
781
# mutter('check %s {%s}', path, file_id)
775
if (not specific_files or
776
is_inside_or_parent_of_any(specific_files, path)):
777
# mutter('%s selected for commit', path)
778
if definitely_changed or existing_ie is None:
779
ie = inventory.make_entry(kind, name, parent_id, file_id)
781
ie = existing_ie.copy()
782
# mutter('%s selected for commit', path)
783
if definitely_changed or existing_ie is None:
784
ie = inventory.make_entry(kind, name, parent_id, file_id)
784
# mutter('%s not selected for commit', path)
785
if self.basis_inv.has_id(file_id):
786
ie = self.basis_inv[file_id].copy()
788
# this entry is new and not being committed
791
self.builder.record_entry_contents(ie, self.parent_invs,
792
path, self.work_tree)
794
self._report_change(ie, path)
786
ie = existing_ie.copy()
788
delta, version_recorded = self.builder.record_entry_contents(ie,
789
self.parent_invs, path, self.work_tree, content_summary)
791
self.any_entries_changed = True
793
self._report_change(ie, path)
797
796
def _report_change(self, ie, path):