/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: 2010-04-28 10:33:44 UTC
  • mfrom: (5171.2.3 401599-strict-warnings)
  • mto: This revision was merged to the branch mainline in revision 5191.
  • Revision ID: v.ladeuil+lp@free.fr-20100428103344-e32qf3cn1avdd2cb
Don't mention --no-strict when we just issue the warning about unclean trees

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
 
 
52
import os
 
53
import re
 
54
import sys
 
55
import time
 
56
 
 
57
from cStringIO import StringIO
 
58
 
53
59
from bzrlib import (
54
60
    debug,
55
61
    errors,
 
62
    revision,
56
63
    trace,
57
64
    tree,
58
 
    ui,
 
65
    xml_serializer,
59
66
    )
60
67
from bzrlib.branch import Branch
61
68
from bzrlib.cleanup import OperationWithCleanups
65
72
                           StrictCommitFailed
66
73
                           )
67
74
from bzrlib.osutils import (get_user_encoding,
68
 
                            is_inside_any,
 
75
                            kind_marker, isdir,isfile, is_inside_any,
 
76
                            is_inside_or_parent_of_any,
69
77
                            minimum_path_selection,
 
78
                            quotefn, sha_file, split_lines,
70
79
                            splitpath,
71
80
                            )
72
 
from bzrlib.trace import mutter, note, is_quiet
 
81
from bzrlib.testament import Testament
 
82
from bzrlib.trace import mutter, note, warning, is_quiet
73
83
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
74
84
from bzrlib import symbol_versioning
 
85
from bzrlib.symbol_versioning import (deprecated_passed,
 
86
        deprecated_function,
 
87
        DEPRECATED_PARAMETER)
 
88
from bzrlib.workingtree import WorkingTree
75
89
from bzrlib.urlutils import unescape_for_display
76
 
from bzrlib.i18n import gettext
 
90
import bzrlib.ui
 
91
 
77
92
 
78
93
class NullCommitReporter(object):
79
94
    """I report on progress of a commit."""
114
129
        note(format, *args)
115
130
 
116
131
    def snapshot_change(self, change, path):
117
 
        if path == '' and change in (gettext('added'), gettext('modified')):
 
132
        if path == '' and change in ('added', 'modified'):
118
133
            return
119
134
        self._note("%s %s", change, path)
120
135
 
128
143
                                   "to started.", DeprecationWarning,
129
144
                                   stacklevel=2)
130
145
            location = ''
131
 
        self._note(gettext('Committing%s'), location)
 
146
        self._note('Committing%s', location)
132
147
 
133
148
    def completed(self, revno, rev_id):
134
 
        self._note(gettext('Committed revision %d.'), revno)
 
149
        self._note('Committed revision %d.', revno)
135
150
        # self._note goes to the console too; so while we want to log the
136
151
        # rev_id, we can't trivially only log it. (See bug 526425). Long
137
152
        # term we should rearrange the reporting structure, but for now
140
155
        mutter('Committed revid %s as revno %d.', rev_id, revno)
141
156
 
142
157
    def deleted(self, path):
143
 
        self._note(gettext('deleted %s'), path)
 
158
        self._note('deleted %s', path)
144
159
 
145
160
    def missing(self, path):
146
 
        self._note(gettext('missing %s'), path)
 
161
        self._note('missing %s', path)
147
162
 
148
163
    def renamed(self, change, old_path, new_path):
149
164
        self._note('%s %s => %s', change, old_path, new_path)
166
181
    """
167
182
    def __init__(self,
168
183
                 reporter=None,
169
 
                 config_stack=None):
 
184
                 config=None):
170
185
        """Create a Commit object.
171
186
 
172
187
        :param reporter: the default reporter to use or None to decide later
173
188
        """
174
189
        self.reporter = reporter
175
 
        self.config_stack = config_stack
176
 
 
177
 
    @staticmethod
178
 
    def update_revprops(revprops, branch, authors=None, author=None,
179
 
                        local=False, possible_master_transports=None):
180
 
        if revprops is None:
181
 
            revprops = {}
182
 
        if possible_master_transports is None:
183
 
            possible_master_transports = []
184
 
        if not 'branch-nick' in revprops:
185
 
            revprops['branch-nick'] = branch._get_nick(
186
 
                local,
187
 
                possible_master_transports)
188
 
        if authors is not None:
189
 
            if author is not None:
190
 
                raise AssertionError('Specifying both author and authors '
191
 
                        'is not allowed. Specify just authors instead')
192
 
            if 'author' in revprops or 'authors' in revprops:
193
 
                # XXX: maybe we should just accept one of them?
194
 
                raise AssertionError('author property given twice')
195
 
            if authors:
196
 
                for individual in authors:
197
 
                    if '\n' in individual:
198
 
                        raise AssertionError('\\n is not a valid character '
199
 
                                'in an author identity')
200
 
                revprops['authors'] = '\n'.join(authors)
201
 
        if author is not None:
202
 
            symbol_versioning.warn('The parameter author was deprecated'
203
 
                   ' in version 1.13. Use authors instead',
204
 
                   DeprecationWarning)
205
 
            if 'author' in revprops or 'authors' in revprops:
206
 
                # XXX: maybe we should just accept one of them?
207
 
                raise AssertionError('author property given twice')
208
 
            if '\n' in author:
209
 
                raise AssertionError('\\n is not a valid character '
210
 
                        'in an author identity')
211
 
            revprops['authors'] = author
212
 
        return revprops
 
190
        self.config = config
213
191
 
214
192
    def commit(self,
215
193
               message=None,
229
207
               message_callback=None,
230
208
               recursive='down',
231
209
               exclude=None,
232
 
               possible_master_transports=None,
233
 
               lossy=False):
 
210
               possible_master_transports=None):
234
211
        """Commit working copy as a new revision.
235
212
 
236
213
        :param message: the commit message (it or message_callback is required)
263
240
        :param exclude: None or a list of relative paths to exclude from the
264
241
            commit. Pending changes to excluded files will be ignored by the
265
242
            commit.
266
 
        :param lossy: When committing to a foreign VCS, ignore any
267
 
            data that can not be natively represented.
268
243
        """
269
244
        operation = OperationWithCleanups(self._commit)
270
245
        self.revprops = revprops or {}
271
246
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
272
 
        self.config_stack = config or self.config_stack
 
247
        self.config = config or self.config
273
248
        return operation.run(
274
249
               message=message,
275
250
               timestamp=timestamp,
286
261
               message_callback=message_callback,
287
262
               recursive=recursive,
288
263
               exclude=exclude,
289
 
               possible_master_transports=possible_master_transports,
290
 
               lossy=lossy)
 
264
               possible_master_transports=possible_master_transports)
291
265
 
292
266
    def _commit(self, operation, message, timestamp, timezone, committer,
293
267
            specific_files, rev_id, allow_pointless, strict, verbose,
294
268
            working_tree, local, reporter, message_callback, recursive,
295
 
            exclude, possible_master_transports, lossy):
 
269
            exclude, possible_master_transports):
296
270
        mutter('preparing to commit')
297
271
 
298
272
        if working_tree is None:
330
304
                minimum_path_selection(specific_files))
331
305
        else:
332
306
            self.specific_files = None
333
 
 
 
307
            
334
308
        self.allow_pointless = allow_pointless
335
309
        self.message_callback = message_callback
336
310
        self.timestamp = timestamp
350
324
            not self.branch.repository._format.supports_tree_reference and
351
325
            (self.branch.repository._format.fast_deltas or
352
326
             len(self.parents) < 2))
353
 
        self.pb = ui.ui_factory.nested_progress_bar()
 
327
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
354
328
        operation.add_cleanup(self.pb.finished)
355
329
        self.basis_revid = self.work_tree.last_revision()
356
330
        self.basis_tree = self.work_tree.basis_tree()
364
338
        self._check_bound_branch(operation, possible_master_transports)
365
339
 
366
340
        # Check that the working tree is up to date
367
 
        old_revno, old_revid, new_revno = self._check_out_of_date_tree()
 
341
        old_revno, new_revno = self._check_out_of_date_tree()
368
342
 
369
343
        # Complete configuration setup
370
344
        if reporter is not None:
371
345
            self.reporter = reporter
372
346
        elif self.reporter is None:
373
347
            self.reporter = self._select_reporter()
374
 
        if self.config_stack is None:
375
 
            self.config_stack = self.branch.get_config_stack()
 
348
        if self.config is None:
 
349
            self.config = self.branch.get_config()
376
350
 
377
351
        self._set_specific_file_ids()
378
352
 
384
358
        self.pb_stage_count = 0
385
359
        self.pb_stage_total = 5
386
360
        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
 
361
            self.pb_stage_total += 1
390
362
        self.pb.show_pct = False
391
363
        self.pb.show_spinner = False
392
364
        self.pb.show_eta = False
404
376
 
405
377
        # Collect the changes
406
378
        self._set_progress_stage("Collecting changes", counter=True)
407
 
        self._lossy = lossy
408
379
        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")
 
380
            self.config, timestamp, timezone, committer, self.revprops, rev_id)
420
381
 
421
382
        try:
422
383
            self.builder.will_record_deletes()
449
410
        except Exception, e:
450
411
            mutter("aborting commit write group because of exception:")
451
412
            trace.log_exception_quietly()
 
413
            note("aborting commit write group: %r" % (e,))
452
414
            self.builder.abort()
453
415
            raise
454
416
 
455
 
        self._update_branches(old_revno, old_revid, new_revno)
 
417
        self._process_pre_hooks(old_revno, new_revno)
 
418
 
 
419
        # Upload revision data to the master.
 
420
        # this will propagate merged revisions too if needed.
 
421
        if self.bound_branch:
 
422
            self._set_progress_stage("Uploading data to master branch")
 
423
            # 'commit' to the master first so a timeout here causes the
 
424
            # local branch to be out of date
 
425
            self.master_branch.import_last_revision_info(
 
426
                self.branch.repository, new_revno, self.rev_id)
 
427
 
 
428
        # and now do the commit locally.
 
429
        self.branch.set_last_revision_info(new_revno, self.rev_id)
456
430
 
457
431
        # Make the working tree be up to date with the branch. This
458
432
        # includes automatic changes scheduled to be made to the tree, such
465
439
        self._process_post_hooks(old_revno, new_revno)
466
440
        return self.rev_id
467
441
 
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
442
    def _select_reporter(self):
515
443
        """Select the CommitReporter to use."""
516
444
        if is_quiet():
523
451
        # A merge with no effect on files
524
452
        if len(self.parents) > 1:
525
453
            return
 
454
        # TODO: we could simplify this by using self.builder.basis_delta.
 
455
 
 
456
        # The initial commit adds a root directory, but this in itself is not
 
457
        # a worthwhile commit.
 
458
        if (self.basis_revid == revision.NULL_REVISION and
 
459
            ((self.builder.new_inventory is not None and
 
460
             len(self.builder.new_inventory) == 1) or
 
461
            len(self.builder._basis_delta) == 1)):
 
462
            raise PointlessCommit()
526
463
        if self.builder.any_changes():
527
464
            return
528
465
        raise PointlessCommit()
573
510
    def _check_out_of_date_tree(self):
574
511
        """Check that the working tree is up to date.
575
512
 
576
 
        :return: old_revision_number, old_revision_id, new_revision_number
577
 
            tuple
 
513
        :return: old_revision_number,new_revision_number tuple
578
514
        """
579
515
        try:
580
516
            first_tree_parent = self.work_tree.get_parent_ids()[0]
593
529
        else:
594
530
            # ghost parents never appear in revision history.
595
531
            new_revno = 1
596
 
        return old_revno, master_last, new_revno
 
532
        return old_revno,new_revno
597
533
 
598
534
    def _process_pre_hooks(self, old_revno, new_revno):
599
535
        """Process any registered pre commit hooks."""
605
541
        # Process the post commit hooks, if any
606
542
        self._set_progress_stage("Running post_commit hooks")
607
543
        # 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(' ')
 
544
        # 0.15)
 
545
        if self.config.post_commit() is not None:
 
546
            hooks = self.config.post_commit().split(' ')
612
547
            # this would be nicer with twisted.python.reflect.namedAny
613
548
            for hook in hooks:
614
549
                result = eval(hook + '(branch, rev_id)',
726
661
                # Reset the new path (None) and new versioned flag (False)
727
662
                change = (change[0], (change[1][0], None), change[2],
728
663
                    (change[3][0], False)) + change[4:]
729
 
                new_path = change[1][1]
730
 
                versioned = False
731
664
            elif kind == 'tree-reference':
732
665
                if self.recursive == 'down':
733
666
                    self._commit_nested_tree(change[0], change[1][1])
737
670
                    if new_path is None:
738
671
                        reporter.deleted(old_path)
739
672
                    elif old_path is None:
740
 
                        reporter.snapshot_change(gettext('added'), new_path)
 
673
                        reporter.snapshot_change('added', new_path)
741
674
                    elif old_path != new_path:
742
 
                        reporter.renamed(gettext('renamed'), old_path, new_path)
 
675
                        reporter.renamed('renamed', old_path, new_path)
743
676
                    else:
744
677
                        if (new_path or 
745
678
                            self.work_tree.branch.repository._format.rich_root_data):
746
679
                            # Don't report on changes to '' in non rich root
747
680
                            # repositories.
748
 
                            reporter.snapshot_change(gettext('modified'), new_path)
 
681
                            reporter.snapshot_change('modified', new_path)
749
682
            self._next_progress_entry()
750
683
        # Unversion IDs that were found to be deleted
751
684
        self.deleted_ids = deleted_ids
757
690
        if self.specific_files or self.exclude:
758
691
            specific_files = self.specific_files or []
759
692
            for path, old_ie in self.basis_inv.iter_entries():
760
 
                if self.builder.new_inventory.has_id(old_ie.file_id):
 
693
                if old_ie.file_id in self.builder.new_inventory:
761
694
                    # already added - skip.
762
695
                    continue
763
696
                if (is_inside_any(specific_files, path)
974
907
            self.reporter.renamed(change, old_path, path)
975
908
            self._next_progress_entry()
976
909
        else:
977
 
            if change == gettext('unchanged'):
 
910
            if change == 'unchanged':
978
911
                return
979
912
            self.reporter.snapshot_change(change, path)
980
913
            self._next_progress_entry()
996
929
 
997
930
    def _emit_progress(self):
998
931
        if self.pb_entries_count is not None:
999
 
            text = gettext("{0} [{1}] - Stage").format(self.pb_stage_name,
 
932
            text = "%s [%d] - Stage" % (self.pb_stage_name,
1000
933
                self.pb_entries_count)
1001
934
        else:
1002
 
            text = gettext("%s - Stage") % (self.pb_stage_name, )
 
935
            text = "%s - Stage" % (self.pb_stage_name, )
1003
936
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
1004
937
 
1005
938
    def _set_specific_file_ids(self):