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