/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: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
51
51
# the rest of the code; add a deprecation of the old name.
52
52
 
53
 
from . import (
 
53
from bzrlib import (
54
54
    debug,
55
55
    errors,
56
56
    trace,
57
57
    tree,
58
58
    ui,
59
59
    )
60
 
from .branch import Branch
61
 
from .cleanup import OperationWithCleanups
62
 
import breezy.config
63
 
from .errors import (BzrError,
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 .urlutils import unescape_for_display
74
 
from .i18n import gettext
75
 
 
76
 
 
77
 
class PointlessCommit(BzrError):
78
 
 
79
 
    _fmt = "No changes to commit"
80
 
 
81
 
 
82
 
class CannotCommitSelectedFileMerge(BzrError):
83
 
 
84
 
    _fmt = 'Selected-file commit of merges is not supported yet:'\
85
 
        ' files %(files_str)s'
86
 
 
87
 
    def __init__(self, files):
88
 
        files_str = ', '.join(files)
89
 
        BzrError.__init__(self, files=files, files_str=files_str)
90
 
 
91
 
 
92
 
def filter_excluded(iter_changes, exclude):
93
 
    """Filter exclude filenames.
94
 
 
95
 
    :param iter_changes: iter_changes function
96
 
    :param exclude: List of paths to exclude
97
 
    :return: iter_changes function
98
 
    """
99
 
    for change in iter_changes:
100
 
        old_path = change[1][0]
101
 
        new_path = change[1][1]
102
 
 
103
 
        new_excluded = (new_path is not None and
104
 
            is_inside_any(exclude, new_path))
105
 
 
106
 
        old_excluded = (old_path is not None and
107
 
            is_inside_any(exclude, old_path))
108
 
 
109
 
        if old_excluded and new_excluded:
110
 
            continue
111
 
 
112
 
        if old_excluded or new_excluded:
113
 
            # TODO(jelmer): Perhaps raise an error here instead?
114
 
            continue
115
 
 
116
 
        yield change
117
 
 
 
60
from bzrlib.branch import Branch
 
61
from bzrlib.cleanup import OperationWithCleanups
 
62
import bzrlib.config
 
63
from bzrlib.errors import (BzrError, PointlessCommit,
 
64
                           ConflictsInTree,
 
65
                           StrictCommitFailed
 
66
                           )
 
67
from bzrlib.osutils import (get_user_encoding,
 
68
                            is_inside_any,
 
69
                            minimum_path_selection,
 
70
                            splitpath,
 
71
                            )
 
72
from bzrlib.trace import mutter, note, is_quiet
 
73
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
 
74
from bzrlib import symbol_versioning
 
75
from bzrlib.urlutils import unescape_for_display
 
76
from bzrlib.i18n import gettext
118
77
 
119
78
class NullCommitReporter(object):
120
79
    """I report on progress of a commit."""
121
80
 
122
 
    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)
123
86
        pass
124
87
 
125
88
    def snapshot_change(self, change, path):
155
118
            return
156
119
        self._note("%s %s", change, path)
157
120
 
158
 
    def started(self, revno, rev_id, location):
159
 
        self._note(
160
 
            gettext('Committing to: %s'),
161
 
            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(gettext('Committing%s'), location)
162
132
 
163
133
    def completed(self, revno, rev_id):
164
134
        self._note(gettext('Committed revision %d.'), revno)
205
175
        self.config_stack = config_stack
206
176
 
207
177
    @staticmethod
208
 
    def update_revprops(revprops, branch, authors=None,
 
178
    def update_revprops(revprops, branch, authors=None, author=None,
209
179
                        local=False, possible_master_transports=None):
210
180
        if revprops is None:
211
181
            revprops = {}
212
182
        if possible_master_transports is None:
213
183
            possible_master_transports = []
214
 
        if (not 'branch-nick' in revprops and
215
 
                branch.repository._format.supports_storing_branch_nick):
 
184
        if not 'branch-nick' in revprops:
216
185
            revprops['branch-nick'] = branch._get_nick(
217
186
                local,
218
187
                possible_master_transports)
219
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')
220
192
            if 'author' in revprops or 'authors' in revprops:
221
193
                # XXX: maybe we should just accept one of them?
222
194
                raise AssertionError('author property given twice')
226
198
                        raise AssertionError('\\n is not a valid character '
227
199
                                'in an author identity')
228
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
229
212
        return revprops
230
213
 
231
214
    def commit(self,
322
305
                    raise errors.RootNotRich()
323
306
        if message_callback is None:
324
307
            if message is not None:
325
 
                if isinstance(message, bytes):
 
308
                if isinstance(message, str):
326
309
                    message = message.decode(get_user_encoding())
327
310
                message_callback = lambda x: message
328
311
            else:
359
342
        self.work_tree.lock_write()
360
343
        operation.add_cleanup(self.work_tree.unlock)
361
344
        self.parents = self.work_tree.get_parent_ids()
 
345
        # We can use record_iter_changes IFF iter_changes is compatible with
 
346
        # the command line parameters, and the repository has fast delta
 
347
        # generation. See bug 347649.
 
348
        self.use_record_iter_changes = (
 
349
            not self.exclude and 
 
350
            not self.branch.repository._format.supports_tree_reference and
 
351
            (self.branch.repository._format.fast_deltas or
 
352
             len(self.parents) < 2))
362
353
        self.pb = ui.ui_factory.nested_progress_bar()
363
354
        operation.add_cleanup(self.pb.finished)
364
355
        self.basis_revid = self.work_tree.last_revision()
383
374
        if self.config_stack is None:
384
375
            self.config_stack = self.work_tree.get_config_stack()
385
376
 
 
377
        self._set_specific_file_ids()
 
378
 
386
379
        # Setup the progress bar. As the number of files that need to be
387
380
        # committed in unknown, progress is reported as stages.
388
381
        # We keep track of entries separately though and include that
400
393
        self.pb.show_count = True
401
394
        self.pb.show_bar = True
402
395
 
 
396
        self._gather_parents()
403
397
        # After a merge, a selected file commit is not supported.
404
398
        # See 'bzr help merge' for an explanation as to why.
405
399
        if len(self.parents) > 1 and self.specific_files is not None:
406
 
            raise CannotCommitSelectedFileMerge(self.specific_files)
 
400
            raise errors.CannotCommitSelectedFileMerge(self.specific_files)
407
401
        # Excludes are a form of selected file commit.
408
402
        if len(self.parents) > 1 and self.exclude:
409
 
            raise CannotCommitSelectedFileMerge(self.exclude)
 
403
            raise errors.CannotCommitSelectedFileMerge(self.exclude)
410
404
 
411
405
        # Collect the changes
412
406
        self._set_progress_stage("Collecting changes", counter=True)
414
408
        self.builder = self.branch.get_commit_builder(self.parents,
415
409
            self.config_stack, timestamp, timezone, committer, self.revprops,
416
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)
417
414
 
418
415
        if self.builder.updates_branch and self.bound_branch:
419
416
            self.builder.abort()
422
419
                "that update the branch")
423
420
 
424
421
        try:
 
422
            self.builder.will_record_deletes()
425
423
            # find the location being committed to
426
424
            if self.bound_branch:
427
425
                master_location = self.master_branch.base
448
446
            # Add revision data to the local branch
449
447
            self.rev_id = self.builder.commit(self.message)
450
448
 
451
 
        except Exception as e:
 
449
        except Exception, e:
452
450
            mutter("aborting commit write group because of exception:")
453
451
            trace.log_exception_quietly()
454
452
            self.builder.abort()
459
457
        # Make the working tree be up to date with the branch. This
460
458
        # includes automatic changes scheduled to be made to the tree, such
461
459
        # as updating its basis and unversioning paths that were missing.
462
 
        self.work_tree.unversion(self.deleted_paths)
 
460
        self.work_tree.unversion(self.deleted_ids)
463
461
        self._set_progress_stage("Updating the working tree")
464
462
        self.work_tree.update_basis_by_delta(self.rev_id,
465
463
             self.builder.get_basis_delta())
585
583
            # this is so that we still consider the master branch
586
584
            # - in a checkout scenario the tree may have no
587
585
            # parents but the branch may do.
588
 
            first_tree_parent = breezy.revision.NULL_REVISION
 
586
            first_tree_parent = bzrlib.revision.NULL_REVISION
589
587
        old_revno, master_last = self.master_branch.last_revision_info()
590
588
        if master_last != first_tree_parent:
591
 
            if master_last != breezy.revision.NULL_REVISION:
 
589
            if master_last != bzrlib.revision.NULL_REVISION:
592
590
                raise errors.OutOfDateTree(self.work_tree)
593
591
        if self.branch.repository.has_revision(first_tree_parent):
594
592
            new_revno = old_revno + 1
615
613
            for hook in hooks:
616
614
                result = eval(hook + '(branch, rev_id)',
617
615
                              {'branch':self.branch,
618
 
                               'breezy':breezy,
 
616
                               'bzrlib':bzrlib,
619
617
                               'rev_id':self.rev_id})
620
618
        # process new style post commit hooks
621
619
        self._process_hooks("post_commit", old_revno, new_revno)
637
635
        if self.parents:
638
636
            old_revid = self.parents[0]
639
637
        else:
640
 
            old_revid = breezy.revision.NULL_REVISION
 
638
            old_revid = bzrlib.revision.NULL_REVISION
641
639
 
642
640
        if hook_name == "pre_commit":
643
641
            future_tree = self.builder.revision_tree()
663
661
                     old_revno, old_revid, new_revno, self.rev_id,
664
662
                     tree_delta, future_tree)
665
663
 
 
664
    def _gather_parents(self):
 
665
        """Record the parents of a merge for merge detection."""
 
666
        # TODO: Make sure that this list doesn't contain duplicate
 
667
        # entries and the order is preserved when doing this.
 
668
        if self.use_record_iter_changes:
 
669
            return
 
670
        self.basis_inv = self.basis_tree.root_inventory
 
671
        self.parent_invs = [self.basis_inv]
 
672
        for revision in self.parents[1:]:
 
673
            if self.branch.repository.has_revision(revision):
 
674
                mutter('commit parent revision {%s}', revision)
 
675
                inventory = self.branch.repository.get_inventory(revision)
 
676
                self.parent_invs.append(inventory)
 
677
            else:
 
678
                mutter('commit parent ghost revision {%s}', revision)
 
679
 
666
680
    def _update_builder_with_changes(self):
667
681
        """Update the commit builder with the data about what has changed.
668
682
        """
 
683
        exclude = self.exclude
669
684
        specific_files = self.specific_files
670
 
        mutter("Selecting files for commit with filter %r", specific_files)
 
685
        mutter("Selecting files for commit with filter %s", specific_files)
671
686
 
672
687
        self._check_strict()
673
 
        iter_changes = self.work_tree.iter_changes(self.basis_tree,
674
 
            specific_files=specific_files)
675
 
        if self.exclude:
676
 
            iter_changes = filter_excluded(iter_changes, self.exclude)
677
 
        iter_changes = self._filter_iter_changes(iter_changes)
678
 
        for file_id, path, fs_hash in self.builder.record_iter_changes(
679
 
            self.work_tree, self.basis_revid, iter_changes):
680
 
            self.work_tree._observed_sha1(file_id, path, fs_hash)
 
688
        if self.use_record_iter_changes:
 
689
            iter_changes = self.work_tree.iter_changes(self.basis_tree,
 
690
                specific_files=specific_files)
 
691
            iter_changes = self._filter_iter_changes(iter_changes)
 
692
            for file_id, path, fs_hash in self.builder.record_iter_changes(
 
693
                self.work_tree, self.basis_revid, iter_changes):
 
694
                self.work_tree._observed_sha1(file_id, path, fs_hash)
 
695
        else:
 
696
            # Build the new inventory
 
697
            self._populate_from_inventory()
 
698
            self._record_unselected()
 
699
            self._report_and_accumulate_deletes()
681
700
 
682
701
    def _filter_iter_changes(self, iter_changes):
683
702
        """Process iter_changes.
691
710
        """
692
711
        reporter = self.reporter
693
712
        report_changes = reporter.is_verbose()
694
 
        deleted_paths = []
 
713
        deleted_ids = []
695
714
        for change in iter_changes:
696
715
            if report_changes:
697
716
                old_path = change[1][0]
703
722
                # 'missing' path
704
723
                if report_changes:
705
724
                    reporter.missing(new_path)
706
 
                deleted_paths.append(change[1][1])
 
725
                deleted_ids.append(change[0])
707
726
                # Reset the new path (None) and new versioned flag (False)
708
727
                change = (change[0], (change[1][0], None), change[2],
709
728
                    (change[3][0], False)) + change[4:]
728
747
                            # repositories.
729
748
                            reporter.snapshot_change(gettext('modified'), new_path)
730
749
            self._next_progress_entry()
731
 
        # Unversion files that were found to be deleted
732
 
        self.deleted_paths = deleted_paths
 
750
        # Unversion IDs that were found to be deleted
 
751
        self.deleted_ids = deleted_ids
 
752
 
 
753
    def _record_unselected(self):
 
754
        # If specific files are selected, then all un-selected files must be
 
755
        # recorded in their previous state. For more details, see
 
756
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
 
757
        if self.specific_files or self.exclude:
 
758
            specific_files = self.specific_files or []
 
759
            for path, old_ie in self.basis_inv.iter_entries():
 
760
                if self.builder.new_inventory.has_id(old_ie.file_id):
 
761
                    # already added - skip.
 
762
                    continue
 
763
                if (is_inside_any(specific_files, path)
 
764
                    and not is_inside_any(self.exclude, path)):
 
765
                    # was inside the selected path, and not excluded - if not
 
766
                    # present it has been deleted so skip.
 
767
                    continue
 
768
                # From here down it was either not selected, or was excluded:
 
769
                # We preserve the entry unaltered.
 
770
                ie = old_ie.copy()
 
771
                # Note: specific file commits after a merge are currently
 
772
                # prohibited. This test is for sanity/safety in case it's
 
773
                # required after that changes.
 
774
                if len(self.parents) > 1:
 
775
                    ie.revision = None
 
776
                self.builder.record_entry_contents(ie, self.parent_invs, path,
 
777
                    self.basis_tree, None)
 
778
 
 
779
    def _report_and_accumulate_deletes(self):
 
780
        if (isinstance(self.basis_inv, Inventory)
 
781
            and isinstance(self.builder.new_inventory, Inventory)):
 
782
            # the older Inventory classes provide a _byid dict, and building a
 
783
            # set from the keys of this dict is substantially faster than even
 
784
            # getting a set of ids from the inventory
 
785
            #
 
786
            # <lifeless> set(dict) is roughly the same speed as
 
787
            # set(iter(dict)) and both are significantly slower than
 
788
            # set(dict.keys())
 
789
            deleted_ids = set(self.basis_inv._byid.keys()) - \
 
790
               set(self.builder.new_inventory._byid.keys())
 
791
        else:
 
792
            deleted_ids = set(self.basis_inv) - set(self.builder.new_inventory)
 
793
        if deleted_ids:
 
794
            self.any_entries_deleted = True
 
795
            deleted = [(self.basis_tree.id2path(file_id), file_id)
 
796
                for file_id in deleted_ids]
 
797
            deleted.sort()
 
798
            # XXX: this is not quite directory-order sorting
 
799
            for path, file_id in deleted:
 
800
                self.builder.record_delete(path, file_id)
 
801
                self.reporter.deleted(path)
733
802
 
734
803
    def _check_strict(self):
735
804
        # XXX: when we use iter_changes this would likely be faster if
740
809
            for unknown in self.work_tree.unknowns():
741
810
                raise StrictCommitFailed()
742
811
 
 
812
    def _populate_from_inventory(self):
 
813
        """Populate the CommitBuilder by walking the working tree inventory."""
 
814
        # Build the revision inventory.
 
815
        #
 
816
        # This starts by creating a new empty inventory. Depending on
 
817
        # which files are selected for commit, and what is present in the
 
818
        # current tree, the new inventory is populated. inventory entries
 
819
        # which are candidates for modification have their revision set to
 
820
        # None; inventory entries that are carried over untouched have their
 
821
        # revision set to their prior value.
 
822
        #
 
823
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
 
824
        # results to create a new inventory at the same time, which results
 
825
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
 
826
        # ADHB 11-07-2006
 
827
 
 
828
        specific_files = self.specific_files
 
829
        exclude = self.exclude
 
830
        report_changes = self.reporter.is_verbose()
 
831
        deleted_ids = []
 
832
        # A tree of paths that have been deleted. E.g. if foo/bar has been
 
833
        # deleted, then we have {'foo':{'bar':{}}}
 
834
        deleted_paths = {}
 
835
        # XXX: Note that entries may have the wrong kind because the entry does
 
836
        # not reflect the status on disk.
 
837
        # NB: entries will include entries within the excluded ids/paths
 
838
        # because iter_entries_by_dir has no 'exclude' facility today.
 
839
        entries = self.work_tree.iter_entries_by_dir(
 
840
            specific_file_ids=self.specific_file_ids, yield_parents=True)
 
841
        for path, existing_ie in entries:
 
842
            file_id = existing_ie.file_id
 
843
            name = existing_ie.name
 
844
            parent_id = existing_ie.parent_id
 
845
            kind = existing_ie.kind
 
846
            # Skip files that have been deleted from the working tree.
 
847
            # The deleted path ids are also recorded so they can be explicitly
 
848
            # unversioned later.
 
849
            if deleted_paths:
 
850
                path_segments = splitpath(path)
 
851
                deleted_dict = deleted_paths
 
852
                for segment in path_segments:
 
853
                    deleted_dict = deleted_dict.get(segment, None)
 
854
                    if not deleted_dict:
 
855
                        # We either took a path not present in the dict
 
856
                        # (deleted_dict was None), or we've reached an empty
 
857
                        # child dir in the dict, so are now a sub-path.
 
858
                        break
 
859
                else:
 
860
                    deleted_dict = None
 
861
                if deleted_dict is not None:
 
862
                    # the path has a deleted parent, do not add it.
 
863
                    continue
 
864
            if exclude and is_inside_any(exclude, path):
 
865
                # Skip excluded paths. Excluded paths are processed by
 
866
                # _update_builder_with_changes.
 
867
                continue
 
868
            content_summary = self.work_tree.path_content_summary(path)
 
869
            kind = content_summary[0]
 
870
            # Note that when a filter of specific files is given, we must only
 
871
            # skip/record deleted files matching that filter.
 
872
            if not specific_files or is_inside_any(specific_files, path):
 
873
                if kind == 'missing':
 
874
                    if not deleted_paths:
 
875
                        # path won't have been split yet.
 
876
                        path_segments = splitpath(path)
 
877
                    deleted_dict = deleted_paths
 
878
                    for segment in path_segments:
 
879
                        deleted_dict = deleted_dict.setdefault(segment, {})
 
880
                    self.reporter.missing(path)
 
881
                    self._next_progress_entry()
 
882
                    deleted_ids.append(file_id)
 
883
                    continue
 
884
            # TODO: have the builder do the nested commit just-in-time IF and
 
885
            # only if needed.
 
886
            if kind == 'tree-reference':
 
887
                # enforce repository nested tree policy.
 
888
                if (not self.work_tree.supports_tree_reference() or
 
889
                    # repository does not support it either.
 
890
                    not self.branch.repository._format.supports_tree_reference):
 
891
                    kind = 'directory'
 
892
                    content_summary = (kind, None, None, None)
 
893
                elif self.recursive == 'down':
 
894
                    nested_revision_id = self._commit_nested_tree(
 
895
                        file_id, path)
 
896
                    content_summary = (kind, None, None, nested_revision_id)
 
897
                else:
 
898
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
 
899
                    content_summary = (kind, None, None, nested_revision_id)
 
900
 
 
901
            # Record an entry for this item
 
902
            # Note: I don't particularly want to have the existing_ie
 
903
            # parameter but the test suite currently (28-Jun-07) breaks
 
904
            # without it thanks to a unicode normalisation issue. :-(
 
905
            definitely_changed = kind != existing_ie.kind
 
906
            self._record_entry(path, file_id, specific_files, kind, name,
 
907
                parent_id, definitely_changed, existing_ie, report_changes,
 
908
                content_summary)
 
909
 
 
910
        # Unversion IDs that were found to be deleted
 
911
        self.deleted_ids = deleted_ids
 
912
 
743
913
    def _commit_nested_tree(self, file_id, path):
744
914
        "Commit a nested tree."
745
 
        sub_tree = self.work_tree.get_nested_tree(path, file_id)
 
915
        sub_tree = self.work_tree.get_nested_tree(file_id, path)
746
916
        # FIXME: be more comprehensive here:
747
917
        # this works when both trees are in --trees repository,
748
918
        # but when both are bound to a different repository,
762
932
                allow_pointless=self.allow_pointless,
763
933
                strict=self.strict, verbose=self.verbose,
764
934
                local=self.local, reporter=self.reporter)
765
 
        except PointlessCommit:
766
 
            return self.work_tree.get_reference_revision(path, file_id)
 
935
        except errors.PointlessCommit:
 
936
            return self.work_tree.get_reference_revision(file_id)
 
937
 
 
938
    def _record_entry(self, path, file_id, specific_files, kind, name,
 
939
        parent_id, definitely_changed, existing_ie, report_changes,
 
940
        content_summary):
 
941
        "Record the new inventory entry for a path if any."
 
942
        # mutter('check %s {%s}', path, file_id)
 
943
        # mutter('%s selected for commit', path)
 
944
        if definitely_changed or existing_ie is None:
 
945
            ie = make_entry(kind, name, parent_id, file_id)
 
946
        else:
 
947
            ie = existing_ie.copy()
 
948
            ie.revision = None
 
949
        # For carried over entries we don't care about the fs hash - the repo
 
950
        # isn't generating a sha, so we're not saving computation time.
 
951
        _, _, fs_hash = self.builder.record_entry_contents(
 
952
            ie, self.parent_invs, path, self.work_tree, content_summary)
 
953
        if report_changes:
 
954
            self._report_change(ie, path)
 
955
        if fs_hash:
 
956
            self.work_tree._observed_sha1(ie.file_id, path, fs_hash)
 
957
        return ie
 
958
 
 
959
    def _report_change(self, ie, path):
 
960
        """Report a change to the user.
 
961
 
 
962
        The change that has occurred is described relative to the basis
 
963
        inventory.
 
964
        """
 
965
        if (self.basis_inv.has_id(ie.file_id)):
 
966
            basis_ie = self.basis_inv[ie.file_id]
 
967
        else:
 
968
            basis_ie = None
 
969
        change = ie.describe_change(basis_ie, ie)
 
970
        if change in (InventoryEntry.RENAMED,
 
971
            InventoryEntry.MODIFIED_AND_RENAMED):
 
972
            old_path = self.basis_inv.id2path(ie.file_id)
 
973
            self.reporter.renamed(change, old_path, path)
 
974
            self._next_progress_entry()
 
975
        else:
 
976
            if change == gettext('unchanged'):
 
977
                return
 
978
            self.reporter.snapshot_change(change, path)
 
979
            self._next_progress_entry()
767
980
 
768
981
    def _set_progress_stage(self, name, counter=False):
769
982
        """Set the progress stage and emit an update to the progress bar."""
787
1000
        else:
788
1001
            text = gettext("%s - Stage") % (self.pb_stage_name, )
789
1002
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)
 
1003
 
 
1004
    def _set_specific_file_ids(self):
 
1005
        """populate self.specific_file_ids if we will use it."""
 
1006
        if not self.use_record_iter_changes:
 
1007
            # If provided, ensure the specified files are versioned
 
1008
            if self.specific_files is not None:
 
1009
                # Note: This routine is being called because it raises
 
1010
                # PathNotVersionedError as a side effect of finding the IDs. We
 
1011
                # later use the ids we found as input to the working tree
 
1012
                # inventory iterator, so we only consider those ids rather than
 
1013
                # examining the whole tree again.
 
1014
                # XXX: Dont we have filter_unversioned to do this more
 
1015
                # cheaply?
 
1016
                self.specific_file_ids = tree.find_ids_across_trees(
 
1017
                    self.specific_files, [self.basis_tree, self.work_tree])
 
1018
            else:
 
1019
                self.specific_file_ids = None