592
589
specific_files = self.specific_files
593
590
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)
598
592
# Check and warn about old CommitBuilders
599
entries = work_inv.iter_entries()
593
root_added_already = False
600
594
if not self.builder.record_root_entry:
601
595
symbol_versioning.warn('CommitBuilders should support recording'
602
596
' the root entry as of bzr 0.10.', DeprecationWarning,
604
598
self.builder.new_inventory.add(self.basis_inv.root.copy())
608
deleted_paths = set()
609
for path, new_ie in entries:
610
self._emit_progress_next_entry()
611
file_id = new_ie.file_id
613
# Skip files that have been deleted from the working tree.
614
# The deleted files/directories are also recorded so they
615
# can be explicitly unversioned later. Note that when a
616
# filter of specific files is given, we must only skip/record
617
# deleted files matching that filter.
618
if is_inside_any(deleted_paths, path):
620
if not specific_files or is_inside_any(specific_files, path):
621
if not self.work_tree.has_filename(path):
622
deleted_paths.add(path)
623
self.reporter.missing(path)
624
deleted_ids.append(file_id)
627
kind = self.work_tree.kind(file_id)
628
if kind == 'tree-reference' and self.recursive == 'down':
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)
656
except errors.NoSuchFile:
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()
669
# this entry is new and not being committed
671
self.builder.record_entry_contents(ie, self.parent_invs,
672
path, self.work_tree)
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)
599
root_added_already = True
601
# Build the new inventory. _populate_from_tree() is the preferred
602
# direction here but it doesn't support multiple parents yet,
603
# it triggers a unicode normalisation issue in the test suite and
604
# it needs more testing.
605
populator = self._populate_from_inventory
606
#if len(self.parents) == 1:
607
# populator = self._populate_from_tree
608
populator(specific_files, root_added_already)
690
610
# If specific files/directories were nominated, it is possible
691
611
# that some data from outside those needs to be preserved from
711
631
if ie.file_id not in self.builder.new_inventory:
712
632
self.reporter.deleted(path)
714
def _emit_progress_set_stage(self, name, show_entries=False):
634
def _populate_from_inventory(self, specific_files, root_added_already):
635
"""Populate the CommitBuilder by walking the working tree inventory."""
637
# raise an exception as soon as we find a single unknown.
638
for unknown in self.work_tree.unknowns():
639
raise StrictCommitFailed()
642
deleted_paths = set()
643
work_inv = self.work_tree.inventory
644
assert work_inv.root is not None
645
entries = work_inv.iter_entries()
646
if root_added_already:
648
for path, new_ie in entries:
649
file_id = new_ie.file_id
651
parent_id = new_ie.parent_id
653
if kind == 'directory':
654
self._emit_progress_next_entry()
656
# Skip files that have been deleted from the working tree.
657
# The deleted files/directories are also recorded so they
658
# can be explicitly unversioned later. Note that when a
659
# filter of specific files is given, we must only skip/record
660
# deleted files matching that filter.
661
if is_inside_any(deleted_paths, path):
663
if not specific_files or is_inside_any(specific_files, path):
664
if not self.work_tree.has_filename(path):
665
deleted_paths.add(path)
666
self.reporter.missing(path)
667
deleted_ids.append(file_id)
670
kind = self.work_tree.kind(file_id)
671
# TODO: specific_files filtering before nested tree processing?
672
if kind == 'tree-reference' and self.recursive == 'down':
673
self._commit_nested_tree(path)
674
except errors.NoSuchFile:
677
# Record an entry for this item
678
# Note: I don't particularly want to have the existing_ie
679
# parameter but the test suite currently (28-Jun-07) breaks
680
# without it thanks to a unicode normalisation issue. :-(
681
definitely_changed = kind != new_ie.kind
682
self._record_entry(path, file_id, specific_files, kind, name,
683
parent_id, definitely_changed, new_ie)
685
# Unversion IDs that were found to be deleted
686
self.work_tree.unversion(deleted_ids)
688
def _populate_from_tree(self, specific_files, root_added_already):
689
"""Populate the CommitBuilder by walking the working tree."""
690
# Until trees supports an "iter_commitable" iterator that
691
# understand multiple parents, this assertion is required.
692
assert len(self.parents) == 1
695
deleted_paths = set()
696
entries = self.work_tree._iter_changes(self.basis_tree,
697
include_unchanged=True, want_unversioned=True,
698
require_versioned=False)
699
if root_added_already:
701
for entry in entries:
702
(file_id, paths, changed_content, versioned_flags, parents, names,
703
kinds, executables) = entry
705
if kind == 'directory':
706
self._emit_progress_next_entry()
708
# Skip unknowns unless strict mode
709
if versioned_flags == (False,False):
711
raise StrictCommitFailed()
715
# Skip missing files (may auto-delete these one day)
716
# Note that when a filter of specific files is given, we must
717
# only skip/record deleted files matching that filter.
720
if is_inside_any(deleted_paths, path):
722
if not specific_files or is_inside_any(specific_files, path):
723
deleted_paths.add(path)
724
self.reporter.missing(path)
725
deleted_ids.append(file_id)
727
# It's missing but still needs to be recorded
734
parent = parents[index]
737
# TODO: specific_files filtering before nested tree processing?
738
# TODO: keep track of nested trees and don't recurse into them?
739
if kind == 'tree-reference' and self.recursive == 'down':
740
self._commit_nested_tree(path)
742
# Record an entry for this item
743
self._record_entry(path, file_id, specific_files, kind,
744
name, parent, changed_content, None)
745
# Note: If the iterator passed back the sha here we could
746
# set it and the executable info *now* into the inventory entry
747
# saving look ups later
749
# Unversion IDs that were found to be deleted
750
# Note: the logic above collects some IDs already gone so
751
# we walk the list and trap the exception
752
for id in deleted_ids:
754
self.work_tree.unversion([id])
758
def _commit_nested_tree(self, path):
759
"Commit a nested tree."
760
sub_tree = WorkingTree.open(self.work_tree.abspath(path))
761
# FIXME: be more comprehensive here:
762
# this works when both trees are in --trees repository,
763
# but when both are bound to a different repository,
764
# it fails; a better way of approaching this is to
765
# finally implement the explicit-caches approach design
766
# a while back - RBC 20070306.
767
if (sub_tree.branch.repository.bzrdir.root_transport.base
769
self.work_tree.branch.repository.bzrdir.root_transport.base):
770
sub_tree.branch.repository = \
771
self.work_tree.branch.repository
773
sub_tree.commit(message=None, revprops=self.revprops,
774
recursive=self.recursive,
775
message_callback=self.message_callback,
776
timestamp=self.timestamp, timezone=self.timezone,
777
committer=self.committer,
778
allow_pointless=self.allow_pointless,
779
strict=self.strict, verbose=self.verbose,
780
local=self.local, reporter=self.reporter)
781
except errors.PointlessCommit:
784
def _record_entry(self, path, file_id, specific_files, kind, name,
785
parent_id, definitely_changed, existing_ie=None):
786
"Record the new inventory entry for a path if any."
787
# mutter('check %s {%s}', path, file_id)
788
if (not specific_files or
789
is_inside_or_parent_of_any(specific_files, path)):
790
# mutter('%s selected for commit', path)
791
if definitely_changed or existing_ie is None:
792
ie = inventory.make_entry(kind, name, parent_id, file_id)
794
ie = existing_ie.copy()
797
# mutter('%s not selected for commit', path)
798
if self.basis_inv.has_id(file_id):
799
ie = self.basis_inv[file_id].copy()
801
# this entry is new and not being committed
804
self.builder.record_entry_contents(ie, self.parent_invs,
805
path, self.work_tree)
806
self._report_change(ie, path)
809
def _report_change(self, ie, path):
810
"""Report a change to the user.
812
The change that has occurred is described relative to the basis
815
if (self.basis_inv.has_id(ie.file_id)):
816
basis_ie = self.basis_inv[ie.file_id]
819
change = ie.describe_change(basis_ie, ie)
820
if change in (InventoryEntry.RENAMED,
821
InventoryEntry.MODIFIED_AND_RENAMED):
822
old_path = self.basis_inv.id2path(ie.file_id)
823
self.reporter.renamed(change, old_path, path)
825
self.reporter.snapshot_change(change, path)
827
def _emit_progress_set_stage(self, name, entries_title=None):
715
828
"""Set the progress stage and emit an update to the progress bar."""
716
829
self.pb_stage_name = name
717
830
self.pb_stage_count += 1
718
self.pb_entries_show = show_entries
831
self.pb_entries_title = entries_title
832
if entries_title is not None:
720
833
self.pb_entries_count = 0
721
834
self.pb_entries_total = '?'
722
835
self._emit_progress()