~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Aaron Bentley
  • Date: 2007-12-09 23:53:50 UTC
  • mto: This revision was merged to the branch mainline in revision 3133.
  • Revision ID: aaron.bentley@utoronto.ca-20071209235350-qp39yk0xzx7a4f6p
Don't use the base if not cherrypicking

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
60
60
    debug,
61
61
    errors,
62
62
    revision,
63
 
    trace,
64
63
    tree,
65
64
    )
66
65
from bzrlib.branch import Branch
69
68
                           ConflictsInTree,
70
69
                           StrictCommitFailed
71
70
                           )
72
 
from bzrlib.osutils import (get_user_encoding,
73
 
                            kind_marker, isdir,isfile, is_inside_any,
 
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
74
72
                            is_inside_or_parent_of_any,
75
73
                            minimum_path_selection,
76
74
                            quotefn, sha_file, split_lines,
78
76
                            )
79
77
from bzrlib.testament import Testament
80
78
from bzrlib.trace import mutter, note, warning, is_quiet
81
 
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
 
79
from bzrlib.xml5 import serializer_v5
 
80
from bzrlib.inventory import InventoryEntry, make_entry
82
81
from bzrlib import symbol_versioning
83
82
from bzrlib.symbol_versioning import (deprecated_passed,
84
83
        deprecated_function,
205
204
               reporter=None,
206
205
               config=None,
207
206
               message_callback=None,
208
 
               recursive='down',
209
 
               exclude=None,
210
 
               possible_master_transports=None):
 
207
               recursive='down'):
211
208
        """Commit working copy as a new revision.
212
209
 
213
210
        :param message: the commit message (it or message_callback is required)
235
232
        :param verbose: if True and the reporter is not None, report everything
236
233
        :param recursive: If set to 'down', commit in any subtrees that have
237
234
            pending changes of any sort during this commit.
238
 
        :param exclude: None or a list of relative paths to exclude from the
239
 
            commit. Pending changes to excluded files will be ignored by the
240
 
            commit. 
241
235
        """
242
236
        mutter('preparing to commit')
243
237
 
252
246
        if message_callback is None:
253
247
            if message is not None:
254
248
                if isinstance(message, str):
255
 
                    message = message.decode(get_user_encoding())
 
249
                    message = message.decode(bzrlib.user_encoding)
256
250
                message_callback = lambda x: message
257
251
            else:
258
252
                raise BzrError("The message or message_callback keyword"
261
255
        self.bound_branch = None
262
256
        self.any_entries_changed = False
263
257
        self.any_entries_deleted = False
264
 
        if exclude is not None:
265
 
            self.exclude = sorted(
266
 
                minimum_path_selection(exclude))
267
 
        else:
268
 
            self.exclude = []
269
258
        self.local = local
270
259
        self.master_branch = None
271
260
        self.master_locked = False
285
274
        self.committer = committer
286
275
        self.strict = strict
287
276
        self.verbose = verbose
 
277
        # accumulates an inventory delta to the basis entry, so we can make
 
278
        # just the necessary updates to the workingtree's cached basis.
 
279
        self._basis_delta = []
288
280
 
289
281
        self.work_tree.lock_write()
290
282
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
297
289
                raise ConflictsInTree
298
290
 
299
291
            # Setup the bound branch variables as needed.
300
 
            self._check_bound_branch(possible_master_transports)
 
292
            self._check_bound_branch()
301
293
 
302
294
            # Check that the working tree is up to date
303
295
            old_revno, new_revno = self._check_out_of_date_tree()
337
329
            self.pb.show_count = True
338
330
            self.pb.show_bar = True
339
331
 
 
332
            # After a merge, a selected file commit is not supported.
 
333
            # See 'bzr help merge' for an explanation as to why.
340
334
            self.basis_inv = self.basis_tree.inventory
341
335
            self._gather_parents()
342
 
            # After a merge, a selected file commit is not supported.
343
 
            # See 'bzr help merge' for an explanation as to why.
344
336
            if len(self.parents) > 1 and self.specific_files:
345
337
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
346
 
            # Excludes are a form of selected file commit.
347
 
            if len(self.parents) > 1 and self.exclude:
348
 
                raise errors.CannotCommitSelectedFileMerge(self.exclude)
349
338
 
350
339
            # Collect the changes
351
340
            self._set_progress_stage("Collecting changes",
352
341
                    entries_title="Directory")
353
342
            self.builder = self.branch.get_commit_builder(self.parents,
354
343
                self.config, timestamp, timezone, committer, revprops, rev_id)
355
 
 
 
344
            
356
345
            try:
357
 
                self.builder.will_record_deletes()
358
346
                # find the location being committed to
359
347
                if self.bound_branch:
360
348
                    master_location = self.master_branch.base
377
365
 
378
366
                # Prompt the user for a commit message if none provided
379
367
                message = message_callback(self)
 
368
                assert isinstance(message, unicode), type(message)
380
369
                self.message = message
381
370
                self._escape_commit_message()
382
371
 
383
372
                # Add revision data to the local branch
384
373
                self.rev_id = self.builder.commit(self.message)
385
374
 
386
 
            except Exception, e:
387
 
                mutter("aborting commit write group because of exception:")
388
 
                trace.log_exception_quietly()
389
 
                note("aborting commit write group: %r" % (e,))
 
375
            except:
390
376
                self.builder.abort()
391
377
                raise
392
378
 
395
381
            # Upload revision data to the master.
396
382
            # this will propagate merged revisions too if needed.
397
383
            if self.bound_branch:
398
 
                if not self.master_branch.repository.has_same_location(
399
 
                        self.branch.repository):
400
 
                    self._set_progress_stage("Uploading data to master branch")
401
 
                    self.master_branch.repository.fetch(self.branch.repository,
402
 
                        revision_id=self.rev_id)
 
384
                self._set_progress_stage("Uploading data to master branch")
 
385
                self.master_branch.repository.fetch(self.branch.repository,
 
386
                                                    revision_id=self.rev_id)
403
387
                # now the master has the revision data
404
388
                # 'commit' to the master first so a timeout here causes the
405
389
                # local branch to be out of date
412
396
            # Make the working tree up to date with the branch
413
397
            self._set_progress_stage("Updating the working tree")
414
398
            self.work_tree.update_basis_by_delta(self.rev_id,
415
 
                 self.builder.get_basis_delta())
 
399
                 self._basis_delta)
416
400
            self.reporter.completed(new_revno, self.rev_id)
417
401
            self._process_post_hooks(old_revno, new_revno)
418
402
        finally:
431
415
        # A merge with no effect on files
432
416
        if len(self.parents) > 1:
433
417
            return
434
 
        # TODO: we could simplify this by using self.builder.basis_delta.
 
418
        # TODO: we could simplify this by using self._basis_delta.
435
419
 
436
420
        # The initial commit adds a root directory, but this in itself is not
437
421
        # a worthwhile commit.
447
431
            return
448
432
        raise PointlessCommit()
449
433
 
450
 
    def _check_bound_branch(self, possible_master_transports=None):
 
434
    def _check_bound_branch(self):
451
435
        """Check to see if the local branch is bound.
452
436
 
453
437
        If it is bound, then most of the commit will actually be
458
442
            raise errors.LocalRequiresBoundBranch()
459
443
 
460
444
        if not self.local:
461
 
            self.master_branch = self.branch.get_master_branch(
462
 
                possible_master_transports)
 
445
            self.master_branch = self.branch.get_master_branch()
463
446
 
464
447
        if not self.master_branch:
465
448
            # make this branch the reference branch for out of date checks.
664
647
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
665
648
        # ADHB 11-07-2006
666
649
 
667
 
        exclude = self.exclude
668
 
        specific_files = self.specific_files or []
 
650
        specific_files = self.specific_files
669
651
        mutter("Selecting files for commit with filter %s", specific_files)
670
652
 
671
653
        # Build the new inventory
672
 
        self._populate_from_inventory()
 
654
        self._populate_from_inventory(specific_files)
673
655
 
674
656
        # If specific files are selected, then all un-selected files must be
675
657
        # recorded in their previous state. For more details, see
676
658
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
677
 
        if specific_files or exclude:
 
659
        if specific_files:
678
660
            for path, old_ie in self.basis_inv.iter_entries():
679
661
                if old_ie.file_id in self.builder.new_inventory:
680
662
                    # already added - skip.
681
663
                    continue
682
 
                if (is_inside_any(specific_files, path)
683
 
                    and not is_inside_any(exclude, path)):
684
 
                    # was inside the selected path, and not excluded - if not
685
 
                    # present it has been deleted so skip.
 
664
                if is_inside_any(specific_files, path):
 
665
                    # was inside the selected path, if not present it has been
 
666
                    # deleted so skip.
686
667
                    continue
687
 
                # From here down it was either not selected, or was excluded:
688
668
                if old_ie.kind == 'directory':
689
669
                    self._next_progress_entry()
690
 
                # We preserve the entry unaltered.
 
670
                # not in final inv yet, was not in the selected files, so is an
 
671
                # entry to be preserved unaltered.
691
672
                ie = old_ie.copy()
692
673
                # Note: specific file commits after a merge are currently
693
674
                # prohibited. This test is for sanity/safety in case it's
694
675
                # required after that changes.
695
676
                if len(self.parents) > 1:
696
677
                    ie.revision = None
697
 
                _, version_recorded, _ = self.builder.record_entry_contents(
 
678
                delta, version_recorded = self.builder.record_entry_contents(
698
679
                    ie, self.parent_invs, path, self.basis_tree, None)
699
680
                if version_recorded:
700
681
                    self.any_entries_changed = True
 
682
                if delta: self._basis_delta.append(delta)
701
683
 
702
684
    def _report_and_accumulate_deletes(self):
703
685
        # XXX: Could the list of deleted paths and ids be instead taken from
704
686
        # _populate_from_inventory?
705
 
        if (isinstance(self.basis_inv, Inventory)
706
 
            and isinstance(self.builder.new_inventory, Inventory)):
707
 
            # the older Inventory classes provide a _byid dict, and building a
708
 
            # set from the keys of this dict is substantially faster than even
709
 
            # getting a set of ids from the inventory
710
 
            #
711
 
            # <lifeless> set(dict) is roughly the same speed as
712
 
            # set(iter(dict)) and both are significantly slower than
713
 
            # set(dict.keys())
714
 
            deleted_ids = set(self.basis_inv._byid.keys()) - \
715
 
               set(self.builder.new_inventory._byid.keys())
716
 
        else:
717
 
            deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
 
687
        deleted_ids = set(self.basis_inv._byid.keys()) - \
 
688
            set(self.builder.new_inventory._byid.keys())
718
689
        if deleted_ids:
719
690
            self.any_entries_deleted = True
720
691
            deleted = [(self.basis_tree.id2path(file_id), file_id)
722
693
            deleted.sort()
723
694
            # XXX: this is not quite directory-order sorting
724
695
            for path, file_id in deleted:
725
 
                self.builder.record_delete(path, file_id)
 
696
                self._basis_delta.append((path, None, file_id, None))
726
697
                self.reporter.deleted(path)
727
698
 
728
 
    def _populate_from_inventory(self):
 
699
    def _populate_from_inventory(self, specific_files):
729
700
        """Populate the CommitBuilder by walking the working tree inventory."""
730
701
        if self.strict:
731
702
            # raise an exception as soon as we find a single unknown.
732
703
            for unknown in self.work_tree.unknowns():
733
704
                raise StrictCommitFailed()
734
 
        
735
 
        specific_files = self.specific_files
736
 
        exclude = self.exclude
 
705
               
737
706
        report_changes = self.reporter.is_verbose()
738
707
        deleted_ids = []
739
708
        # A tree of paths that have been deleted. E.g. if foo/bar has been
742
711
        # XXX: Note that entries may have the wrong kind because the entry does
743
712
        # not reflect the status on disk.
744
713
        work_inv = self.work_tree.inventory
745
 
        # NB: entries will include entries within the excluded ids/paths
746
 
        # because iter_entries_by_dir has no 'exclude' facility today.
747
714
        entries = work_inv.iter_entries_by_dir(
748
715
            specific_file_ids=self.specific_file_ids, yield_parents=True)
749
716
        for path, existing_ie in entries:
771
738
                if deleted_dict is not None:
772
739
                    # the path has a deleted parent, do not add it.
773
740
                    continue
774
 
            if exclude and is_inside_any(exclude, path):
775
 
                # Skip excluded paths. Excluded paths are processed by
776
 
                # _update_builder_with_changes.
777
 
                continue
778
741
            content_summary = self.work_tree.path_content_summary(path)
779
742
            # Note that when a filter of specific files is given, we must only
780
743
            # skip/record deleted files matching that filter.
857
820
        else:
858
821
            ie = existing_ie.copy()
859
822
            ie.revision = None
860
 
        # For carried over entries we don't care about the fs hash - the repo
861
 
        # isn't generating a sha, so we're not saving computation time.
862
 
        _, version_recorded, fs_hash = self.builder.record_entry_contents(
863
 
            ie, self.parent_invs, path, self.work_tree, content_summary)
 
823
        delta, version_recorded = self.builder.record_entry_contents(ie,
 
824
            self.parent_invs, path, self.work_tree, content_summary)
 
825
        if delta:
 
826
            self._basis_delta.append(delta)
864
827
        if version_recorded:
865
828
            self.any_entries_changed = True
866
829
        if report_changes:
867
830
            self._report_change(ie, path)
868
 
        if fs_hash:
869
 
            self.work_tree._observed_sha1(ie.file_id, path, fs_hash)
870
831
        return ie
871
832
 
872
833
    def _report_change(self, ie, path):