/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 breezy/commit.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-30 22:09:29 UTC
  • mto: This revision was merged to the branch mainline in revision 6641.
  • Revision ID: jelmer@jelmer.uk-20170530220929-qkha5n0v2fgvftk9
Ignore warning.

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
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
17
18
 
18
19
# The newly committed revision is going to have a shape corresponding
19
20
# to that of the working tree.  Files that are not in the
49
50
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
50
51
# the rest of the code; add a deprecation of the old name.
51
52
 
52
 
from bzrlib import (
 
53
from . import (
53
54
    debug,
54
55
    errors,
55
 
    revision,
56
56
    trace,
57
57
    tree,
 
58
    ui,
58
59
    )
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
 
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 . import symbol_versioning
 
75
from .urlutils import unescape_for_display
 
76
from .i18n import gettext
76
77
 
77
78
 
78
79
class NullCommitReporter(object):
114
115
        note(format, *args)
115
116
 
116
117
    def snapshot_change(self, change, path):
117
 
        if path == '' and change in ('added', 'modified'):
 
118
        if path == '' and change in (gettext('added'), gettext('modified')):
118
119
            return
119
120
        self._note("%s %s", change, path)
120
121
 
128
129
                                   "to started.", DeprecationWarning,
129
130
                                   stacklevel=2)
130
131
            location = ''
131
 
        self._note('Committing%s', location)
 
132
        self._note(gettext('Committing%s'), location)
132
133
 
133
134
    def completed(self, revno, rev_id):
134
 
        self._note('Committed revision %d.', revno)
 
135
        self._note(gettext('Committed revision %d.'), revno)
135
136
        # self._note goes to the console too; so while we want to log the
136
137
        # rev_id, we can't trivially only log it. (See bug 526425). Long
137
138
        # term we should rearrange the reporting structure, but for now
140
141
        mutter('Committed revid %s as revno %d.', rev_id, revno)
141
142
 
142
143
    def deleted(self, path):
143
 
        self._note('deleted %s', path)
 
144
        self._note(gettext('deleted %s'), path)
144
145
 
145
146
    def missing(self, path):
146
 
        self._note('missing %s', path)
 
147
        self._note(gettext('missing %s'), path)
147
148
 
148
149
    def renamed(self, change, old_path, new_path):
149
150
        self._note('%s %s => %s', change, old_path, new_path)
166
167
    """
167
168
    def __init__(self,
168
169
                 reporter=None,
169
 
                 config=None):
 
170
                 config_stack=None):
170
171
        """Create a Commit object.
171
172
 
172
173
        :param reporter: the default reporter to use or None to decide later
173
174
        """
174
175
        self.reporter = reporter
175
 
        self.config = config
 
176
        self.config_stack = config_stack
 
177
 
 
178
    @staticmethod
 
179
    def update_revprops(revprops, branch, authors=None, author=None,
 
180
                        local=False, possible_master_transports=None):
 
181
        if revprops is None:
 
182
            revprops = {}
 
183
        if possible_master_transports is None:
 
184
            possible_master_transports = []
 
185
        if not 'branch-nick' in revprops:
 
186
            revprops['branch-nick'] = branch._get_nick(
 
187
                local,
 
188
                possible_master_transports)
 
189
        if authors is not None:
 
190
            if author is not None:
 
191
                raise AssertionError('Specifying both author and authors '
 
192
                        'is not allowed. Specify just authors instead')
 
193
            if 'author' in revprops or 'authors' in revprops:
 
194
                # XXX: maybe we should just accept one of them?
 
195
                raise AssertionError('author property given twice')
 
196
            if authors:
 
197
                for individual in authors:
 
198
                    if '\n' in individual:
 
199
                        raise AssertionError('\\n is not a valid character '
 
200
                                'in an author identity')
 
201
                revprops['authors'] = '\n'.join(authors)
 
202
        if author is not None:
 
203
            symbol_versioning.warn('The parameter author was deprecated'
 
204
                   ' in version 1.13. Use authors instead',
 
205
                   DeprecationWarning)
 
206
            if 'author' in revprops or 'authors' in revprops:
 
207
                # XXX: maybe we should just accept one of them?
 
208
                raise AssertionError('author property given twice')
 
209
            if '\n' in author:
 
210
                raise AssertionError('\\n is not a valid character '
 
211
                        'in an author identity')
 
212
            revprops['authors'] = author
 
213
        return revprops
176
214
 
177
215
    def commit(self,
178
216
               message=None,
192
230
               message_callback=None,
193
231
               recursive='down',
194
232
               exclude=None,
195
 
               possible_master_transports=None):
 
233
               possible_master_transports=None,
 
234
               lossy=False):
196
235
        """Commit working copy as a new revision.
197
236
 
198
237
        :param message: the commit message (it or message_callback is required)
225
264
        :param exclude: None or a list of relative paths to exclude from the
226
265
            commit. Pending changes to excluded files will be ignored by the
227
266
            commit.
 
267
        :param lossy: When committing to a foreign VCS, ignore any
 
268
            data that can not be natively represented.
228
269
        """
229
270
        operation = OperationWithCleanups(self._commit)
230
271
        self.revprops = revprops or {}
231
272
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
232
 
        self.config = config or self.config
 
273
        self.config_stack = config or self.config_stack
233
274
        return operation.run(
234
275
               message=message,
235
276
               timestamp=timestamp,
246
287
               message_callback=message_callback,
247
288
               recursive=recursive,
248
289
               exclude=exclude,
249
 
               possible_master_transports=possible_master_transports)
 
290
               possible_master_transports=possible_master_transports,
 
291
               lossy=lossy)
250
292
 
251
293
    def _commit(self, operation, message, timestamp, timezone, committer,
252
294
            specific_files, rev_id, allow_pointless, strict, verbose,
253
295
            working_tree, local, reporter, message_callback, recursive,
254
 
            exclude, possible_master_transports):
 
296
            exclude, possible_master_transports, lossy):
255
297
        mutter('preparing to commit')
256
298
 
257
299
        if working_tree is None:
289
331
                minimum_path_selection(specific_files))
290
332
        else:
291
333
            self.specific_files = None
292
 
            
 
334
 
293
335
        self.allow_pointless = allow_pointless
294
336
        self.message_callback = message_callback
295
337
        self.timestamp = timestamp
309
351
            not self.branch.repository._format.supports_tree_reference and
310
352
            (self.branch.repository._format.fast_deltas or
311
353
             len(self.parents) < 2))
312
 
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
354
        self.pb = ui.ui_factory.nested_progress_bar()
313
355
        operation.add_cleanup(self.pb.finished)
314
356
        self.basis_revid = self.work_tree.last_revision()
315
357
        self.basis_tree = self.work_tree.basis_tree()
323
365
        self._check_bound_branch(operation, possible_master_transports)
324
366
 
325
367
        # Check that the working tree is up to date
326
 
        old_revno, new_revno = self._check_out_of_date_tree()
 
368
        old_revno, old_revid, new_revno = self._check_out_of_date_tree()
327
369
 
328
370
        # Complete configuration setup
329
371
        if reporter is not None:
330
372
            self.reporter = reporter
331
373
        elif self.reporter is None:
332
374
            self.reporter = self._select_reporter()
333
 
        if self.config is None:
334
 
            self.config = self.branch.get_config()
 
375
        if self.config_stack is None:
 
376
            self.config_stack = self.work_tree.get_config_stack()
335
377
 
336
378
        self._set_specific_file_ids()
337
379
 
343
385
        self.pb_stage_count = 0
344
386
        self.pb_stage_total = 5
345
387
        if self.bound_branch:
346
 
            self.pb_stage_total += 1
 
388
            # 2 extra stages: "Uploading data to master branch" and "Merging
 
389
            # tags to master branch"
 
390
            self.pb_stage_total += 2
347
391
        self.pb.show_pct = False
348
392
        self.pb.show_spinner = False
349
393
        self.pb.show_eta = False
361
405
 
362
406
        # Collect the changes
363
407
        self._set_progress_stage("Collecting changes", counter=True)
 
408
        self._lossy = lossy
364
409
        self.builder = self.branch.get_commit_builder(self.parents,
365
 
            self.config, timestamp, timezone, committer, self.revprops, rev_id)
 
410
            self.config_stack, timestamp, timezone, committer, self.revprops,
 
411
            rev_id, lossy=lossy)
 
412
        if not self.builder.supports_record_entry_contents and self.exclude:
 
413
            self.builder.abort()
 
414
            raise errors.ExcludesUnsupported(self.branch.repository)
 
415
 
 
416
        if self.builder.updates_branch and self.bound_branch:
 
417
            self.builder.abort()
 
418
            raise AssertionError(
 
419
                "bound branches not supported for commit builders "
 
420
                "that update the branch")
366
421
 
367
422
        try:
368
423
            self.builder.will_record_deletes()
392
447
            # Add revision data to the local branch
393
448
            self.rev_id = self.builder.commit(self.message)
394
449
 
395
 
        except Exception, e:
 
450
        except Exception as e:
396
451
            mutter("aborting commit write group because of exception:")
397
452
            trace.log_exception_quietly()
398
 
            note("aborting commit write group: %r" % (e,))
399
453
            self.builder.abort()
400
454
            raise
401
455
 
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)
 
456
        self._update_branches(old_revno, old_revid, new_revno)
415
457
 
416
458
        # Make the working tree be up to date with the branch. This
417
459
        # includes automatic changes scheduled to be made to the tree, such
424
466
        self._process_post_hooks(old_revno, new_revno)
425
467
        return self.rev_id
426
468
 
 
469
    def _update_branches(self, old_revno, old_revid, new_revno):
 
470
        """Update the master and local branch to the new revision.
 
471
 
 
472
        This will try to make sure that the master branch is updated
 
473
        before the local branch.
 
474
 
 
475
        :param old_revno: Revision number of master branch before the
 
476
            commit
 
477
        :param old_revid: Tip of master branch before the commit
 
478
        :param new_revno: Revision number of the new commit
 
479
        """
 
480
        if not self.builder.updates_branch:
 
481
            self._process_pre_hooks(old_revno, new_revno)
 
482
 
 
483
            # Upload revision data to the master.
 
484
            # this will propagate merged revisions too if needed.
 
485
            if self.bound_branch:
 
486
                self._set_progress_stage("Uploading data to master branch")
 
487
                # 'commit' to the master first so a timeout here causes the
 
488
                # local branch to be out of date
 
489
                (new_revno, self.rev_id) = self.master_branch.import_last_revision_info_and_tags(
 
490
                    self.branch, new_revno, self.rev_id, lossy=self._lossy)
 
491
                if self._lossy:
 
492
                    self.branch.fetch(self.master_branch, self.rev_id)
 
493
 
 
494
            # and now do the commit locally.
 
495
            self.branch.set_last_revision_info(new_revno, self.rev_id)
 
496
        else:
 
497
            try:
 
498
                self._process_pre_hooks(old_revno, new_revno)
 
499
            except:
 
500
                # The commit builder will already have updated the branch,
 
501
                # revert it.
 
502
                self.branch.set_last_revision_info(old_revno, old_revid)
 
503
                raise
 
504
 
 
505
        # Merge local tags to remote
 
506
        if self.bound_branch:
 
507
            self._set_progress_stage("Merging tags to master branch")
 
508
            tag_updates, tag_conflicts = self.branch.tags.merge_to(
 
509
                self.master_branch.tags)
 
510
            if tag_conflicts:
 
511
                warning_lines = ['    ' + name for name, _, _ in tag_conflicts]
 
512
                note( gettext("Conflicting tags in bound branch:\n{0}".format(
 
513
                    "\n".join(warning_lines))) )
 
514
 
427
515
    def _select_reporter(self):
428
516
        """Select the CommitReporter to use."""
429
517
        if is_quiet():
436
524
        # A merge with no effect on files
437
525
        if len(self.parents) > 1:
438
526
            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()
448
527
        if self.builder.any_changes():
449
528
            return
450
529
        raise PointlessCommit()
495
574
    def _check_out_of_date_tree(self):
496
575
        """Check that the working tree is up to date.
497
576
 
498
 
        :return: old_revision_number,new_revision_number tuple
 
577
        :return: old_revision_number, old_revision_id, new_revision_number
 
578
            tuple
499
579
        """
500
580
        try:
501
581
            first_tree_parent = self.work_tree.get_parent_ids()[0]
504
584
            # this is so that we still consider the master branch
505
585
            # - in a checkout scenario the tree may have no
506
586
            # parents but the branch may do.
507
 
            first_tree_parent = bzrlib.revision.NULL_REVISION
 
587
            first_tree_parent = breezy.revision.NULL_REVISION
508
588
        old_revno, master_last = self.master_branch.last_revision_info()
509
589
        if master_last != first_tree_parent:
510
 
            if master_last != bzrlib.revision.NULL_REVISION:
 
590
            if master_last != breezy.revision.NULL_REVISION:
511
591
                raise errors.OutOfDateTree(self.work_tree)
512
592
        if self.branch.repository.has_revision(first_tree_parent):
513
593
            new_revno = old_revno + 1
514
594
        else:
515
595
            # ghost parents never appear in revision history.
516
596
            new_revno = 1
517
 
        return old_revno,new_revno
 
597
        return old_revno, master_last, new_revno
518
598
 
519
599
    def _process_pre_hooks(self, old_revno, new_revno):
520
600
        """Process any registered pre commit hooks."""
526
606
        # Process the post commit hooks, if any
527
607
        self._set_progress_stage("Running post_commit hooks")
528
608
        # old style commit hooks - should be deprecated ? (obsoleted in
529
 
        # 0.15)
530
 
        if self.config.post_commit() is not None:
531
 
            hooks = self.config.post_commit().split(' ')
 
609
        # 0.15^H^H^H^H 2.5.0)
 
610
        post_commit = self.config_stack.get('post_commit')
 
611
        if post_commit is not None:
 
612
            hooks = post_commit.split(' ')
532
613
            # this would be nicer with twisted.python.reflect.namedAny
533
614
            for hook in hooks:
534
615
                result = eval(hook + '(branch, rev_id)',
535
616
                              {'branch':self.branch,
536
 
                               'bzrlib':bzrlib,
 
617
                               'breezy':breezy,
537
618
                               'rev_id':self.rev_id})
538
619
        # process new style post commit hooks
539
620
        self._process_hooks("post_commit", old_revno, new_revno)
555
636
        if self.parents:
556
637
            old_revid = self.parents[0]
557
638
        else:
558
 
            old_revid = bzrlib.revision.NULL_REVISION
 
639
            old_revid = breezy.revision.NULL_REVISION
559
640
 
560
641
        if hook_name == "pre_commit":
561
642
            future_tree = self.builder.revision_tree()
587
668
        # entries and the order is preserved when doing this.
588
669
        if self.use_record_iter_changes:
589
670
            return
590
 
        self.basis_inv = self.basis_tree.inventory
 
671
        self.basis_inv = self.basis_tree.root_inventory
591
672
        self.parent_invs = [self.basis_inv]
592
673
        for revision in self.parents[1:]:
593
674
            if self.branch.repository.has_revision(revision):
646
727
                # Reset the new path (None) and new versioned flag (False)
647
728
                change = (change[0], (change[1][0], None), change[2],
648
729
                    (change[3][0], False)) + change[4:]
 
730
                new_path = change[1][1]
 
731
                versioned = False
649
732
            elif kind == 'tree-reference':
650
733
                if self.recursive == 'down':
651
734
                    self._commit_nested_tree(change[0], change[1][1])
655
738
                    if new_path is None:
656
739
                        reporter.deleted(old_path)
657
740
                    elif old_path is None:
658
 
                        reporter.snapshot_change('added', new_path)
 
741
                        reporter.snapshot_change(gettext('added'), new_path)
659
742
                    elif old_path != new_path:
660
 
                        reporter.renamed('renamed', old_path, new_path)
 
743
                        reporter.renamed(gettext('renamed'), old_path, new_path)
661
744
                    else:
662
745
                        if (new_path or 
663
746
                            self.work_tree.branch.repository._format.rich_root_data):
664
747
                            # Don't report on changes to '' in non rich root
665
748
                            # repositories.
666
 
                            reporter.snapshot_change('modified', new_path)
 
749
                            reporter.snapshot_change(gettext('modified'), new_path)
667
750
            self._next_progress_entry()
668
751
        # Unversion IDs that were found to be deleted
669
752
        self.deleted_ids = deleted_ids
675
758
        if self.specific_files or self.exclude:
676
759
            specific_files = self.specific_files or []
677
760
            for path, old_ie in self.basis_inv.iter_entries():
678
 
                if old_ie.file_id in self.builder.new_inventory:
 
761
                if self.builder.new_inventory.has_id(old_ie.file_id):
679
762
                    # already added - skip.
680
763
                    continue
681
764
                if (is_inside_any(specific_files, path)
710
793
            deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
711
794
        if deleted_ids:
712
795
            self.any_entries_deleted = True
713
 
            deleted = [(self.basis_tree.id2path(file_id), file_id)
714
 
                for file_id in deleted_ids]
715
 
            deleted.sort()
 
796
            deleted = sorted([(self.basis_tree.id2path(file_id), file_id)
 
797
                for file_id in deleted_ids])
716
798
            # XXX: this is not quite directory-order sorting
717
799
            for path, file_id in deleted:
718
800
                self.builder.record_delete(path, file_id)
752
834
        deleted_paths = {}
753
835
        # XXX: Note that entries may have the wrong kind because the entry does
754
836
        # not reflect the status on disk.
755
 
        work_inv = self.work_tree.inventory
756
837
        # NB: entries will include entries within the excluded ids/paths
757
838
        # because iter_entries_by_dir has no 'exclude' facility today.
758
 
        entries = work_inv.iter_entries_by_dir(
 
839
        entries = self.work_tree.iter_entries_by_dir(
759
840
            specific_file_ids=self.specific_file_ids, yield_parents=True)
760
841
        for path, existing_ie in entries:
761
842
            file_id = existing_ie.file_id
892
973
            self.reporter.renamed(change, old_path, path)
893
974
            self._next_progress_entry()
894
975
        else:
895
 
            if change == 'unchanged':
 
976
            if change == gettext('unchanged'):
896
977
                return
897
978
            self.reporter.snapshot_change(change, path)
898
979
            self._next_progress_entry()
914
995
 
915
996
    def _emit_progress(self):
916
997
        if self.pb_entries_count is not None:
917
 
            text = "%s [%d] - Stage" % (self.pb_stage_name,
 
998
            text = gettext("{0} [{1}] - Stage").format(self.pb_stage_name,
918
999
                self.pb_entries_count)
919
1000
        else:
920
 
            text = "%s - Stage" % (self.pb_stage_name, )
 
1001
            text = gettext("%s - Stage") % (self.pb_stage_name, )
921
1002
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
922
1003
 
923
1004
    def _set_specific_file_ids(self):