591
592
specific_files = self.specific_files
592
593
mutter("Selecting files for commit with filter %s", specific_files)
594
work_inv = self.work_tree.inventory
595
assert work_inv.root is not None
596
self.pb_entries_total = len(work_inv)
594
598
# Check and warn about old CommitBuilders
599
entries = work_inv.iter_entries()
595
600
if not self.builder.record_root_entry:
596
601
symbol_versioning.warn('CommitBuilders should support recording'
597
602
' the root entry as of bzr 0.10.', DeprecationWarning,
599
604
self.builder.new_inventory.add(self.basis_inv.root.copy())
601
# Build the new inventory
602
self._populate_from_inventory(specific_files)
604
# If specific files are selected, then all un-selected files must be
605
# recorded in their previous state. For more details, see
606
# https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
608
for path, new_ie in self.basis_inv.iter_entries():
609
if new_ie.file_id in self.builder.new_inventory:
611
if is_inside_any(specific_files, path):
615
self.builder.record_entry_contents(ie, self.parent_invs, path,
618
# Report what was deleted. We could skip this when no deletes are
619
# detected to gain a performance win, but it arguably serves as a
620
# 'safety check' by informing the user whenever anything disappears.
621
for path, ie in self.basis_inv.iter_entries():
622
if ie.file_id not in self.builder.new_inventory:
623
self.reporter.deleted(path)
625
def _populate_from_inventory(self, specific_files):
626
"""Populate the CommitBuilder by walking the working tree inventory."""
628
# raise an exception as soon as we find a single unknown.
629
for unknown in self.work_tree.unknowns():
630
raise StrictCommitFailed()
633
608
deleted_paths = set()
634
work_inv = self.work_tree.inventory
635
assert work_inv.root is not None
636
entries = work_inv.iter_entries()
637
if not self.builder.record_root_entry:
639
for path, existing_ie in entries:
640
file_id = existing_ie.file_id
641
name = existing_ie.name
642
parent_id = existing_ie.parent_id
643
kind = existing_ie.kind
644
if kind == 'directory':
645
self._next_progress_entry()
609
for path, new_ie in entries:
610
self._emit_progress_next_entry()
611
file_id = new_ie.file_id
647
613
# Skip files that have been deleted from the working tree.
648
614
# The deleted files/directories are also recorded so they
661
627
kind = self.work_tree.kind(file_id)
662
# TODO: specific_files filtering before nested tree processing
663
628
if kind == 'tree-reference' and self.recursive == 'down':
664
self._commit_nested_tree(file_id, path)
629
# nested tree: commit in it
630
sub_tree = WorkingTree.open(self.work_tree.abspath(path))
631
# FIXME: be more comprehensive here:
632
# this works when both trees are in --trees repository,
633
# but when both are bound to a different repository,
634
# it fails; a better way of approaching this is to
635
# finally implement the explicit-caches approach design
636
# a while back - RBC 20070306.
637
if (sub_tree.branch.repository.bzrdir.root_transport.base
639
self.work_tree.branch.repository.bzrdir.root_transport.base):
640
sub_tree.branch.repository = \
641
self.work_tree.branch.repository
643
sub_tree.commit(message=None, revprops=self.revprops,
644
recursive=self.recursive,
645
message_callback=self.message_callback,
646
timestamp=self.timestamp, timezone=self.timezone,
647
committer=self.committer,
648
allow_pointless=self.allow_pointless,
649
strict=self.strict, verbose=self.verbose,
650
local=self.local, reporter=self.reporter)
651
except errors.PointlessCommit:
653
if kind != new_ie.kind:
654
new_ie = inventory.make_entry(kind, new_ie.name,
655
new_ie.parent_id, file_id)
665
656
except errors.NoSuchFile:
668
# Record an entry for this item
669
# Note: I don't particularly want to have the existing_ie
670
# parameter but the test suite currently (28-Jun-07) breaks
671
# without it thanks to a unicode normalisation issue. :-(
672
definitely_changed = kind != existing_ie.kind
673
self._record_entry(path, file_id, specific_files, kind, name,
674
parent_id, definitely_changed, existing_ie)
676
# Unversion IDs that were found to be deleted
677
self.work_tree.unversion(deleted_ids)
679
def _commit_nested_tree(self, file_id, path):
680
"Commit a nested tree."
681
sub_tree = self.work_tree.get_nested_tree(file_id, path)
682
# FIXME: be more comprehensive here:
683
# this works when both trees are in --trees repository,
684
# but when both are bound to a different repository,
685
# it fails; a better way of approaching this is to
686
# finally implement the explicit-caches approach design
687
# a while back - RBC 20070306.
688
if (sub_tree.branch.repository.bzrdir.root_transport.base
690
self.work_tree.branch.repository.bzrdir.root_transport.base):
691
sub_tree.branch.repository = \
692
self.work_tree.branch.repository
694
sub_tree.commit(message=None, revprops=self.revprops,
695
recursive=self.recursive,
696
message_callback=self.message_callback,
697
timestamp=self.timestamp, timezone=self.timezone,
698
committer=self.committer,
699
allow_pointless=self.allow_pointless,
700
strict=self.strict, verbose=self.verbose,
701
local=self.local, reporter=self.reporter)
702
except errors.PointlessCommit:
705
def _record_entry(self, path, file_id, specific_files, kind, name,
706
parent_id, definitely_changed, existing_ie=None):
707
"Record the new inventory entry for a path if any."
708
# mutter('check %s {%s}', path, file_id)
709
if (not specific_files or
710
is_inside_or_parent_of_any(specific_files, path)):
711
# mutter('%s selected for commit', path)
712
if definitely_changed or existing_ie is None:
713
ie = inventory.make_entry(kind, name, parent_id, file_id)
658
# mutter('check %s {%s}', path, file_id)
659
if (not specific_files or
660
is_inside_or_parent_of_any(specific_files, path)):
661
# mutter('%s selected for commit', path)
665
# mutter('%s not selected for commit', path)
666
if self.basis_inv.has_id(file_id):
667
ie = self.basis_inv[file_id].copy()
715
ie = existing_ie.copy()
718
# mutter('%s not selected for commit', path)
719
if self.basis_inv.has_id(file_id):
720
ie = self.basis_inv[file_id].copy()
722
# this entry is new and not being committed
669
# this entry is new and not being committed
725
671
self.builder.record_entry_contents(ie, self.parent_invs,
726
672
path, self.work_tree)
727
self._report_change(ie, path)
730
def _report_change(self, ie, path):
731
"""Report a change to the user.
733
The change that has occurred is described relative to the basis
736
if (self.basis_inv.has_id(ie.file_id)):
737
basis_ie = self.basis_inv[ie.file_id]
740
change = ie.describe_change(basis_ie, ie)
741
if change in (InventoryEntry.RENAMED,
742
InventoryEntry.MODIFIED_AND_RENAMED):
743
old_path = self.basis_inv.id2path(ie.file_id)
744
self.reporter.renamed(change, old_path, path)
746
self.reporter.snapshot_change(change, path)
748
def _set_progress_stage(self, name, entries_title=None):
673
# describe the nature of the change that has occurred relative to
674
# the basis inventory.
675
if (self.basis_inv.has_id(ie.file_id)):
676
basis_ie = self.basis_inv[ie.file_id]
679
change = ie.describe_change(basis_ie, ie)
680
if change in (InventoryEntry.RENAMED,
681
InventoryEntry.MODIFIED_AND_RENAMED):
682
old_path = self.basis_inv.id2path(ie.file_id)
683
self.reporter.renamed(change, old_path, path)
685
self.reporter.snapshot_change(change, path)
687
# Unversion IDs that were found to be deleted
688
self.work_tree.unversion(deleted_ids)
690
# If specific files/directories were nominated, it is possible
691
# that some data from outside those needs to be preserved from
692
# the basis tree. For example, if a file x is moved from out of
693
# directory foo into directory bar and the user requests
694
# ``commit foo``, then information about bar/x must also be
697
for path, new_ie in self.basis_inv.iter_entries():
698
if new_ie.file_id in work_inv:
700
if is_inside_any(specific_files, path):
704
self.builder.record_entry_contents(ie, self.parent_invs, path,
707
# Report what was deleted. We could skip this when no deletes are
708
# detected to gain a performance win, but it arguably serves as a
709
# 'safety check' by informing the user whenever anything disappears.
710
for path, ie in self.basis_inv.iter_entries():
711
if ie.file_id not in self.builder.new_inventory:
712
self.reporter.deleted(path)
714
def _emit_progress_set_stage(self, name, show_entries=False):
749
715
"""Set the progress stage and emit an update to the progress bar."""
750
716
self.pb_stage_name = name
751
717
self.pb_stage_count += 1
752
self.pb_entries_title = entries_title
753
if entries_title is not None:
718
self.pb_entries_show = show_entries
754
720
self.pb_entries_count = 0
755
721
self.pb_entries_total = '?'
756
722
self._emit_progress()
758
def _next_progress_entry(self):
759
"""Emit an update to the progress bar and increment the entry count."""
724
def _emit_progress_next_entry(self):
725
"""Emit an update to the progress bar and increment the file count."""
760
726
self.pb_entries_count += 1
761
727
self._emit_progress()
763
729
def _emit_progress(self):
764
if self.pb_entries_title:
765
if self.pb_entries_total == '?':
766
text = "%s [%s %d] - Stage" % (self.pb_stage_name,
767
self.pb_entries_title, self.pb_entries_count)
769
text = "%s [%s %d/%s] - Stage" % (self.pb_stage_name,
770
self.pb_entries_title, self.pb_entries_count,
771
str(self.pb_entries_total))
730
if self.pb_entries_show:
731
text = "%s [Entry %d/%s] - Stage" % (self.pb_stage_name,
732
self.pb_entries_count,str(self.pb_entries_total))
773
734
text = "%s - Stage" % (self.pb_stage_name)
774
735
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)