/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: Robert Collins
  • Date: 2009-05-23 20:57:12 UTC
  • mfrom: (4371 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4441.
  • Revision ID: robertc@robertcollins.net-20090523205712-lcwbfqk6vwavinuv
MergeĀ .dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
# The newly committed revision is going to have a shape corresponding
62
62
    revision,
63
63
    trace,
64
64
    tree,
 
65
    xml_serializer,
65
66
    )
66
67
from bzrlib.branch import Branch
67
68
import bzrlib.config
104
105
    def completed(self, revno, rev_id):
105
106
        pass
106
107
 
107
 
    def deleted(self, file_id):
 
108
    def deleted(self, path):
108
109
        pass
109
110
 
110
111
    def escaped(self, escape_count, message):
130
131
        note(format, *args)
131
132
 
132
133
    def snapshot_change(self, change, path):
133
 
        if change == 'unchanged':
134
 
            return
135
 
        if change == 'added' and path == '':
 
134
        if path == '' and change in ('added', 'modified'):
136
135
            return
137
136
        self._note("%s %s", change, path)
138
137
 
151
150
    def completed(self, revno, rev_id):
152
151
        self._note('Committed revision %d.', revno)
153
152
 
154
 
    def deleted(self, file_id):
155
 
        self._note('deleted %s', file_id)
 
153
    def deleted(self, path):
 
154
        self._note('deleted %s', path)
156
155
 
157
156
    def escaped(self, escape_count, message):
158
157
        self._note("replaced %d control characters in message", escape_count)
259
258
                               " parameter is required for commit().")
260
259
 
261
260
        self.bound_branch = None
262
 
        self.any_entries_changed = False
263
261
        self.any_entries_deleted = False
264
262
        if exclude is not None:
265
263
            self.exclude = sorted(
276
274
                minimum_path_selection(specific_files))
277
275
        else:
278
276
            self.specific_files = None
279
 
        self.specific_file_ids = None
 
277
            
280
278
        self.allow_pointless = allow_pointless
281
279
        self.revprops = revprops
282
280
        self.message_callback = message_callback
287
285
        self.verbose = verbose
288
286
 
289
287
        self.work_tree.lock_write()
 
288
        self.parents = self.work_tree.get_parent_ids()
 
289
        # We can use record_iter_changes IFF iter_changes is compatible with
 
290
        # the command line parameters, and the repository has fast delta
 
291
        # generation. See bug 347649.
 
292
        self.use_record_iter_changes = (
 
293
            not self.specific_files and
 
294
            not self.exclude and 
 
295
            not self.branch.repository._format.supports_tree_reference and
 
296
            (self.branch.repository._format.fast_deltas or
 
297
             len(self.parents) < 2))
290
298
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
291
299
        self.basis_revid = self.work_tree.last_revision()
292
300
        self.basis_tree = self.work_tree.basis_tree()
310
318
            if self.config is None:
311
319
                self.config = self.branch.get_config()
312
320
 
313
 
            # If provided, ensure the specified files are versioned
314
 
            if self.specific_files is not None:
315
 
                # Note: This routine is being called because it raises
316
 
                # PathNotVersionedError as a side effect of finding the IDs. We
317
 
                # later use the ids we found as input to the working tree
318
 
                # inventory iterator, so we only consider those ids rather than
319
 
                # examining the whole tree again.
320
 
                # XXX: Dont we have filter_unversioned to do this more
321
 
                # cheaply?
322
 
                self.specific_file_ids = tree.find_ids_across_trees(
323
 
                    specific_files, [self.basis_tree, self.work_tree])
 
321
            self._set_specific_file_ids()
324
322
 
325
323
            # Setup the progress bar. As the number of files that need to be
326
324
            # committed in unknown, progress is reported as stages.
337
335
            self.pb.show_count = True
338
336
            self.pb.show_bar = True
339
337
 
340
 
            self.basis_inv = self.basis_tree.inventory
341
338
            self._gather_parents()
342
339
            # After a merge, a selected file commit is not supported.
343
340
            # See 'bzr help merge' for an explanation as to why.
348
345
                raise errors.CannotCommitSelectedFileMerge(self.exclude)
349
346
 
350
347
            # Collect the changes
351
 
            self._set_progress_stage("Collecting changes",
352
 
                    entries_title="Directory")
 
348
            self._set_progress_stage("Collecting changes", counter=True)
353
349
            self.builder = self.branch.get_commit_builder(self.parents,
354
350
                self.config, timestamp, timezone, committer, revprops, rev_id)
355
351
 
365
361
                self.reporter.started(new_revno, self.rev_id, master_location)
366
362
 
367
363
                self._update_builder_with_changes()
368
 
                self._report_and_accumulate_deletes()
369
364
                self._check_pointless()
370
365
 
371
366
                # TODO: Now the new inventory is known, check for conflicts.
431
426
        # The initial commit adds a root directory, but this in itself is not
432
427
        # a worthwhile commit.
433
428
        if (self.basis_revid == revision.NULL_REVISION and
434
 
            len(self.builder.new_inventory) == 1):
 
429
            ((self.builder.new_inventory is not None and
 
430
             len(self.builder.new_inventory) == 1) or
 
431
            len(self.builder._basis_delta) == 1)):
435
432
            raise PointlessCommit()
436
 
        # If length == 1, then we only have the root entry. Which means
437
 
        # that there is no real difference (only the root could be different)
438
 
        # unless deletes occured, in which case the length is irrelevant.
439
 
        if (self.any_entries_deleted or
440
 
            (len(self.builder.new_inventory) != 1 and
441
 
             self.any_entries_changed)):
 
433
        if self.builder.any_changes():
442
434
            return
443
435
        raise PointlessCommit()
444
436
 
616
608
        # serialiser not by commit. Then we can also add an unescaper
617
609
        # in the deserializer and start roundtripping revision messages
618
610
        # precisely. See repository_implementations/test_repository.py
619
 
 
620
 
        # Python strings can include characters that can't be
621
 
        # represented in well-formed XML; escape characters that
622
 
        # aren't listed in the XML specification
623
 
        # (http://www.w3.org/TR/REC-xml/#NT-Char).
624
 
        self.message, escape_count = re.subn(
625
 
            u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
626
 
            lambda match: match.group(0).encode('unicode_escape'),
 
611
        self.message, escape_count = xml_serializer.escape_invalid_chars(
627
612
            self.message)
628
613
        if escape_count:
629
614
            self.reporter.escaped(escape_count, self.message)
632
617
        """Record the parents of a merge for merge detection."""
633
618
        # TODO: Make sure that this list doesn't contain duplicate
634
619
        # entries and the order is preserved when doing this.
635
 
        self.parents = self.work_tree.get_parent_ids()
 
620
        if self.use_record_iter_changes:
 
621
            return
 
622
        self.basis_inv = self.basis_tree.inventory
636
623
        self.parent_invs = [self.basis_inv]
637
624
        for revision in self.parents[1:]:
638
625
            if self.branch.repository.has_revision(revision):
645
632
    def _update_builder_with_changes(self):
646
633
        """Update the commit builder with the data about what has changed.
647
634
        """
648
 
        # Build the revision inventory.
649
 
        #
650
 
        # This starts by creating a new empty inventory. Depending on
651
 
        # which files are selected for commit, and what is present in the
652
 
        # current tree, the new inventory is populated. inventory entries
653
 
        # which are candidates for modification have their revision set to
654
 
        # None; inventory entries that are carried over untouched have their
655
 
        # revision set to their prior value.
656
 
        #
657
 
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
658
 
        # results to create a new inventory at the same time, which results
659
 
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
660
 
        # ADHB 11-07-2006
661
 
 
662
635
        exclude = self.exclude
663
636
        specific_files = self.specific_files or []
664
637
        mutter("Selecting files for commit with filter %s", specific_files)
665
638
 
666
 
        # Build the new inventory
667
 
        self._populate_from_inventory()
668
 
 
 
639
        self._check_strict()
 
640
        if self.use_record_iter_changes:
 
641
            iter_changes = self.work_tree.iter_changes(self.basis_tree)
 
642
            iter_changes = self._filter_iter_changes(iter_changes)
 
643
            for file_id, path, fs_hash in self.builder.record_iter_changes(
 
644
                self.work_tree, self.basis_revid, iter_changes):
 
645
                self.work_tree._observed_sha1(file_id, path, fs_hash)
 
646
        else:
 
647
            # Build the new inventory
 
648
            self._populate_from_inventory()
 
649
            self._record_unselected()
 
650
            self._report_and_accumulate_deletes()
 
651
 
 
652
    def _filter_iter_changes(self, iter_changes):
 
653
        """Process iter_changes.
 
654
 
 
655
        This method reports on the changes in iter_changes to the user, and 
 
656
        converts 'missing' entries in the iter_changes iterator to 'deleted'
 
657
        entries. 'missing' entries have their
 
658
 
 
659
        :param iter_changes: An iter_changes to process.
 
660
        :return: A generator of changes.
 
661
        """
 
662
        reporter = self.reporter
 
663
        report_changes = reporter.is_verbose()
 
664
        deleted_ids = []
 
665
        for change in iter_changes:
 
666
            if report_changes:
 
667
                old_path = change[1][0]
 
668
                new_path = change[1][1]
 
669
                versioned = change[3][1]
 
670
            kind = change[6][1]
 
671
            versioned = change[3][1]
 
672
            if kind is None and versioned:
 
673
                # 'missing' path
 
674
                if report_changes:
 
675
                    reporter.missing(new_path)
 
676
                deleted_ids.append(change[0])
 
677
                # Reset the new path (None) and new versioned flag (False)
 
678
                change = (change[0], (change[1][0], None), change[2],
 
679
                    (change[3][0], False)) + change[4:]
 
680
            elif kind == 'tree-reference':
 
681
                if self.recursive == 'down':
 
682
                    self._commit_nested_tree(change[0], change[1][1])
 
683
            if change[3][0] or change[3][1]:
 
684
                yield change
 
685
                if report_changes:
 
686
                    if new_path is None:
 
687
                        reporter.deleted(old_path)
 
688
                    elif old_path is None:
 
689
                        reporter.snapshot_change('added', new_path)
 
690
                    elif old_path != new_path:
 
691
                        reporter.renamed('renamed', old_path, new_path)
 
692
                    else:
 
693
                        if (new_path or 
 
694
                            self.work_tree.branch.repository._format.rich_root_data):
 
695
                            # Don't report on changes to '' in non rich root
 
696
                            # repositories.
 
697
                            reporter.snapshot_change('modified', new_path)
 
698
            self._next_progress_entry()
 
699
        # Unversion IDs that were found to be deleted
 
700
        self.work_tree.unversion(deleted_ids)
 
701
 
 
702
    def _record_unselected(self):
669
703
        # If specific files are selected, then all un-selected files must be
670
704
        # recorded in their previous state. For more details, see
671
705
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
672
 
        if specific_files or exclude:
 
706
        if self.specific_files or self.exclude:
 
707
            specific_files = self.specific_files or []
673
708
            for path, old_ie in self.basis_inv.iter_entries():
674
709
                if old_ie.file_id in self.builder.new_inventory:
675
710
                    # already added - skip.
676
711
                    continue
677
712
                if (is_inside_any(specific_files, path)
678
 
                    and not is_inside_any(exclude, path)):
 
713
                    and not is_inside_any(self.exclude, path)):
679
714
                    # was inside the selected path, and not excluded - if not
680
715
                    # present it has been deleted so skip.
681
716
                    continue
682
717
                # From here down it was either not selected, or was excluded:
683
 
                if old_ie.kind == 'directory':
684
 
                    self._next_progress_entry()
685
718
                # We preserve the entry unaltered.
686
719
                ie = old_ie.copy()
687
720
                # Note: specific file commits after a merge are currently
689
722
                # required after that changes.
690
723
                if len(self.parents) > 1:
691
724
                    ie.revision = None
692
 
                _, version_recorded, _ = self.builder.record_entry_contents(
693
 
                    ie, self.parent_invs, path, self.basis_tree, None)
694
 
                if version_recorded:
695
 
                    self.any_entries_changed = True
 
725
                self.builder.record_entry_contents(ie, self.parent_invs, path,
 
726
                    self.basis_tree, None)
696
727
 
697
728
    def _report_and_accumulate_deletes(self):
698
 
        # XXX: Could the list of deleted paths and ids be instead taken from
699
 
        # _populate_from_inventory?
700
729
        if (isinstance(self.basis_inv, Inventory)
701
730
            and isinstance(self.builder.new_inventory, Inventory)):
702
731
            # the older Inventory classes provide a _byid dict, and building a
720
749
                self.builder.record_delete(path, file_id)
721
750
                self.reporter.deleted(path)
722
751
 
723
 
    def _populate_from_inventory(self):
724
 
        """Populate the CommitBuilder by walking the working tree inventory."""
 
752
    def _check_strict(self):
 
753
        # XXX: when we use iter_changes this would likely be faster if
 
754
        # iter_changes would check for us (even in the presence of
 
755
        # selected_files).
725
756
        if self.strict:
726
757
            # raise an exception as soon as we find a single unknown.
727
758
            for unknown in self.work_tree.unknowns():
728
759
                raise StrictCommitFailed()
729
760
 
 
761
    def _populate_from_inventory(self):
 
762
        """Populate the CommitBuilder by walking the working tree inventory."""
 
763
        # Build the revision inventory.
 
764
        #
 
765
        # This starts by creating a new empty inventory. Depending on
 
766
        # which files are selected for commit, and what is present in the
 
767
        # current tree, the new inventory is populated. inventory entries
 
768
        # which are candidates for modification have their revision set to
 
769
        # None; inventory entries that are carried over untouched have their
 
770
        # revision set to their prior value.
 
771
        #
 
772
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
 
773
        # results to create a new inventory at the same time, which results
 
774
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
 
775
        # ADHB 11-07-2006
 
776
 
730
777
        specific_files = self.specific_files
731
778
        exclude = self.exclude
732
779
        report_changes = self.reporter.is_verbose()
746
793
            name = existing_ie.name
747
794
            parent_id = existing_ie.parent_id
748
795
            kind = existing_ie.kind
749
 
            if kind == 'directory':
750
 
                self._next_progress_entry()
751
796
            # Skip files that have been deleted from the working tree.
752
797
            # The deleted path ids are also recorded so they can be explicitly
753
798
            # unversioned later.
782
827
                    for segment in path_segments:
783
828
                        deleted_dict = deleted_dict.setdefault(segment, {})
784
829
                    self.reporter.missing(path)
 
830
                    self._next_progress_entry()
785
831
                    deleted_ids.append(file_id)
786
832
                    continue
787
833
            # TODO: have the builder do the nested commit just-in-time IF and
854
900
            ie.revision = None
855
901
        # For carried over entries we don't care about the fs hash - the repo
856
902
        # isn't generating a sha, so we're not saving computation time.
857
 
        _, version_recorded, fs_hash = self.builder.record_entry_contents(
 
903
        _, _, fs_hash = self.builder.record_entry_contents(
858
904
            ie, self.parent_invs, path, self.work_tree, content_summary)
859
 
        if version_recorded:
860
 
            self.any_entries_changed = True
861
905
        if report_changes:
862
906
            self._report_change(ie, path)
863
907
        if fs_hash:
879
923
            InventoryEntry.MODIFIED_AND_RENAMED):
880
924
            old_path = self.basis_inv.id2path(ie.file_id)
881
925
            self.reporter.renamed(change, old_path, path)
 
926
            self._next_progress_entry()
882
927
        else:
 
928
            if change == 'unchanged':
 
929
                return
883
930
            self.reporter.snapshot_change(change, path)
 
931
            self._next_progress_entry()
884
932
 
885
 
    def _set_progress_stage(self, name, entries_title=None):
 
933
    def _set_progress_stage(self, name, counter=False):
886
934
        """Set the progress stage and emit an update to the progress bar."""
887
935
        self.pb_stage_name = name
888
936
        self.pb_stage_count += 1
889
 
        self.pb_entries_title = entries_title
890
 
        if entries_title is not None:
 
937
        if counter:
891
938
            self.pb_entries_count = 0
892
 
            self.pb_entries_total = '?'
 
939
        else:
 
940
            self.pb_entries_count = None
893
941
        self._emit_progress()
894
942
 
895
943
    def _next_progress_entry(self):
898
946
        self._emit_progress()
899
947
 
900
948
    def _emit_progress(self):
901
 
        if self.pb_entries_title:
902
 
            if self.pb_entries_total == '?':
903
 
                text = "%s [%s %d] - Stage" % (self.pb_stage_name,
904
 
                    self.pb_entries_title, self.pb_entries_count)
905
 
            else:
906
 
                text = "%s [%s %d/%s] - Stage" % (self.pb_stage_name,
907
 
                    self.pb_entries_title, self.pb_entries_count,
908
 
                    str(self.pb_entries_total))
 
949
        if self.pb_entries_count is not None:
 
950
            text = "%s [%d] - Stage" % (self.pb_stage_name,
 
951
                self.pb_entries_count)
909
952
        else:
910
 
            text = "%s - Stage" % (self.pb_stage_name)
 
953
            text = "%s - Stage" % (self.pb_stage_name, )
911
954
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
912
955
 
 
956
    def _set_specific_file_ids(self):
 
957
        """populate self.specific_file_ids if we will use it."""
 
958
        if not self.use_record_iter_changes:
 
959
            # If provided, ensure the specified files are versioned
 
960
            if self.specific_files is not None:
 
961
                # Note: This routine is being called because it raises
 
962
                # PathNotVersionedError as a side effect of finding the IDs. We
 
963
                # later use the ids we found as input to the working tree
 
964
                # inventory iterator, so we only consider those ids rather than
 
965
                # examining the whole tree again.
 
966
                # XXX: Dont we have filter_unversioned to do this more
 
967
                # cheaply?
 
968
                self.specific_file_ids = tree.find_ids_across_trees(
 
969
                    self.specific_files, [self.basis_tree, self.work_tree])
 
970
            else:
 
971
                self.specific_file_ids = None