/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: Daniel Watkins
  • Date: 2007-11-06 09:33:05 UTC
  • mfrom: (2967 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2993.
  • Revision ID: d.m.watkins@warwick.ac.uk-20071106093305-zfef3c0jbcvunnuz
Merged bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
# The newly committed revision is going to have a shape corresponding
19
 
# to that of the working inventory.  Files that are not in the
 
19
# to that of the working tree.  Files that are not in the
20
20
# working tree and that were in the predecessor are reported as
21
21
# removed --- this can include files that were either removed from the
22
22
# inventory or deleted in the working tree.  If they were only
25
25
# We then consider the remaining entries, which will be in the new
26
26
# version.  Directory entries are simply copied across.  File entries
27
27
# must be checked to see if a new version of the file should be
28
 
# recorded.  For each parent revision inventory, we check to see what
 
28
# recorded.  For each parent revision tree, we check to see what
29
29
# version of the file was present.  If the file was present in at
30
30
# least one tree, and if it was the same version in all the trees,
31
31
# then we can just refer to that version.  Otherwise, a new version
59
59
from bzrlib import (
60
60
    debug,
61
61
    errors,
62
 
    inventory,
 
62
    revision,
63
63
    tree,
64
64
    )
65
65
from bzrlib.branch import Branch
68
68
                           ConflictsInTree,
69
69
                           StrictCommitFailed
70
70
                           )
71
 
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any, 
 
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
72
72
                            is_inside_or_parent_of_any,
 
73
                            minimum_path_selection,
73
74
                            quotefn, sha_file, split_lines)
74
75
from bzrlib.testament import Testament
75
76
from bzrlib.trace import mutter, note, warning, is_quiet
76
77
from bzrlib.xml5 import serializer_v5
77
 
from bzrlib.inventory import Inventory, InventoryEntry
 
78
from bzrlib.inventory import InventoryEntry, make_entry
78
79
from bzrlib import symbol_versioning
79
80
from bzrlib.symbol_versioning import (deprecated_passed,
80
81
        deprecated_function,
241
242
                               " parameter is required for commit().")
242
243
 
243
244
        self.bound_branch = None
 
245
        self.any_entries_changed = False
 
246
        self.any_entries_deleted = False
244
247
        self.local = local
245
248
        self.master_branch = None
246
249
        self.master_locked = False
 
250
        self.recursive = recursive
247
251
        self.rev_id = None
248
 
        self.specific_files = specific_files
 
252
        if specific_files is not None:
 
253
            self.specific_files = sorted(
 
254
                minimum_path_selection(specific_files))
 
255
        else:
 
256
            self.specific_files = None
 
257
        self.specific_file_ids = None
249
258
        self.allow_pointless = allow_pointless
250
 
        self.recursive = recursive
251
259
        self.revprops = revprops
252
260
        self.message_callback = message_callback
253
261
        self.timestamp = timestamp
255
263
        self.committer = committer
256
264
        self.strict = strict
257
265
        self.verbose = verbose
 
266
        # accumulates an inventory delta to the basis entry, so we can make
 
267
        # just the necessary updates to the workingtree's cached basis.
 
268
        self._basis_delta = []
258
269
 
259
270
        self.work_tree.lock_write()
260
271
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
272
        self.basis_revid = self.work_tree.last_revision()
261
273
        self.basis_tree = self.work_tree.basis_tree()
262
274
        self.basis_tree.lock_read()
263
275
        try:
280
292
                self.config = self.branch.get_config()
281
293
 
282
294
            # If provided, ensure the specified files are versioned
283
 
            if specific_files is not None:
284
 
                # Note: We don't actually need the IDs here. This routine
285
 
                # is being called because it raises PathNotVerisonedError
286
 
                # as a side effect of finding the IDs.
 
295
            if self.specific_files is not None:
 
296
                # Note: This routine is being called because it raises
 
297
                # PathNotVersionedError as a side effect of finding the IDs. We
 
298
                # later use the ids we found as input to the working tree
 
299
                # inventory iterator, so we only consider those ids rather than
 
300
                # examining the whole tree again.
287
301
                # XXX: Dont we have filter_unversioned to do this more
288
302
                # cheaply?
289
 
                tree.find_ids_across_trees(specific_files,
290
 
                                           [self.basis_tree, self.work_tree])
 
303
                self.specific_file_ids = tree.find_ids_across_trees(
 
304
                    specific_files, [self.basis_tree, self.work_tree])
291
305
 
292
306
            # Setup the progress bar. As the number of files that need to be
293
307
            # committed in unknown, progress is reported as stages.
328
342
                self.reporter.started(new_revno, self.rev_id, master_location)
329
343
 
330
344
                self._update_builder_with_changes()
 
345
                self._report_and_accumulate_deletes()
331
346
                self._check_pointless()
332
347
 
333
348
                # TODO: Now the new inventory is known, check for conflicts.
369
384
 
370
385
            # Make the working tree up to date with the branch
371
386
            self._set_progress_stage("Updating the working tree")
372
 
            rev_tree = self.builder.revision_tree()
373
 
            self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
 
387
            self.work_tree.update_basis_by_delta(self.rev_id,
 
388
                 self._basis_delta)
374
389
            self.reporter.completed(new_revno, self.rev_id)
375
390
            self._process_post_hooks(old_revno, new_revno)
376
391
        finally:
383
398
            return NullCommitReporter()
384
399
        return ReportCommitToLog()
385
400
 
386
 
    def _any_real_changes(self):
387
 
        """Are there real changes between new_inventory and basis?
388
 
 
389
 
        For trees without rich roots, inv.root.revision changes every commit.
390
 
        But if that is the only change, we want to treat it as though there
391
 
        are *no* changes.
392
 
        """
393
 
        new_entries = self.builder.new_inventory.iter_entries()
394
 
        basis_entries = self.basis_inv.iter_entries()
395
 
        new_path, new_root_ie = new_entries.next()
396
 
        basis_path, basis_root_ie = basis_entries.next()
397
 
 
398
 
        # This is a copy of InventoryEntry.__eq__ only leaving out .revision
399
 
        def ie_equal_no_revision(this, other):
400
 
            return ((this.file_id == other.file_id)
401
 
                    and (this.name == other.name)
402
 
                    and (this.symlink_target == other.symlink_target)
403
 
                    and (this.text_sha1 == other.text_sha1)
404
 
                    and (this.text_size == other.text_size)
405
 
                    and (this.text_id == other.text_id)
406
 
                    and (this.parent_id == other.parent_id)
407
 
                    and (this.kind == other.kind)
408
 
                    and (this.executable == other.executable)
409
 
                    and (this.reference_revision == other.reference_revision)
410
 
                    )
411
 
        if not ie_equal_no_revision(new_root_ie, basis_root_ie):
412
 
            return True
413
 
 
414
 
        for new_ie, basis_ie in zip(new_entries, basis_entries):
415
 
            if new_ie != basis_ie:
416
 
                return True
417
 
 
418
 
        # No actual changes present
419
 
        return False
420
 
 
421
401
    def _check_pointless(self):
422
402
        if self.allow_pointless:
423
403
            return
424
404
        # A merge with no effect on files
425
405
        if len(self.parents) > 1:
426
406
            return
427
 
        # work around the fact that a newly-initted tree does differ from its
428
 
        # basis
429
 
        if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
 
407
        # TODO: we could simplify this by using self._basis_delta.
 
408
 
 
409
        # The initial commit adds a root directory, but this in itself is not
 
410
        # a worthwhile commit.
 
411
        if (self.basis_revid == revision.NULL_REVISION and
 
412
            len(self.builder.new_inventory) == 1):
430
413
            raise PointlessCommit()
431
 
        # Shortcut, if the number of entries changes, then we obviously have
432
 
        # a change
433
 
        if len(self.builder.new_inventory) != len(self.basis_inv):
434
 
            return
435
414
        # If length == 1, then we only have the root entry. Which means
436
415
        # that there is no real difference (only the root could be different)
437
 
        if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
 
416
        # unless deletes occured, in which case the length is irrelevant.
 
417
        if (self.any_entries_deleted or 
 
418
            (len(self.builder.new_inventory) != 1 and
 
419
             self.any_entries_changed)):
438
420
            return
439
421
        raise PointlessCommit()
440
422
 
657
639
        specific_files = self.specific_files
658
640
        mutter("Selecting files for commit with filter %s", specific_files)
659
641
 
660
 
        # Check and warn about old CommitBuilders
661
 
        if not self.builder.record_root_entry:
662
 
            symbol_versioning.warn('CommitBuilders should support recording'
663
 
                ' the root entry as of bzr 0.10.', DeprecationWarning, 
664
 
                stacklevel=1)
665
 
            self.builder.new_inventory.add(self.basis_inv.root.copy())
666
 
 
667
642
        # Build the new inventory
668
643
        self._populate_from_inventory(specific_files)
669
644
 
671
646
        # recorded in their previous state. For more details, see
672
647
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
673
648
        if specific_files:
674
 
            for path, new_ie in self.basis_inv.iter_entries():
675
 
                if new_ie.file_id in self.builder.new_inventory:
 
649
            for path, old_ie in self.basis_inv.iter_entries():
 
650
                if old_ie.file_id in self.builder.new_inventory:
 
651
                    # already added - skip.
676
652
                    continue
677
653
                if is_inside_any(specific_files, path):
 
654
                    # was inside the selected path, if not present it has been
 
655
                    # deleted so skip.
678
656
                    continue
679
 
                ie = new_ie.copy()
680
 
                ie.revision = None
681
 
                self.builder.record_entry_contents(ie, self.parent_invs, path,
682
 
                                                   self.basis_tree)
 
657
                if old_ie.kind == 'directory':
 
658
                    self._next_progress_entry()
 
659
                # not in final inv yet, was not in the selected files, so is an
 
660
                # entry to be preserved unaltered.
 
661
                ie = old_ie.copy()
 
662
                # Note: specific file commits after a merge are currently
 
663
                # prohibited. This test is for sanity/safety in case it's
 
664
                # required after that changes.
 
665
                if len(self.parents) > 1:
 
666
                    ie.revision = None
 
667
                delta, version_recorded = self.builder.record_entry_contents(
 
668
                    ie, self.parent_invs, path, self.basis_tree, None)
 
669
                if version_recorded:
 
670
                    self.any_entries_changed = True
 
671
                if delta: self._basis_delta.append(delta)
683
672
 
684
 
        # Report what was deleted.
685
 
        if self.reporter.is_verbose():
686
 
            for path, ie in self.basis_inv.iter_entries():
687
 
                if ie.file_id not in self.builder.new_inventory:
688
 
                    self.reporter.deleted(path)
 
673
    def _report_and_accumulate_deletes(self):
 
674
        # XXX: Could the list of deleted paths and ids be instead taken from
 
675
        # _populate_from_inventory?
 
676
        deleted_ids = set(self.basis_inv._byid.keys()) - \
 
677
            set(self.builder.new_inventory._byid.keys())
 
678
        if deleted_ids:
 
679
            self.any_entries_deleted = True
 
680
            deleted = [(self.basis_tree.id2path(file_id), file_id)
 
681
                for file_id in deleted_ids]
 
682
            deleted.sort()
 
683
            # XXX: this is not quite directory-order sorting
 
684
            for path, file_id in deleted:
 
685
                self._basis_delta.append((path, None, file_id, None))
 
686
                self.reporter.deleted(path)
689
687
 
690
688
    def _populate_from_inventory(self, specific_files):
691
689
        """Populate the CommitBuilder by walking the working tree inventory."""
697
695
        report_changes = self.reporter.is_verbose()
698
696
        deleted_ids = []
699
697
        deleted_paths = set()
 
698
        # XXX: Note that entries may have the wrong kind because the entry does
 
699
        # not reflect the status on disk.
700
700
        work_inv = self.work_tree.inventory
701
 
        assert work_inv.root is not None
702
 
        entries = work_inv.iter_entries()
703
 
        if not self.builder.record_root_entry:
704
 
            entries.next()
 
701
        entries = work_inv.iter_entries_by_dir(
 
702
            specific_file_ids=self.specific_file_ids, yield_parents=True)
705
703
        for path, existing_ie in entries:
706
704
            file_id = existing_ie.file_id
707
705
            name = existing_ie.name
709
707
            kind = existing_ie.kind
710
708
            if kind == 'directory':
711
709
                self._next_progress_entry()
712
 
 
713
710
            # Skip files that have been deleted from the working tree.
714
711
            # The deleted files/directories are also recorded so they
715
712
            # can be explicitly unversioned later. Note that when a
717
714
            # deleted files matching that filter.
718
715
            if is_inside_any(deleted_paths, path):
719
716
                continue
 
717
            content_summary = self.work_tree.path_content_summary(path)
720
718
            if not specific_files or is_inside_any(specific_files, path):
721
 
                if not self.work_tree.has_filename(path):
 
719
                if content_summary[0] == 'missing':
722
720
                    deleted_paths.add(path)
723
721
                    self.reporter.missing(path)
724
722
                    deleted_ids.append(file_id)
725
723
                    continue
726
 
            try:
727
 
                kind = self.work_tree.kind(file_id)
728
 
                # TODO: specific_files filtering before nested tree processing
729
 
                if kind == 'tree-reference' and self.recursive == 'down':
730
 
                    self._commit_nested_tree(file_id, path)
731
 
            except errors.NoSuchFile:
732
 
                pass
 
724
            # TODO: have the builder do the nested commit just-in-time IF and
 
725
            # only if needed.
 
726
            if content_summary[0] == 'tree-reference':
 
727
                # enforce repository nested tree policy.
 
728
                if (not self.work_tree.supports_tree_reference() or
 
729
                    # repository does not support it either.
 
730
                    not self.branch.repository._format.supports_tree_reference):
 
731
                    content_summary = ('directory',) + content_summary[1:]
 
732
            kind = content_summary[0]
 
733
            # TODO: specific_files filtering before nested tree processing
 
734
            if kind == 'tree-reference':
 
735
                if self.recursive == 'down':
 
736
                    nested_revision_id = self._commit_nested_tree(
 
737
                        file_id, path)
 
738
                    content_summary = content_summary[:3] + (
 
739
                        nested_revision_id,)
 
740
                else:
 
741
                    content_summary = content_summary[:3] + (
 
742
                        self.work_tree.get_reference_revision(file_id),)
733
743
 
734
744
            # Record an entry for this item
735
745
            # Note: I don't particularly want to have the existing_ie
736
746
            # parameter but the test suite currently (28-Jun-07) breaks
737
747
            # without it thanks to a unicode normalisation issue. :-(
738
 
            definitely_changed = kind != existing_ie.kind 
 
748
            definitely_changed = kind != existing_ie.kind
739
749
            self._record_entry(path, file_id, specific_files, kind, name,
740
 
                parent_id, definitely_changed, existing_ie, report_changes)
 
750
                parent_id, definitely_changed, existing_ie, report_changes,
 
751
                content_summary)
741
752
 
742
753
        # Unversion IDs that were found to be deleted
743
754
        self.work_tree.unversion(deleted_ids)
756
767
            sub_tree.branch.repository = \
757
768
                self.work_tree.branch.repository
758
769
        try:
759
 
            sub_tree.commit(message=None, revprops=self.revprops,
 
770
            return sub_tree.commit(message=None, revprops=self.revprops,
760
771
                recursive=self.recursive,
761
772
                message_callback=self.message_callback,
762
773
                timestamp=self.timestamp, timezone=self.timezone,
765
776
                strict=self.strict, verbose=self.verbose,
766
777
                local=self.local, reporter=self.reporter)
767
778
        except errors.PointlessCommit:
768
 
            pass
 
779
            return self.work_tree.get_reference_revision(file_id)
769
780
 
770
781
    def _record_entry(self, path, file_id, specific_files, kind, name,
771
 
            parent_id, definitely_changed, existing_ie=None,
772
 
            report_changes=True):
 
782
        parent_id, definitely_changed, existing_ie, report_changes,
 
783
        content_summary):
773
784
        "Record the new inventory entry for a path if any."
774
785
        # mutter('check %s {%s}', path, file_id)
775
 
        if (not specific_files or 
776
 
            is_inside_or_parent_of_any(specific_files, path)):
777
 
                # mutter('%s selected for commit', path)
778
 
                if definitely_changed or existing_ie is None:
779
 
                    ie = inventory.make_entry(kind, name, parent_id, file_id)
780
 
                else:
781
 
                    ie = existing_ie.copy()
782
 
                    ie.revision = None
 
786
        # mutter('%s selected for commit', path)
 
787
        if definitely_changed or existing_ie is None:
 
788
            ie = make_entry(kind, name, parent_id, file_id)
783
789
        else:
784
 
            # mutter('%s not selected for commit', path)
785
 
            if self.basis_inv.has_id(file_id):
786
 
                ie = self.basis_inv[file_id].copy()
787
 
            else:
788
 
                # this entry is new and not being committed
789
 
                ie = None
790
 
        if ie is not None:
791
 
            self.builder.record_entry_contents(ie, self.parent_invs, 
792
 
                path, self.work_tree)
793
 
            if report_changes:
794
 
                self._report_change(ie, path)
 
790
            ie = existing_ie.copy()
 
791
            ie.revision = None
 
792
        delta, version_recorded = self.builder.record_entry_contents(ie,
 
793
            self.parent_invs, path, self.work_tree, content_summary)
 
794
        if delta:
 
795
            self._basis_delta.append(delta)
 
796
        if version_recorded:
 
797
            self.any_entries_changed = True
 
798
        if report_changes:
 
799
            self._report_change(ie, path)
795
800
        return ie
796
801
 
797
802
    def _report_change(self, ie, path):