/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: Vincent Ladeuil
  • Date: 2007-09-24 15:01:26 UTC
  • mfrom: (2853 +trunk)
  • mto: (2885.1.1 140432)
  • mto: This revision was merged to the branch mainline in revision 2886.
  • Revision ID: v.ladeuil+lp@free.fr-20070924150126-nll7i0385kisklyj
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
from bzrlib.trace import mutter, note, warning
 
76
from bzrlib.trace import mutter, note, warning, is_quiet
76
77
from bzrlib.xml5 import serializer_v5
77
78
from bzrlib.inventory import Inventory, InventoryEntry
78
79
from bzrlib import symbol_versioning
108
109
    def renamed(self, change, old_path, new_path):
109
110
        pass
110
111
 
 
112
    def is_verbose(self):
 
113
        return False
 
114
 
111
115
 
112
116
class ReportCommitToLog(NullCommitReporter):
113
117
 
134
138
 
135
139
    def completed(self, revno, rev_id):
136
140
        self._note('Committed revision %d.', revno)
137
 
    
 
141
 
138
142
    def deleted(self, file_id):
139
143
        self._note('deleted %s', file_id)
140
144
 
147
151
    def renamed(self, change, old_path, new_path):
148
152
        self._note('%s %s => %s', change, old_path, new_path)
149
153
 
 
154
    def is_verbose(self):
 
155
        return True
 
156
 
150
157
 
151
158
class Commit(object):
152
159
    """Task of committing a new revision.
163
170
    def __init__(self,
164
171
                 reporter=None,
165
172
                 config=None):
166
 
        if reporter is not None:
167
 
            self.reporter = reporter
168
 
        else:
169
 
            self.reporter = NullCommitReporter()
 
173
        """Create a Commit object.
 
174
 
 
175
        :param reporter: the default reporter to use or None to decide later
 
176
        """
 
177
        self.reporter = reporter
170
178
        self.config = config
171
 
        
 
179
 
172
180
    def commit(self,
173
181
               message=None,
174
182
               timestamp=None,
209
217
 
210
218
        :param revprops: Properties for new revision
211
219
        :param local: Perform a local only commit.
 
220
        :param reporter: the reporter to use or None for the default
 
221
        :param verbose: if True and the reporter is not None, report everything
212
222
        :param recursive: If set to 'down', commit in any subtrees that have
213
223
            pending changes of any sort during this commit.
214
224
        """
232
242
                               " parameter is required for commit().")
233
243
 
234
244
        self.bound_branch = None
 
245
        self.any_entries_changed = False
 
246
        self.any_entries_deleted = False
235
247
        self.local = local
236
248
        self.master_branch = None
237
249
        self.master_locked = False
238
250
        self.rev_id = None
239
 
        self.specific_files = specific_files
 
251
        if specific_files is not None:
 
252
            self.specific_files = sorted(
 
253
                minimum_path_selection(specific_files))
 
254
        else:
 
255
            self.specific_files = None
 
256
        self.specific_file_ids = None
240
257
        self.allow_pointless = allow_pointless
241
258
        self.recursive = recursive
242
259
        self.revprops = revprops
247
264
        self.strict = strict
248
265
        self.verbose = verbose
249
266
 
250
 
        if reporter is None and self.reporter is None:
251
 
            self.reporter = NullCommitReporter()
252
 
        elif reporter is not None:
253
 
            self.reporter = reporter
254
 
 
255
267
        self.work_tree.lock_write()
256
268
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
257
269
        self.basis_tree = self.work_tree.basis_tree()
267
279
            # Check that the working tree is up to date
268
280
            old_revno, new_revno = self._check_out_of_date_tree()
269
281
 
 
282
            # Complete configuration setup
 
283
            if reporter is not None:
 
284
                self.reporter = reporter
 
285
            elif self.reporter is None:
 
286
                self.reporter = self._select_reporter()
270
287
            if self.config is None:
271
288
                self.config = self.branch.get_config()
272
289
 
273
290
            # If provided, ensure the specified files are versioned
274
 
            if specific_files is not None:
275
 
                # Note: We don't actually need the IDs here. This routine
 
291
            if self.specific_files is not None:
 
292
                # Note: This routine
276
293
                # is being called because it raises PathNotVerisonedError
277
 
                # as a side effect of finding the IDs.
 
294
                # as a side effect of finding the IDs. We later use the ids we
 
295
                # found as input to the working tree inventory iterator, so we
 
296
                # only consider those ids rather than examining the whole tree
 
297
                # again.
278
298
                # XXX: Dont we have filter_unversioned to do this more
279
299
                # cheaply?
280
 
                tree.find_ids_across_trees(specific_files,
281
 
                                           [self.basis_tree, self.work_tree])
 
300
                self.specific_file_ids = tree.find_ids_across_trees(
 
301
                    specific_files, [self.basis_tree, self.work_tree])
282
302
 
283
303
            # Setup the progress bar. As the number of files that need to be
284
304
            # committed in unknown, progress is reported as stages.
368
388
            self._cleanup()
369
389
        return self.rev_id
370
390
 
371
 
    def _any_real_changes(self):
372
 
        """Are there real changes between new_inventory and basis?
373
 
 
374
 
        For trees without rich roots, inv.root.revision changes every commit.
375
 
        But if that is the only change, we want to treat it as though there
376
 
        are *no* changes.
377
 
        """
378
 
        new_entries = self.builder.new_inventory.iter_entries()
379
 
        basis_entries = self.basis_inv.iter_entries()
380
 
        new_path, new_root_ie = new_entries.next()
381
 
        basis_path, basis_root_ie = basis_entries.next()
382
 
 
383
 
        # This is a copy of InventoryEntry.__eq__ only leaving out .revision
384
 
        def ie_equal_no_revision(this, other):
385
 
            return ((this.file_id == other.file_id)
386
 
                    and (this.name == other.name)
387
 
                    and (this.symlink_target == other.symlink_target)
388
 
                    and (this.text_sha1 == other.text_sha1)
389
 
                    and (this.text_size == other.text_size)
390
 
                    and (this.text_id == other.text_id)
391
 
                    and (this.parent_id == other.parent_id)
392
 
                    and (this.kind == other.kind)
393
 
                    and (this.executable == other.executable)
394
 
                    and (this.reference_revision == other.reference_revision)
395
 
                    )
396
 
        if not ie_equal_no_revision(new_root_ie, basis_root_ie):
397
 
            return True
398
 
 
399
 
        for new_ie, basis_ie in zip(new_entries, basis_entries):
400
 
            if new_ie != basis_ie:
401
 
                return True
402
 
 
403
 
        # No actual changes present
404
 
        return False
 
391
    def _select_reporter(self):
 
392
        """Select the CommitReporter to use."""
 
393
        if is_quiet():
 
394
            return NullCommitReporter()
 
395
        return ReportCommitToLog()
405
396
 
406
397
    def _check_pointless(self):
407
398
        if self.allow_pointless:
419
410
            return
420
411
        # If length == 1, then we only have the root entry. Which means
421
412
        # that there is no real difference (only the root could be different)
422
 
        if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
 
413
        if len(self.builder.new_inventory) != 1 and (self.any_entries_changed
 
414
            or self.any_entries_deleted):
423
415
            return
424
416
        raise PointlessCommit()
425
417
 
656
648
        # recorded in their previous state. For more details, see
657
649
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
658
650
        if specific_files:
659
 
            for path, new_ie in self.basis_inv.iter_entries():
660
 
                if new_ie.file_id in self.builder.new_inventory:
 
651
            for path, old_ie in self.basis_inv.iter_entries():
 
652
                if old_ie.file_id in self.builder.new_inventory:
661
653
                    continue
662
654
                if is_inside_any(specific_files, path):
663
655
                    continue
664
 
                ie = new_ie.copy()
665
 
                ie.revision = None
666
 
                self.builder.record_entry_contents(ie, self.parent_invs, path,
667
 
                                                   self.basis_tree)
 
656
                if old_ie.kind == 'directory':
 
657
                    self._next_progress_entry()
 
658
                ie = old_ie.copy()
 
659
                # Note: specific file commits after a merge are currently
 
660
                # prohibited. This test is for sanity/safety in case it's
 
661
                # required after that changes.
 
662
                if len(self.parents) > 1:
 
663
                    ie.revision = None
 
664
                if self.builder.record_entry_contents(ie, self.parent_invs, path,
 
665
                    self.basis_tree):
 
666
                    self.any_entries_changed = True
668
667
 
669
 
        # Report what was deleted. We could skip this when no deletes are
670
 
        # detected to gain a performance win, but it arguably serves as a
671
 
        # 'safety check' by informing the user whenever anything disappears.
672
 
        for path, ie in self.basis_inv.iter_entries():
673
 
            if ie.file_id not in self.builder.new_inventory:
674
 
                self.reporter.deleted(path)
 
668
        # note that deletes have occurred
 
669
        if set(self.basis_inv._byid.keys()) - set(self.builder.new_inventory._byid.keys()):
 
670
            self.any_entries_deleted = True
 
671
        # Report what was deleted.
 
672
        if self.any_entries_deleted and self.reporter.is_verbose():
 
673
            for path, ie in self.basis_inv.iter_entries():
 
674
                if ie.file_id not in self.builder.new_inventory:
 
675
                    self.reporter.deleted(path)
675
676
 
676
677
    def _populate_from_inventory(self, specific_files):
677
678
        """Populate the CommitBuilder by walking the working tree inventory."""
680
681
            for unknown in self.work_tree.unknowns():
681
682
                raise StrictCommitFailed()
682
683
               
 
684
        report_changes = self.reporter.is_verbose()
683
685
        deleted_ids = []
684
686
        deleted_paths = set()
685
687
        work_inv = self.work_tree.inventory
686
688
        assert work_inv.root is not None
687
 
        entries = work_inv.iter_entries()
 
689
        entries = work_inv.iter_entries_by_dir(
 
690
            specific_file_ids=self.specific_file_ids, yield_parents=True)
688
691
        if not self.builder.record_root_entry:
689
692
            entries.next()
690
693
        for path, existing_ie in entries:
694
697
            kind = existing_ie.kind
695
698
            if kind == 'directory':
696
699
                self._next_progress_entry()
697
 
 
698
700
            # Skip files that have been deleted from the working tree.
699
701
            # The deleted files/directories are also recorded so they
700
702
            # can be explicitly unversioned later. Note that when a
702
704
            # deleted files matching that filter.
703
705
            if is_inside_any(deleted_paths, path):
704
706
                continue
705
 
            if not specific_files or is_inside_any(specific_files, path):
706
 
                if not self.work_tree.has_filename(path):
707
 
                    deleted_paths.add(path)
708
 
                    self.reporter.missing(path)
709
 
                    deleted_ids.append(file_id)
710
 
                    continue
 
707
            if not self.work_tree.has_filename(path):
 
708
                deleted_paths.add(path)
 
709
                self.reporter.missing(path)
 
710
                deleted_ids.append(file_id)
 
711
                continue
711
712
            try:
712
713
                kind = self.work_tree.kind(file_id)
713
714
                # TODO: specific_files filtering before nested tree processing
720
721
            # Note: I don't particularly want to have the existing_ie
721
722
            # parameter but the test suite currently (28-Jun-07) breaks
722
723
            # without it thanks to a unicode normalisation issue. :-(
723
 
            definitely_changed = kind != existing_ie.kind 
 
724
            definitely_changed = kind != existing_ie.kind
724
725
            self._record_entry(path, file_id, specific_files, kind, name,
725
 
                parent_id, definitely_changed, existing_ie)
 
726
                parent_id, definitely_changed, existing_ie, report_changes)
726
727
 
727
728
        # Unversion IDs that were found to be deleted
728
729
        self.work_tree.unversion(deleted_ids)
753
754
            pass
754
755
 
755
756
    def _record_entry(self, path, file_id, specific_files, kind, name,
756
 
                      parent_id, definitely_changed, existing_ie=None):
 
757
            parent_id, definitely_changed, existing_ie=None,
 
758
            report_changes=True):
757
759
        "Record the new inventory entry for a path if any."
758
760
        # mutter('check %s {%s}', path, file_id)
759
 
        if (not specific_files or 
760
 
            is_inside_or_parent_of_any(specific_files, path)):
761
 
                # mutter('%s selected for commit', path)
762
 
                if definitely_changed or existing_ie is None:
763
 
                    ie = inventory.make_entry(kind, name, parent_id, file_id)
764
 
                else:
765
 
                    ie = existing_ie.copy()
766
 
                    ie.revision = None
 
761
        # mutter('%s selected for commit', path)
 
762
        if definitely_changed or existing_ie is None:
 
763
            ie = inventory.make_entry(kind, name, parent_id, file_id)
767
764
        else:
768
 
            # mutter('%s not selected for commit', path)
769
 
            if self.basis_inv.has_id(file_id):
770
 
                ie = self.basis_inv[file_id].copy()
771
 
            else:
772
 
                # this entry is new and not being committed
773
 
                ie = None
774
 
        if ie is not None:
775
 
            self.builder.record_entry_contents(ie, self.parent_invs, 
776
 
                path, self.work_tree)
 
765
            ie = existing_ie.copy()
 
766
            ie.revision = None
 
767
        if self.builder.record_entry_contents(ie, self.parent_invs, 
 
768
            path, self.work_tree):
 
769
            self.any_entries_changed = True
 
770
        if report_changes:
777
771
            self._report_change(ie, path)
778
772
        return ie
779
773