~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Patch Queue Manager
  • Date: 2011-12-19 00:49:48 UTC
  • mfrom: (6379.2.1 lazy-eol-filters)
  • Revision ID: pqm@pqm.ubuntu.com-20111219004948-irl4u0gumo2a4sni
(jelmer) Lazily load bzrlib.filters.eol. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

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