309
317
if self.config is None:
310
318
self.config = self.branch.get_config()
312
# If provided, ensure the specified files are versioned
313
if self.specific_files is not None:
314
# Note: This routine is being called because it raises
315
# PathNotVersionedError as a side effect of finding the IDs. We
316
# later use the ids we found as input to the working tree
317
# inventory iterator, so we only consider those ids rather than
318
# examining the whole tree again.
319
# XXX: Dont we have filter_unversioned to do this more
321
self.specific_file_ids = tree.find_ids_across_trees(
322
specific_files, [self.basis_tree, self.work_tree])
320
self._set_specific_file_ids()
324
322
# Setup the progress bar. As the number of files that need to be
325
323
# committed in unknown, progress is reported as stages.
430
425
# The initial commit adds a root directory, but this in itself is not
431
426
# a worthwhile commit.
432
427
if (self.basis_revid == revision.NULL_REVISION and
433
len(self.builder.new_inventory) == 1):
428
((self.builder.new_inventory is not None and
429
len(self.builder.new_inventory) == 1) or
430
len(self.builder._basis_delta) == 1)):
434
431
raise PointlessCommit()
435
# If length == 1, then we only have the root entry. Which means
436
# that there is no real difference (only the root could be different)
437
# unless deletes occured, in which case the length is irrelevant.
438
if (self.any_entries_deleted or
439
(len(self.builder.new_inventory) != 1 and
440
self.builder.any_changes())):
432
if self.builder.any_changes():
442
434
raise PointlessCommit()
644
638
def _update_builder_with_changes(self):
645
639
"""Update the commit builder with the data about what has changed.
647
# Build the revision inventory.
649
# This starts by creating a new empty inventory. Depending on
650
# which files are selected for commit, and what is present in the
651
# current tree, the new inventory is populated. inventory entries
652
# which are candidates for modification have their revision set to
653
# None; inventory entries that are carried over untouched have their
654
# revision set to their prior value.
656
# ESEPARATIONOFCONCERNS: this function is diffing and using the diff
657
# results to create a new inventory at the same time, which results
658
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
661
641
exclude = self.exclude
662
642
specific_files = self.specific_files or []
663
643
mutter("Selecting files for commit with filter %s", specific_files)
665
# Build the new inventory
666
self._populate_from_inventory()
646
if self.use_record_iter_changes:
647
iter_changes = self.work_tree.iter_changes(self.basis_tree)
648
iter_changes = self._filter_iter_changes(iter_changes)
649
for file_id, path, fs_hash in self.builder.record_iter_changes(
650
self.work_tree, self.basis_revid, iter_changes):
651
self.work_tree._observed_sha1(file_id, path, fs_hash)
653
# Build the new inventory
654
self._populate_from_inventory()
655
self._record_unselected()
656
self._report_and_accumulate_deletes()
658
def _filter_iter_changes(self, iter_changes):
659
"""Process iter_changes.
661
This method reports on the changes in iter_changes to the user, and
662
converts 'missing' entries in the iter_changes iterator to 'deleted'
663
entries. 'missing' entries have their
665
:param iter_changes: An iter_changes to process.
666
:return: A generator of changes.
668
reporter = self.reporter
669
report_changes = reporter.is_verbose()
671
for change in iter_changes:
673
old_path = change[1][0]
674
new_path = change[1][1]
675
versioned = change[3][1]
677
versioned = change[3][1]
678
if kind is None and versioned:
681
reporter.missing(new_path)
682
deleted_ids.append(change[0])
683
# Reset the new path (None) and new versioned flag (False)
684
change = (change[0], (change[1][0], None), change[2],
685
(change[3][0], False)) + change[4:]
686
elif kind == 'tree-reference':
687
if self.recursive == 'down':
688
self._commit_nested_tree(change[0], change[1][1])
689
if change[3][0] or change[3][1]:
693
reporter.deleted(old_path)
694
elif old_path is None:
695
reporter.snapshot_change('added', new_path)
696
elif old_path != new_path:
697
reporter.renamed('renamed', old_path, new_path)
700
self.work_tree.branch.repository._format.rich_root_data):
701
# Don't report on changes to '' in non rich root
703
reporter.snapshot_change('modified', new_path)
704
self._next_progress_entry()
705
# Unversion IDs that were found to be deleted
706
self.work_tree.unversion(deleted_ids)
708
def _record_unselected(self):
668
709
# If specific files are selected, then all un-selected files must be
669
710
# recorded in their previous state. For more details, see
670
711
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
671
if specific_files or exclude:
712
if self.specific_files or self.exclude:
713
specific_files = self.specific_files or []
672
714
for path, old_ie in self.basis_inv.iter_entries():
673
715
if old_ie.file_id in self.builder.new_inventory:
674
716
# already added - skip.
676
718
if (is_inside_any(specific_files, path)
677
and not is_inside_any(exclude, path)):
719
and not is_inside_any(self.exclude, path)):
678
720
# was inside the selected path, and not excluded - if not
679
721
# present it has been deleted so skip.
681
723
# From here down it was either not selected, or was excluded:
682
if old_ie.kind == 'directory':
683
self._next_progress_entry()
684
724
# We preserve the entry unaltered.
685
725
ie = old_ie.copy()
686
726
# Note: specific file commits after a merge are currently
717
755
self.builder.record_delete(path, file_id)
718
756
self.reporter.deleted(path)
720
def _populate_from_inventory(self):
721
"""Populate the CommitBuilder by walking the working tree inventory."""
758
def _check_strict(self):
759
# XXX: when we use iter_changes this would likely be faster if
760
# iter_changes would check for us (even in the presence of
723
763
# raise an exception as soon as we find a single unknown.
724
764
for unknown in self.work_tree.unknowns():
725
765
raise StrictCommitFailed()
767
def _populate_from_inventory(self):
768
"""Populate the CommitBuilder by walking the working tree inventory."""
769
# Build the revision inventory.
771
# This starts by creating a new empty inventory. Depending on
772
# which files are selected for commit, and what is present in the
773
# current tree, the new inventory is populated. inventory entries
774
# which are candidates for modification have their revision set to
775
# None; inventory entries that are carried over untouched have their
776
# revision set to their prior value.
778
# ESEPARATIONOFCONCERNS: this function is diffing and using the diff
779
# results to create a new inventory at the same time, which results
780
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
727
783
specific_files = self.specific_files
728
784
exclude = self.exclude
729
785
report_changes = self.reporter.is_verbose()
874
929
InventoryEntry.MODIFIED_AND_RENAMED):
875
930
old_path = self.basis_inv.id2path(ie.file_id)
876
931
self.reporter.renamed(change, old_path, path)
932
self._next_progress_entry()
934
if change == 'unchanged':
878
936
self.reporter.snapshot_change(change, path)
937
self._next_progress_entry()
880
def _set_progress_stage(self, name, entries_title=None):
939
def _set_progress_stage(self, name, counter=False):
881
940
"""Set the progress stage and emit an update to the progress bar."""
882
941
self.pb_stage_name = name
883
942
self.pb_stage_count += 1
884
self.pb_entries_title = entries_title
885
if entries_title is not None:
886
944
self.pb_entries_count = 0
887
self.pb_entries_total = '?'
946
self.pb_entries_count = None
888
947
self._emit_progress()
890
949
def _next_progress_entry(self):
893
952
self._emit_progress()
895
954
def _emit_progress(self):
896
if self.pb_entries_title:
897
if self.pb_entries_total == '?':
898
text = "%s [%s %d] - Stage" % (self.pb_stage_name,
899
self.pb_entries_title, self.pb_entries_count)
901
text = "%s [%s %d/%s] - Stage" % (self.pb_stage_name,
902
self.pb_entries_title, self.pb_entries_count,
903
str(self.pb_entries_total))
955
if self.pb_entries_count is not None:
956
text = "%s [%d] - Stage" % (self.pb_stage_name,
957
self.pb_entries_count)
905
text = "%s - Stage" % (self.pb_stage_name)
959
text = "%s - Stage" % (self.pb_stage_name, )
906
960
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
962
def _set_specific_file_ids(self):
963
"""populate self.specific_file_ids if we will use it."""
964
if not self.use_record_iter_changes:
965
# If provided, ensure the specified files are versioned
966
if self.specific_files is not None:
967
# Note: This routine is being called because it raises
968
# PathNotVersionedError as a side effect of finding the IDs. We
969
# later use the ids we found as input to the working tree
970
# inventory iterator, so we only consider those ids rather than
971
# examining the whole tree again.
972
# XXX: Dont we have filter_unversioned to do this more
974
self.specific_file_ids = tree.find_ids_across_trees(
975
self.specific_files, [self.basis_tree, self.work_tree])
977
self.specific_file_ids = None