/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Ian Clatworthy
  • Date: 2007-06-29 04:35:01 UTC
  • mto: (2647.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 2648.
  • Revision ID: ian.clatworthy@internode.on.net-20070629043501-zgm70dyx6ut3yerk
refactor commit to support alternative population meothds

Show diffs side-by-side

added added

removed removed

Lines of Context:
65
65
import bzrlib.config
66
66
from bzrlib.errors import (BzrError, PointlessCommit,
67
67
                           ConflictsInTree,
68
 
                           StrictCommitFailed
 
68
                           StrictCommitFailed,
 
69
                           NoSuchId
69
70
                           )
70
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any, 
71
72
                            is_inside_or_parent_of_any,
255
256
            # Check that the working tree is up to date
256
257
            old_revno,new_revno = self._check_out_of_date_tree()
257
258
 
258
 
            if strict:
259
 
                # raise an exception as soon as we find a single unknown.
260
 
                for unknown in self.work_tree.unknowns():
261
 
                    raise StrictCommitFailed()
262
 
                   
263
259
            if self.config is None:
264
260
                self.config = self.branch.get_config()
265
261
 
286
282
            self.pb.show_spinner = False
287
283
            self.pb.show_eta = False
288
284
            self.pb.show_count = True
289
 
            self.pb.show_bar = False
 
285
            self.pb.show_bar = True
290
286
 
291
287
            # After a merge, a selected file commit is not supported.
292
288
            # See 'bzr help merge' for an explanation as to why.
296
292
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
297
293
            
298
294
            # Collect the changes
299
 
            self._emit_progress_set_stage("Collecting changes", show_entries=True)
 
295
            self._emit_progress_set_stage("Collecting changes",
 
296
                    entries_title="Directory")
300
297
            self.builder = self.branch.get_commit_builder(self.parents,
301
298
                self.config, timestamp, timezone, committer, revprops, rev_id)
302
299
            self._update_builder_with_changes()
591
588
 
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)
597
591
 
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, 
603
597
                stacklevel=1)
604
598
            self.builder.new_inventory.add(self.basis_inv.root.copy())
605
 
            entries.next()
606
 
 
607
 
        deleted_ids = []
608
 
        deleted_paths = set()
609
 
        for path, new_ie in entries:
610
 
            self._emit_progress_next_entry()
611
 
            file_id = new_ie.file_id
612
 
 
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):
619
 
                continue
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)
625
 
                    continue
626
 
            try:
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
638
 
                        ==
639
 
                        self.work_tree.branch.repository.bzrdir.root_transport.base):
640
 
                        sub_tree.branch.repository = \
641
 
                            self.work_tree.branch.repository
642
 
                    try:
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:
652
 
                        pass
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:
657
 
                pass
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)
662
 
                    ie = new_ie.copy()
663
 
                    ie.revision = None
664
 
            else:
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()
668
 
                else:
669
 
                    # this entry is new and not being committed
670
 
                    continue
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]
677
 
            else:
678
 
                basis_ie = None
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)
684
 
            else:
685
 
                self.reporter.snapshot_change(change, path)
686
 
 
687
 
        # Unversion IDs that were found to be deleted
688
 
        self.work_tree.unversion(deleted_ids)
 
599
            root_added_already = True
 
600
 
 
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)
689
609
 
690
610
        # If specific files/directories were nominated, it is possible
691
611
        # that some data from outside those needs to be preserved from
695
615
        # recorded.
696
616
        if specific_files:
697
617
            for path, new_ie in self.basis_inv.iter_entries():
698
 
                if new_ie.file_id in work_inv:
 
618
                if new_ie.file_id in self.builder.new_inventory:
699
619
                    continue
700
620
                if is_inside_any(specific_files, path):
701
621
                    continue
711
631
            if ie.file_id not in self.builder.new_inventory:
712
632
                self.reporter.deleted(path)
713
633
 
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."""
 
636
        if self.strict:
 
637
            # raise an exception as soon as we find a single unknown.
 
638
            for unknown in self.work_tree.unknowns():
 
639
                raise StrictCommitFailed()
 
640
               
 
641
        deleted_ids = []
 
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:
 
647
            entries.next()
 
648
        for path, new_ie in entries:
 
649
            file_id = new_ie.file_id
 
650
            name = new_ie.name
 
651
            parent_id = new_ie.parent_id
 
652
            kind = new_ie.kind
 
653
            if kind == 'directory':
 
654
                self._emit_progress_next_entry()
 
655
 
 
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):
 
662
                continue
 
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)
 
668
                    continue
 
669
            try:
 
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:
 
675
                pass
 
676
 
 
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)
 
684
 
 
685
        # Unversion IDs that were found to be deleted
 
686
        self.work_tree.unversion(deleted_ids)
 
687
 
 
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
 
693
 
 
694
        deleted_ids = []
 
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:
 
700
            entries.next()
 
701
        for entry in entries:
 
702
            (file_id, paths, changed_content, versioned_flags, parents, names,
 
703
                kinds, executables) = entry
 
704
            kind = kinds[1]
 
705
            if kind == 'directory':
 
706
                self._emit_progress_next_entry()
 
707
 
 
708
            # Skip unknowns unless strict mode
 
709
            if versioned_flags == (False,False):
 
710
                if self.strict:
 
711
                    raise StrictCommitFailed()
 
712
                else:
 
713
                    continue
 
714
 
 
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.
 
718
            if kind is None:
 
719
                path = paths[0]
 
720
                if is_inside_any(deleted_paths, path):
 
721
                    continue
 
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)
 
726
                    continue
 
727
                # It's missing but still needs to be recorded
 
728
                index = 0
 
729
            else:
 
730
                index = 1
 
731
            path = paths[index]
 
732
            kind = kinds[index]
 
733
            path = paths[index]
 
734
            parent = parents[index]
 
735
            name = names[index]
 
736
 
 
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)
 
741
 
 
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
 
748
 
 
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:
 
753
            try:
 
754
                self.work_tree.unversion([id])
 
755
            except NoSuchId:
 
756
                pass
 
757
 
 
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
 
768
            ==
 
769
            self.work_tree.branch.repository.bzrdir.root_transport.base):
 
770
            sub_tree.branch.repository = \
 
771
                self.work_tree.branch.repository
 
772
        try:
 
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:
 
782
            pass
 
783
 
 
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)
 
793
                else:
 
794
                    ie = existing_ie.copy()
 
795
                    ie.revision = None
 
796
        else:
 
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()
 
800
            else:
 
801
                # this entry is new and not being committed
 
802
                ie = None
 
803
        if ie is not None:
 
804
            self.builder.record_entry_contents(ie, self.parent_invs, 
 
805
                path, self.work_tree)
 
806
            self._report_change(ie, path)
 
807
        return ie
 
808
 
 
809
    def _report_change(self, ie, path):
 
810
        """Report a change to the user.
 
811
 
 
812
        The change that has occurred is described relative to the basis
 
813
        inventory.
 
814
        """
 
815
        if (self.basis_inv.has_id(ie.file_id)):
 
816
            basis_ie = self.basis_inv[ie.file_id]
 
817
        else:
 
818
            basis_ie = None
 
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)
 
824
        else:
 
825
            self.reporter.snapshot_change(change, path)
 
826
 
 
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
719
 
        if 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()
727
840
        self._emit_progress()
728
841
 
729
842
    def _emit_progress(self):
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))
 
843
        if self.pb_entries_title:
 
844
            if self.pb_entries_total == '?':
 
845
                text = "%s [%s %d] - Stage" % (self.pb_stage_name,
 
846
                    self.pb_entries_title, self.pb_entries_count)
 
847
            else:
 
848
                text = "%s [%s %d/%s] - Stage" % (self.pb_stage_name,
 
849
                    self.pb_entries_title, self.pb_entries_count,
 
850
                    str(self.pb_entries_total))
733
851
        else:
734
852
            text = "%s - Stage" % (self.pb_stage_name)
735
853
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)