~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Andrew Bennetts
  • Date: 2010-10-08 08:15:14 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101008081514-dviqzrdfwyzsqbz2
Split NEWS into per-release doc/en/release-notes/bzr-*.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
17
 
19
18
# The newly committed revision is going to have a shape corresponding
20
19
# to that of the working tree.  Files that are not in the
53
52
from bzrlib import (
54
53
    debug,
55
54
    errors,
 
55
    revision,
56
56
    trace,
57
57
    tree,
58
 
    ui,
59
58
    )
60
59
from bzrlib.branch import Branch
61
60
from bzrlib.cleanup import OperationWithCleanups
73
72
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
74
73
from bzrlib import symbol_versioning
75
74
from bzrlib.urlutils import unescape_for_display
76
 
from bzrlib.i18n import gettext
 
75
import bzrlib.ui
 
76
 
77
77
 
78
78
class NullCommitReporter(object):
79
79
    """I report on progress of a commit."""
114
114
        note(format, *args)
115
115
 
116
116
    def snapshot_change(self, change, path):
117
 
        if path == '' and change in (gettext('added'), gettext('modified')):
 
117
        if path == '' and change in ('added', 'modified'):
118
118
            return
119
119
        self._note("%s %s", change, path)
120
120
 
128
128
                                   "to started.", DeprecationWarning,
129
129
                                   stacklevel=2)
130
130
            location = ''
131
 
        self._note(gettext('Committing%s'), location)
 
131
        self._note('Committing%s', location)
132
132
 
133
133
    def completed(self, revno, rev_id):
134
 
        self._note(gettext('Committed revision %d.'), revno)
 
134
        self._note('Committed revision %d.', revno)
135
135
        # self._note goes to the console too; so while we want to log the
136
136
        # rev_id, we can't trivially only log it. (See bug 526425). Long
137
137
        # term we should rearrange the reporting structure, but for now
140
140
        mutter('Committed revid %s as revno %d.', rev_id, revno)
141
141
 
142
142
    def deleted(self, path):
143
 
        self._note(gettext('deleted %s'), path)
 
143
        self._note('deleted %s', path)
144
144
 
145
145
    def missing(self, path):
146
 
        self._note(gettext('missing %s'), path)
 
146
        self._note('missing %s', path)
147
147
 
148
148
    def renamed(self, change, old_path, new_path):
149
149
        self._note('%s %s => %s', change, old_path, new_path)
166
166
    """
167
167
    def __init__(self,
168
168
                 reporter=None,
169
 
                 config_stack=None):
 
169
                 config=None):
170
170
        """Create a Commit object.
171
171
 
172
172
        :param reporter: the default reporter to use or None to decide later
173
173
        """
174
174
        self.reporter = reporter
175
 
        self.config_stack = config_stack
 
175
        self.config = config
176
176
 
177
177
    @staticmethod
178
178
    def update_revprops(revprops, branch, authors=None, author=None,
229
229
               message_callback=None,
230
230
               recursive='down',
231
231
               exclude=None,
232
 
               possible_master_transports=None,
233
 
               lossy=False):
 
232
               possible_master_transports=None):
234
233
        """Commit working copy as a new revision.
235
234
 
236
235
        :param message: the commit message (it or message_callback is required)
263
262
        :param exclude: None or a list of relative paths to exclude from the
264
263
            commit. Pending changes to excluded files will be ignored by the
265
264
            commit.
266
 
        :param lossy: When committing to a foreign VCS, ignore any
267
 
            data that can not be natively represented.
268
265
        """
269
266
        operation = OperationWithCleanups(self._commit)
270
267
        self.revprops = revprops or {}
271
268
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
272
 
        self.config_stack = config or self.config_stack
 
269
        self.config = config or self.config
273
270
        return operation.run(
274
271
               message=message,
275
272
               timestamp=timestamp,
286
283
               message_callback=message_callback,
287
284
               recursive=recursive,
288
285
               exclude=exclude,
289
 
               possible_master_transports=possible_master_transports,
290
 
               lossy=lossy)
 
286
               possible_master_transports=possible_master_transports)
291
287
 
292
288
    def _commit(self, operation, message, timestamp, timezone, committer,
293
289
            specific_files, rev_id, allow_pointless, strict, verbose,
294
290
            working_tree, local, reporter, message_callback, recursive,
295
 
            exclude, possible_master_transports, lossy):
 
291
            exclude, possible_master_transports):
296
292
        mutter('preparing to commit')
297
293
 
298
294
        if working_tree is None:
330
326
                minimum_path_selection(specific_files))
331
327
        else:
332
328
            self.specific_files = None
333
 
 
 
329
            
334
330
        self.allow_pointless = allow_pointless
335
331
        self.message_callback = message_callback
336
332
        self.timestamp = timestamp
350
346
            not self.branch.repository._format.supports_tree_reference and
351
347
            (self.branch.repository._format.fast_deltas or
352
348
             len(self.parents) < 2))
353
 
        self.pb = ui.ui_factory.nested_progress_bar()
 
349
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
354
350
        operation.add_cleanup(self.pb.finished)
355
351
        self.basis_revid = self.work_tree.last_revision()
356
352
        self.basis_tree = self.work_tree.basis_tree()
364
360
        self._check_bound_branch(operation, possible_master_transports)
365
361
 
366
362
        # Check that the working tree is up to date
367
 
        old_revno, old_revid, new_revno = self._check_out_of_date_tree()
 
363
        old_revno, new_revno = self._check_out_of_date_tree()
368
364
 
369
365
        # Complete configuration setup
370
366
        if reporter is not None:
371
367
            self.reporter = reporter
372
368
        elif self.reporter is None:
373
369
            self.reporter = self._select_reporter()
374
 
        if self.config_stack is None:
375
 
            self.config_stack = self.work_tree.get_config_stack()
 
370
        if self.config is None:
 
371
            self.config = self.branch.get_config()
376
372
 
377
373
        self._set_specific_file_ids()
378
374
 
384
380
        self.pb_stage_count = 0
385
381
        self.pb_stage_total = 5
386
382
        if self.bound_branch:
387
 
            # 2 extra stages: "Uploading data to master branch" and "Merging
388
 
            # tags to master branch"
389
 
            self.pb_stage_total += 2
 
383
            self.pb_stage_total += 1
390
384
        self.pb.show_pct = False
391
385
        self.pb.show_spinner = False
392
386
        self.pb.show_eta = False
404
398
 
405
399
        # Collect the changes
406
400
        self._set_progress_stage("Collecting changes", counter=True)
407
 
        self._lossy = lossy
408
401
        self.builder = self.branch.get_commit_builder(self.parents,
409
 
            self.config_stack, timestamp, timezone, committer, self.revprops,
410
 
            rev_id, lossy=lossy)
411
 
        if not self.builder.supports_record_entry_contents and self.exclude:
412
 
            self.builder.abort()
413
 
            raise errors.ExcludesUnsupported(self.branch.repository)
414
 
 
415
 
        if self.builder.updates_branch and self.bound_branch:
416
 
            self.builder.abort()
417
 
            raise AssertionError(
418
 
                "bound branches not supported for commit builders "
419
 
                "that update the branch")
 
402
            self.config, timestamp, timezone, committer, self.revprops, rev_id)
420
403
 
421
404
        try:
422
405
            self.builder.will_record_deletes()
449
432
        except Exception, e:
450
433
            mutter("aborting commit write group because of exception:")
451
434
            trace.log_exception_quietly()
 
435
            note("aborting commit write group: %r" % (e,))
452
436
            self.builder.abort()
453
437
            raise
454
438
 
455
 
        self._update_branches(old_revno, old_revid, new_revno)
 
439
        self._process_pre_hooks(old_revno, new_revno)
 
440
 
 
441
        # Upload revision data to the master.
 
442
        # this will propagate merged revisions too if needed.
 
443
        if self.bound_branch:
 
444
            self._set_progress_stage("Uploading data to master branch")
 
445
            # 'commit' to the master first so a timeout here causes the
 
446
            # local branch to be out of date
 
447
            self.master_branch.import_last_revision_info(
 
448
                self.branch.repository, new_revno, self.rev_id)
 
449
 
 
450
        # and now do the commit locally.
 
451
        self.branch.set_last_revision_info(new_revno, self.rev_id)
456
452
 
457
453
        # Make the working tree be up to date with the branch. This
458
454
        # includes automatic changes scheduled to be made to the tree, such
465
461
        self._process_post_hooks(old_revno, new_revno)
466
462
        return self.rev_id
467
463
 
468
 
    def _update_branches(self, old_revno, old_revid, new_revno):
469
 
        """Update the master and local branch to the new revision.
470
 
 
471
 
        This will try to make sure that the master branch is updated
472
 
        before the local branch.
473
 
 
474
 
        :param old_revno: Revision number of master branch before the
475
 
            commit
476
 
        :param old_revid: Tip of master branch before the commit
477
 
        :param new_revno: Revision number of the new commit
478
 
        """
479
 
        if not self.builder.updates_branch:
480
 
            self._process_pre_hooks(old_revno, new_revno)
481
 
 
482
 
            # Upload revision data to the master.
483
 
            # this will propagate merged revisions too if needed.
484
 
            if self.bound_branch:
485
 
                self._set_progress_stage("Uploading data to master branch")
486
 
                # 'commit' to the master first so a timeout here causes the
487
 
                # local branch to be out of date
488
 
                (new_revno, self.rev_id) = self.master_branch.import_last_revision_info_and_tags(
489
 
                    self.branch, new_revno, self.rev_id, lossy=self._lossy)
490
 
                if self._lossy:
491
 
                    self.branch.fetch(self.master_branch, self.rev_id)
492
 
 
493
 
            # and now do the commit locally.
494
 
            self.branch.set_last_revision_info(new_revno, self.rev_id)
495
 
        else:
496
 
            try:
497
 
                self._process_pre_hooks(old_revno, new_revno)
498
 
            except:
499
 
                # The commit builder will already have updated the branch,
500
 
                # revert it.
501
 
                self.branch.set_last_revision_info(old_revno, old_revid)
502
 
                raise
503
 
 
504
 
        # Merge local tags to remote
505
 
        if self.bound_branch:
506
 
            self._set_progress_stage("Merging tags to master branch")
507
 
            tag_updates, tag_conflicts = self.branch.tags.merge_to(
508
 
                self.master_branch.tags)
509
 
            if tag_conflicts:
510
 
                warning_lines = ['    ' + name for name, _, _ in tag_conflicts]
511
 
                note( gettext("Conflicting tags in bound branch:\n{0}".format(
512
 
                    "\n".join(warning_lines))) )
513
 
 
514
464
    def _select_reporter(self):
515
465
        """Select the CommitReporter to use."""
516
466
        if is_quiet():
523
473
        # A merge with no effect on files
524
474
        if len(self.parents) > 1:
525
475
            return
 
476
        # TODO: we could simplify this by using self.builder.basis_delta.
 
477
 
 
478
        # The initial commit adds a root directory, but this in itself is not
 
479
        # a worthwhile commit.
 
480
        if (self.basis_revid == revision.NULL_REVISION and
 
481
            ((self.builder.new_inventory is not None and
 
482
             len(self.builder.new_inventory) == 1) or
 
483
            len(self.builder._basis_delta) == 1)):
 
484
            raise PointlessCommit()
526
485
        if self.builder.any_changes():
527
486
            return
528
487
        raise PointlessCommit()
573
532
    def _check_out_of_date_tree(self):
574
533
        """Check that the working tree is up to date.
575
534
 
576
 
        :return: old_revision_number, old_revision_id, new_revision_number
577
 
            tuple
 
535
        :return: old_revision_number,new_revision_number tuple
578
536
        """
579
537
        try:
580
538
            first_tree_parent = self.work_tree.get_parent_ids()[0]
593
551
        else:
594
552
            # ghost parents never appear in revision history.
595
553
            new_revno = 1
596
 
        return old_revno, master_last, new_revno
 
554
        return old_revno,new_revno
597
555
 
598
556
    def _process_pre_hooks(self, old_revno, new_revno):
599
557
        """Process any registered pre commit hooks."""
605
563
        # Process the post commit hooks, if any
606
564
        self._set_progress_stage("Running post_commit hooks")
607
565
        # old style commit hooks - should be deprecated ? (obsoleted in
608
 
        # 0.15^H^H^H^H 2.5.0)
609
 
        post_commit = self.config_stack.get('post_commit')
610
 
        if post_commit is not None:
611
 
            hooks = post_commit.split(' ')
 
566
        # 0.15)
 
567
        if self.config.post_commit() is not None:
 
568
            hooks = self.config.post_commit().split(' ')
612
569
            # this would be nicer with twisted.python.reflect.namedAny
613
570
            for hook in hooks:
614
571
                result = eval(hook + '(branch, rev_id)',
667
624
        # entries and the order is preserved when doing this.
668
625
        if self.use_record_iter_changes:
669
626
            return
670
 
        self.basis_inv = self.basis_tree.root_inventory
 
627
        self.basis_inv = self.basis_tree.inventory
671
628
        self.parent_invs = [self.basis_inv]
672
629
        for revision in self.parents[1:]:
673
630
            if self.branch.repository.has_revision(revision):
726
683
                # Reset the new path (None) and new versioned flag (False)
727
684
                change = (change[0], (change[1][0], None), change[2],
728
685
                    (change[3][0], False)) + change[4:]
729
 
                new_path = change[1][1]
730
 
                versioned = False
731
686
            elif kind == 'tree-reference':
732
687
                if self.recursive == 'down':
733
688
                    self._commit_nested_tree(change[0], change[1][1])
737
692
                    if new_path is None:
738
693
                        reporter.deleted(old_path)
739
694
                    elif old_path is None:
740
 
                        reporter.snapshot_change(gettext('added'), new_path)
 
695
                        reporter.snapshot_change('added', new_path)
741
696
                    elif old_path != new_path:
742
 
                        reporter.renamed(gettext('renamed'), old_path, new_path)
 
697
                        reporter.renamed('renamed', old_path, new_path)
743
698
                    else:
744
699
                        if (new_path or 
745
700
                            self.work_tree.branch.repository._format.rich_root_data):
746
701
                            # Don't report on changes to '' in non rich root
747
702
                            # repositories.
748
 
                            reporter.snapshot_change(gettext('modified'), new_path)
 
703
                            reporter.snapshot_change('modified', new_path)
749
704
            self._next_progress_entry()
750
705
        # Unversion IDs that were found to be deleted
751
706
        self.deleted_ids = deleted_ids
757
712
        if self.specific_files or self.exclude:
758
713
            specific_files = self.specific_files or []
759
714
            for path, old_ie in self.basis_inv.iter_entries():
760
 
                if self.builder.new_inventory.has_id(old_ie.file_id):
 
715
                if old_ie.file_id in self.builder.new_inventory:
761
716
                    # already added - skip.
762
717
                    continue
763
718
                if (is_inside_any(specific_files, path)
834
789
        deleted_paths = {}
835
790
        # XXX: Note that entries may have the wrong kind because the entry does
836
791
        # not reflect the status on disk.
 
792
        work_inv = self.work_tree.inventory
837
793
        # NB: entries will include entries within the excluded ids/paths
838
794
        # because iter_entries_by_dir has no 'exclude' facility today.
839
 
        entries = self.work_tree.iter_entries_by_dir(
 
795
        entries = work_inv.iter_entries_by_dir(
840
796
            specific_file_ids=self.specific_file_ids, yield_parents=True)
841
797
        for path, existing_ie in entries:
842
798
            file_id = existing_ie.file_id
973
929
            self.reporter.renamed(change, old_path, path)
974
930
            self._next_progress_entry()
975
931
        else:
976
 
            if change == gettext('unchanged'):
 
932
            if change == 'unchanged':
977
933
                return
978
934
            self.reporter.snapshot_change(change, path)
979
935
            self._next_progress_entry()
995
951
 
996
952
    def _emit_progress(self):
997
953
        if self.pb_entries_count is not None:
998
 
            text = gettext("{0} [{1}] - Stage").format(self.pb_stage_name,
 
954
            text = "%s [%d] - Stage" % (self.pb_stage_name,
999
955
                self.pb_entries_count)
1000
956
        else:
1001
 
            text = gettext("%s - Stage") % (self.pb_stage_name, )
 
957
            text = "%s - Stage" % (self.pb_stage_name, )
1002
958
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
1003
959
 
1004
960
    def _set_specific_file_ids(self):