595
591
specific_files = self.specific_files
596
592
mutter("Selecting files for commit with filter %s", specific_files)
597
work_inv = self.work_tree.inventory
598
assert work_inv.root is not None
599
self.pb_entries_total = len(work_inv)
601
594
# Check and warn about old CommitBuilders
602
entries = work_inv.iter_entries()
603
595
if not self.builder.record_root_entry:
604
596
symbol_versioning.warn('CommitBuilders should support recording'
605
597
' the root entry as of bzr 0.10.', DeprecationWarning,
607
599
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()
611
633
deleted_paths = set()
612
for path, new_ie in entries:
613
self._emit_progress_next_entry()
614
file_id = new_ie.file_id
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()
616
647
# Skip files that have been deleted from the working tree.
617
648
# The deleted files/directories are also recorded so they
630
661
kind = self.work_tree.kind(file_id)
662
# TODO: specific_files filtering before nested tree processing
631
663
if kind == 'tree-reference' and self.recursive == 'down':
632
# nested tree: commit in it
633
sub_tree = WorkingTree.open(self.work_tree.abspath(path))
634
# FIXME: be more comprehensive here:
635
# this works when both trees are in --trees repository,
636
# but when both are bound to a different repository,
637
# it fails; a better way of approaching this is to
638
# finally implement the explicit-caches approach design
639
# a while back - RBC 20070306.
640
if (sub_tree.branch.repository.bzrdir.root_transport.base
642
self.work_tree.branch.repository.bzrdir.root_transport.base):
643
sub_tree.branch.repository = \
644
self.work_tree.branch.repository
646
sub_tree.commit(message=None, revprops=self.revprops,
647
recursive=self.recursive,
648
message_callback=self.message_callback,
649
timestamp=self.timestamp, timezone=self.timezone,
650
committer=self.committer,
651
allow_pointless=self.allow_pointless,
652
strict=self.strict, verbose=self.verbose,
653
local=self.local, reporter=self.reporter)
654
except errors.PointlessCommit:
656
if kind != new_ie.kind:
657
new_ie = inventory.make_entry(kind, new_ie.name,
658
new_ie.parent_id, file_id)
664
self._commit_nested_tree(file_id, path)
659
665
except errors.NoSuchFile:
661
# mutter('check %s {%s}', path, file_id)
662
if (not specific_files or
663
is_inside_or_parent_of_any(specific_files, path)):
664
# mutter('%s selected for commit', path)
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)
715
ie = existing_ie.copy()
666
716
ie.revision = None
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()
668
# mutter('%s not selected for commit', path)
669
if self.basis_inv.has_id(file_id):
670
ie = self.basis_inv[file_id].copy()
672
# this entry is new and not being committed
722
# this entry is new and not being committed
674
725
self.builder.record_entry_contents(ie, self.parent_invs,
675
726
path, self.work_tree)
676
# describe the nature of the change that has occurred relative to
677
# the basis inventory.
678
if (self.basis_inv.has_id(ie.file_id)):
679
basis_ie = self.basis_inv[ie.file_id]
682
change = ie.describe_change(basis_ie, ie)
683
if change in (InventoryEntry.RENAMED,
684
InventoryEntry.MODIFIED_AND_RENAMED):
685
old_path = self.basis_inv.id2path(ie.file_id)
686
self.reporter.renamed(change, old_path, path)
688
self.reporter.snapshot_change(change, path)
690
# Unversion IDs that were found to be deleted
691
self.work_tree.unversion(deleted_ids)
693
# If specific files/directories were nominated, it is possible
694
# that some data from outside those needs to be preserved from
695
# the basis tree. For example, if a file x is moved from out of
696
# directory foo into directory bar and the user requests
697
# ``commit foo``, then information about bar/x must also be
700
for path, new_ie in self.basis_inv.iter_entries():
701
if new_ie.file_id in work_inv:
703
if is_inside_any(specific_files, path):
707
self.builder.record_entry_contents(ie, self.parent_invs, path,
710
# Report what was deleted. We could skip this when no deletes are
711
# detected to gain a performance win, but it arguably serves as a
712
# 'safety check' by informing the user whenever anything disappears.
713
for path, ie in self.basis_inv.iter_entries():
714
if ie.file_id not in self.builder.new_inventory:
715
self.reporter.deleted(path)
717
def _emit_progress_set_stage(self, name, show_entries=False):
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):
718
749
"""Set the progress stage and emit an update to the progress bar."""
719
750
self.pb_stage_name = name
720
751
self.pb_stage_count += 1
721
self.pb_entries_show = show_entries
752
self.pb_entries_title = entries_title
753
if entries_title is not None:
723
754
self.pb_entries_count = 0
724
755
self.pb_entries_total = '?'
725
756
self._emit_progress()
727
def _emit_progress_next_entry(self):
728
"""Emit an update to the progress bar and increment the file count."""
758
def _next_progress_entry(self):
759
"""Emit an update to the progress bar and increment the entry count."""
729
760
self.pb_entries_count += 1
730
761
self._emit_progress()
732
763
def _emit_progress(self):
733
if self.pb_entries_show:
734
text = "%s [Entry %d/%s] - Stage" % (self.pb_stage_name,
735
self.pb_entries_count,str(self.pb_entries_total))
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))
737
773
text = "%s - Stage" % (self.pb_stage_name)
738
774
self.pb.update(text, self.pb_stage_count, self.pb_stage_total)