/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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
50
49
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
51
50
# the rest of the code; add a deprecation of the old name.
52
51
 
53
 
from . import (
 
52
from bzrlib import (
54
53
    debug,
55
54
    errors,
 
55
    revision,
56
56
    trace,
57
57
    tree,
58
 
    ui,
59
58
    )
60
 
from .branch import Branch
61
 
from .cleanup import OperationWithCleanups
62
 
import breezy.config
63
 
from .errors import (BzrError, PointlessCommit,
64
 
                     ConflictsInTree,
65
 
                     StrictCommitFailed
66
 
                     )
67
 
from .osutils import (get_user_encoding,
68
 
                      is_inside_any,
69
 
                      minimum_path_selection,
70
 
                      splitpath,
71
 
                      )
72
 
from .trace import mutter, note, is_quiet
73
 
from .inventory import Inventory, InventoryEntry, make_entry
74
 
from .urlutils import unescape_for_display
75
 
from .i18n import gettext
 
59
from bzrlib.branch import Branch
 
60
from bzrlib.cleanup import OperationWithCleanups
 
61
import bzrlib.config
 
62
from bzrlib.errors import (BzrError, PointlessCommit,
 
63
                           ConflictsInTree,
 
64
                           StrictCommitFailed
 
65
                           )
 
66
from bzrlib.osutils import (get_user_encoding,
 
67
                            is_inside_any,
 
68
                            minimum_path_selection,
 
69
                            splitpath,
 
70
                            )
 
71
from bzrlib.trace import mutter, note, is_quiet
 
72
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
 
73
from bzrlib import symbol_versioning
 
74
from bzrlib.urlutils import unescape_for_display
 
75
import bzrlib.ui
76
76
 
77
77
 
78
78
class NullCommitReporter(object):
79
79
    """I report on progress of a commit."""
80
80
 
81
 
    def started(self, revno, revid, location):
 
81
    def started(self, revno, revid, location=None):
 
82
        if location is None:
 
83
            symbol_versioning.warn("As of bzr 1.0 you must pass a location "
 
84
                                   "to started.", DeprecationWarning,
 
85
                                   stacklevel=2)
82
86
        pass
83
87
 
84
88
    def snapshot_change(self, change, path):
110
114
        note(format, *args)
111
115
 
112
116
    def snapshot_change(self, change, path):
113
 
        if path == '' and change in (gettext('added'), gettext('modified')):
 
117
        if path == '' and change in ('added', 'modified'):
114
118
            return
115
119
        self._note("%s %s", change, path)
116
120
 
117
 
    def started(self, revno, rev_id, location):
118
 
        self._note(
119
 
            gettext('Committing to: %s'),
120
 
            unescape_for_display(location, 'utf-8'))
 
121
    def started(self, revno, rev_id, location=None):
 
122
        if location is not None:
 
123
            location = ' to: ' + unescape_for_display(location, 'utf-8')
 
124
        else:
 
125
            # When started was added, location was only made optional by
 
126
            # accident.  Matt Nordhoff 20071129
 
127
            symbol_versioning.warn("As of bzr 1.0 you must pass a location "
 
128
                                   "to started.", DeprecationWarning,
 
129
                                   stacklevel=2)
 
130
            location = ''
 
131
        self._note('Committing%s', location)
121
132
 
122
133
    def completed(self, revno, rev_id):
123
 
        self._note(gettext('Committed revision %d.'), revno)
 
134
        self._note('Committed revision %d.', revno)
124
135
        # self._note goes to the console too; so while we want to log the
125
136
        # rev_id, we can't trivially only log it. (See bug 526425). Long
126
137
        # term we should rearrange the reporting structure, but for now
129
140
        mutter('Committed revid %s as revno %d.', rev_id, revno)
130
141
 
131
142
    def deleted(self, path):
132
 
        self._note(gettext('deleted %s'), path)
 
143
        self._note('deleted %s', path)
133
144
 
134
145
    def missing(self, path):
135
 
        self._note(gettext('missing %s'), path)
 
146
        self._note('missing %s', path)
136
147
 
137
148
    def renamed(self, change, old_path, new_path):
138
149
        self._note('%s %s => %s', change, old_path, new_path)
155
166
    """
156
167
    def __init__(self,
157
168
                 reporter=None,
158
 
                 config_stack=None):
 
169
                 config=None):
159
170
        """Create a Commit object.
160
171
 
161
172
        :param reporter: the default reporter to use or None to decide later
162
173
        """
163
174
        self.reporter = reporter
164
 
        self.config_stack = config_stack
165
 
 
166
 
    @staticmethod
167
 
    def update_revprops(revprops, branch, authors=None,
168
 
                        local=False, possible_master_transports=None):
169
 
        if revprops is None:
170
 
            revprops = {}
171
 
        if possible_master_transports is None:
172
 
            possible_master_transports = []
173
 
        if not 'branch-nick' in revprops:
174
 
            revprops['branch-nick'] = branch._get_nick(
175
 
                local,
176
 
                possible_master_transports)
177
 
        if authors is not None:
178
 
            if 'author' in revprops or 'authors' in revprops:
179
 
                # XXX: maybe we should just accept one of them?
180
 
                raise AssertionError('author property given twice')
181
 
            if authors:
182
 
                for individual in authors:
183
 
                    if '\n' in individual:
184
 
                        raise AssertionError('\\n is not a valid character '
185
 
                                'in an author identity')
186
 
                revprops['authors'] = '\n'.join(authors)
187
 
        return revprops
 
175
        self.config = config
188
176
 
189
177
    def commit(self,
190
178
               message=None,
204
192
               message_callback=None,
205
193
               recursive='down',
206
194
               exclude=None,
207
 
               possible_master_transports=None,
208
 
               lossy=False):
 
195
               possible_master_transports=None):
209
196
        """Commit working copy as a new revision.
210
197
 
211
198
        :param message: the commit message (it or message_callback is required)
238
225
        :param exclude: None or a list of relative paths to exclude from the
239
226
            commit. Pending changes to excluded files will be ignored by the
240
227
            commit.
241
 
        :param lossy: When committing to a foreign VCS, ignore any
242
 
            data that can not be natively represented.
243
228
        """
244
229
        operation = OperationWithCleanups(self._commit)
245
230
        self.revprops = revprops or {}
246
231
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
247
 
        self.config_stack = config or self.config_stack
 
232
        self.config = config or self.config
248
233
        return operation.run(
249
234
               message=message,
250
235
               timestamp=timestamp,
261
246
               message_callback=message_callback,
262
247
               recursive=recursive,
263
248
               exclude=exclude,
264
 
               possible_master_transports=possible_master_transports,
265
 
               lossy=lossy)
 
249
               possible_master_transports=possible_master_transports)
266
250
 
267
251
    def _commit(self, operation, message, timestamp, timezone, committer,
268
252
            specific_files, rev_id, allow_pointless, strict, verbose,
269
253
            working_tree, local, reporter, message_callback, recursive,
270
 
            exclude, possible_master_transports, lossy):
 
254
            exclude, possible_master_transports):
271
255
        mutter('preparing to commit')
272
256
 
273
257
        if working_tree is None:
305
289
                minimum_path_selection(specific_files))
306
290
        else:
307
291
            self.specific_files = None
308
 
 
 
292
            
309
293
        self.allow_pointless = allow_pointless
310
294
        self.message_callback = message_callback
311
295
        self.timestamp = timestamp
325
309
            not self.branch.repository._format.supports_tree_reference and
326
310
            (self.branch.repository._format.fast_deltas or
327
311
             len(self.parents) < 2))
328
 
        self.pb = ui.ui_factory.nested_progress_bar()
 
312
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
329
313
        operation.add_cleanup(self.pb.finished)
330
314
        self.basis_revid = self.work_tree.last_revision()
331
315
        self.basis_tree = self.work_tree.basis_tree()
339
323
        self._check_bound_branch(operation, possible_master_transports)
340
324
 
341
325
        # Check that the working tree is up to date
342
 
        old_revno, old_revid, new_revno = self._check_out_of_date_tree()
 
326
        old_revno, new_revno = self._check_out_of_date_tree()
343
327
 
344
328
        # Complete configuration setup
345
329
        if reporter is not None:
346
330
            self.reporter = reporter
347
331
        elif self.reporter is None:
348
332
            self.reporter = self._select_reporter()
349
 
        if self.config_stack is None:
350
 
            self.config_stack = self.work_tree.get_config_stack()
 
333
        if self.config is None:
 
334
            self.config = self.branch.get_config()
351
335
 
352
336
        self._set_specific_file_ids()
353
337
 
359
343
        self.pb_stage_count = 0
360
344
        self.pb_stage_total = 5
361
345
        if self.bound_branch:
362
 
            # 2 extra stages: "Uploading data to master branch" and "Merging
363
 
            # tags to master branch"
364
 
            self.pb_stage_total += 2
 
346
            self.pb_stage_total += 1
365
347
        self.pb.show_pct = False
366
348
        self.pb.show_spinner = False
367
349
        self.pb.show_eta = False
379
361
 
380
362
        # Collect the changes
381
363
        self._set_progress_stage("Collecting changes", counter=True)
382
 
        self._lossy = lossy
383
364
        self.builder = self.branch.get_commit_builder(self.parents,
384
 
            self.config_stack, timestamp, timezone, committer, self.revprops,
385
 
            rev_id, lossy=lossy)
386
 
        if not self.builder.supports_record_entry_contents and self.exclude:
387
 
            self.builder.abort()
388
 
            raise errors.ExcludesUnsupported(self.branch.repository)
389
 
 
390
 
        if self.builder.updates_branch and self.bound_branch:
391
 
            self.builder.abort()
392
 
            raise AssertionError(
393
 
                "bound branches not supported for commit builders "
394
 
                "that update the branch")
 
365
            self.config, timestamp, timezone, committer, self.revprops, rev_id)
395
366
 
396
367
        try:
397
368
            self.builder.will_record_deletes()
421
392
            # Add revision data to the local branch
422
393
            self.rev_id = self.builder.commit(self.message)
423
394
 
424
 
        except Exception as e:
 
395
        except Exception, e:
425
396
            mutter("aborting commit write group because of exception:")
426
397
            trace.log_exception_quietly()
 
398
            note("aborting commit write group: %r" % (e,))
427
399
            self.builder.abort()
428
400
            raise
429
401
 
430
 
        self._update_branches(old_revno, old_revid, new_revno)
 
402
        self._process_pre_hooks(old_revno, new_revno)
 
403
 
 
404
        # Upload revision data to the master.
 
405
        # this will propagate merged revisions too if needed.
 
406
        if self.bound_branch:
 
407
            self._set_progress_stage("Uploading data to master branch")
 
408
            # 'commit' to the master first so a timeout here causes the
 
409
            # local branch to be out of date
 
410
            self.master_branch.import_last_revision_info(
 
411
                self.branch.repository, new_revno, self.rev_id)
 
412
 
 
413
        # and now do the commit locally.
 
414
        self.branch.set_last_revision_info(new_revno, self.rev_id)
431
415
 
432
416
        # Make the working tree be up to date with the branch. This
433
417
        # includes automatic changes scheduled to be made to the tree, such
440
424
        self._process_post_hooks(old_revno, new_revno)
441
425
        return self.rev_id
442
426
 
443
 
    def _update_branches(self, old_revno, old_revid, new_revno):
444
 
        """Update the master and local branch to the new revision.
445
 
 
446
 
        This will try to make sure that the master branch is updated
447
 
        before the local branch.
448
 
 
449
 
        :param old_revno: Revision number of master branch before the
450
 
            commit
451
 
        :param old_revid: Tip of master branch before the commit
452
 
        :param new_revno: Revision number of the new commit
453
 
        """
454
 
        if not self.builder.updates_branch:
455
 
            self._process_pre_hooks(old_revno, new_revno)
456
 
 
457
 
            # Upload revision data to the master.
458
 
            # this will propagate merged revisions too if needed.
459
 
            if self.bound_branch:
460
 
                self._set_progress_stage("Uploading data to master branch")
461
 
                # 'commit' to the master first so a timeout here causes the
462
 
                # local branch to be out of date
463
 
                (new_revno, self.rev_id) = self.master_branch.import_last_revision_info_and_tags(
464
 
                    self.branch, new_revno, self.rev_id, lossy=self._lossy)
465
 
                if self._lossy:
466
 
                    self.branch.fetch(self.master_branch, self.rev_id)
467
 
 
468
 
            # and now do the commit locally.
469
 
            self.branch.set_last_revision_info(new_revno, self.rev_id)
470
 
        else:
471
 
            try:
472
 
                self._process_pre_hooks(old_revno, new_revno)
473
 
            except:
474
 
                # The commit builder will already have updated the branch,
475
 
                # revert it.
476
 
                self.branch.set_last_revision_info(old_revno, old_revid)
477
 
                raise
478
 
 
479
 
        # Merge local tags to remote
480
 
        if self.bound_branch:
481
 
            self._set_progress_stage("Merging tags to master branch")
482
 
            tag_updates, tag_conflicts = self.branch.tags.merge_to(
483
 
                self.master_branch.tags)
484
 
            if tag_conflicts:
485
 
                warning_lines = ['    ' + name for name, _, _ in tag_conflicts]
486
 
                note( gettext("Conflicting tags in bound branch:\n{0}".format(
487
 
                    "\n".join(warning_lines))) )
488
 
 
489
427
    def _select_reporter(self):
490
428
        """Select the CommitReporter to use."""
491
429
        if is_quiet():
498
436
        # A merge with no effect on files
499
437
        if len(self.parents) > 1:
500
438
            return
 
439
        # TODO: we could simplify this by using self.builder.basis_delta.
 
440
 
 
441
        # The initial commit adds a root directory, but this in itself is not
 
442
        # a worthwhile commit.
 
443
        if (self.basis_revid == revision.NULL_REVISION and
 
444
            ((self.builder.new_inventory is not None and
 
445
             len(self.builder.new_inventory) == 1) or
 
446
            len(self.builder._basis_delta) == 1)):
 
447
            raise PointlessCommit()
501
448
        if self.builder.any_changes():
502
449
            return
503
450
        raise PointlessCommit()
548
495
    def _check_out_of_date_tree(self):
549
496
        """Check that the working tree is up to date.
550
497
 
551
 
        :return: old_revision_number, old_revision_id, new_revision_number
552
 
            tuple
 
498
        :return: old_revision_number,new_revision_number tuple
553
499
        """
554
500
        try:
555
501
            first_tree_parent = self.work_tree.get_parent_ids()[0]
558
504
            # this is so that we still consider the master branch
559
505
            # - in a checkout scenario the tree may have no
560
506
            # parents but the branch may do.
561
 
            first_tree_parent = breezy.revision.NULL_REVISION
 
507
            first_tree_parent = bzrlib.revision.NULL_REVISION
562
508
        old_revno, master_last = self.master_branch.last_revision_info()
563
509
        if master_last != first_tree_parent:
564
 
            if master_last != breezy.revision.NULL_REVISION:
 
510
            if master_last != bzrlib.revision.NULL_REVISION:
565
511
                raise errors.OutOfDateTree(self.work_tree)
566
512
        if self.branch.repository.has_revision(first_tree_parent):
567
513
            new_revno = old_revno + 1
568
514
        else:
569
515
            # ghost parents never appear in revision history.
570
516
            new_revno = 1
571
 
        return old_revno, master_last, new_revno
 
517
        return old_revno,new_revno
572
518
 
573
519
    def _process_pre_hooks(self, old_revno, new_revno):
574
520
        """Process any registered pre commit hooks."""
580
526
        # Process the post commit hooks, if any
581
527
        self._set_progress_stage("Running post_commit hooks")
582
528
        # old style commit hooks - should be deprecated ? (obsoleted in
583
 
        # 0.15^H^H^H^H 2.5.0)
584
 
        post_commit = self.config_stack.get('post_commit')
585
 
        if post_commit is not None:
586
 
            hooks = post_commit.split(' ')
 
529
        # 0.15)
 
530
        if self.config.post_commit() is not None:
 
531
            hooks = self.config.post_commit().split(' ')
587
532
            # this would be nicer with twisted.python.reflect.namedAny
588
533
            for hook in hooks:
589
534
                result = eval(hook + '(branch, rev_id)',
590
535
                              {'branch':self.branch,
591
 
                               'breezy':breezy,
 
536
                               'bzrlib':bzrlib,
592
537
                               'rev_id':self.rev_id})
593
538
        # process new style post commit hooks
594
539
        self._process_hooks("post_commit", old_revno, new_revno)
610
555
        if self.parents:
611
556
            old_revid = self.parents[0]
612
557
        else:
613
 
            old_revid = breezy.revision.NULL_REVISION
 
558
            old_revid = bzrlib.revision.NULL_REVISION
614
559
 
615
560
        if hook_name == "pre_commit":
616
561
            future_tree = self.builder.revision_tree()
642
587
        # entries and the order is preserved when doing this.
643
588
        if self.use_record_iter_changes:
644
589
            return
645
 
        self.basis_inv = self.basis_tree.root_inventory
 
590
        self.basis_inv = self.basis_tree.inventory
646
591
        self.parent_invs = [self.basis_inv]
647
592
        for revision in self.parents[1:]:
648
593
            if self.branch.repository.has_revision(revision):
701
646
                # Reset the new path (None) and new versioned flag (False)
702
647
                change = (change[0], (change[1][0], None), change[2],
703
648
                    (change[3][0], False)) + change[4:]
704
 
                new_path = change[1][1]
705
 
                versioned = False
706
649
            elif kind == 'tree-reference':
707
650
                if self.recursive == 'down':
708
651
                    self._commit_nested_tree(change[0], change[1][1])
712
655
                    if new_path is None:
713
656
                        reporter.deleted(old_path)
714
657
                    elif old_path is None:
715
 
                        reporter.snapshot_change(gettext('added'), new_path)
 
658
                        reporter.snapshot_change('added', new_path)
716
659
                    elif old_path != new_path:
717
 
                        reporter.renamed(gettext('renamed'), old_path, new_path)
 
660
                        reporter.renamed('renamed', old_path, new_path)
718
661
                    else:
719
662
                        if (new_path or 
720
663
                            self.work_tree.branch.repository._format.rich_root_data):
721
664
                            # Don't report on changes to '' in non rich root
722
665
                            # repositories.
723
 
                            reporter.snapshot_change(gettext('modified'), new_path)
 
666
                            reporter.snapshot_change('modified', new_path)
724
667
            self._next_progress_entry()
725
668
        # Unversion IDs that were found to be deleted
726
669
        self.deleted_ids = deleted_ids
732
675
        if self.specific_files or self.exclude:
733
676
            specific_files = self.specific_files or []
734
677
            for path, old_ie in self.basis_inv.iter_entries():
735
 
                if self.builder.new_inventory.has_id(old_ie.file_id):
 
678
                if old_ie.file_id in self.builder.new_inventory:
736
679
                    # already added - skip.
737
680
                    continue
738
681
                if (is_inside_any(specific_files, path)
767
710
            deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
768
711
        if deleted_ids:
769
712
            self.any_entries_deleted = True
770
 
            deleted = sorted([(self.basis_tree.id2path(file_id), file_id)
771
 
                for file_id in deleted_ids])
 
713
            deleted = [(self.basis_tree.id2path(file_id), file_id)
 
714
                for file_id in deleted_ids]
 
715
            deleted.sort()
772
716
            # XXX: this is not quite directory-order sorting
773
717
            for path, file_id in deleted:
774
718
                self.builder.record_delete(path, file_id)
808
752
        deleted_paths = {}
809
753
        # XXX: Note that entries may have the wrong kind because the entry does
810
754
        # not reflect the status on disk.
 
755
        work_inv = self.work_tree.inventory
811
756
        # NB: entries will include entries within the excluded ids/paths
812
757
        # because iter_entries_by_dir has no 'exclude' facility today.
813
 
        entries = self.work_tree.iter_entries_by_dir(
 
758
        entries = work_inv.iter_entries_by_dir(
814
759
            specific_file_ids=self.specific_file_ids, yield_parents=True)
815
760
        for path, existing_ie in entries:
816
761
            file_id = existing_ie.file_id
947
892
            self.reporter.renamed(change, old_path, path)
948
893
            self._next_progress_entry()
949
894
        else:
950
 
            if change == gettext('unchanged'):
 
895
            if change == 'unchanged':
951
896
                return
952
897
            self.reporter.snapshot_change(change, path)
953
898
            self._next_progress_entry()
969
914
 
970
915
    def _emit_progress(self):
971
916
        if self.pb_entries_count is not None:
972
 
            text = gettext("{0} [{1}] - Stage").format(self.pb_stage_name,
 
917
            text = "%s [%d] - Stage" % (self.pb_stage_name,
973
918
                self.pb_entries_count)
974
919
        else:
975
 
            text = gettext("%s - Stage") % (self.pb_stage_name, )
 
920
            text = "%s - Stage" % (self.pb_stage_name, )
976
921
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
977
922
 
978
923
    def _set_specific_file_ids(self):