~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Robert Collins
  • Date: 2007-09-05 05:51:34 UTC
  • mto: (2592.3.126 repository)
  • mto: This revision was merged to the branch mainline in revision 2879.
  • Revision ID: robertc@robertcollins.net-20070905055134-pwbueao0qq6krf9u
nuke _read_tree_state and snapshot from inventory, moving responsibility into the commit builder.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
# The newly committed revision is going to have a shape corresponding
19
 
# to that of the working tree.  Files that are not in the
 
19
# to that of the working inventory.  Files that are not in the
20
20
# working tree and that were in the predecessor are reported as
21
21
# removed --- this can include files that were either removed from the
22
22
# inventory or deleted in the working tree.  If they were only
25
25
# We then consider the remaining entries, which will be in the new
26
26
# version.  Directory entries are simply copied across.  File entries
27
27
# must be checked to see if a new version of the file should be
28
 
# recorded.  For each parent revision tree, we check to see what
 
28
# recorded.  For each parent revision inventory, we check to see what
29
29
# version of the file was present.  If the file was present in at
30
30
# least one tree, and if it was the same version in all the trees,
31
31
# then we can just refer to that version.  Otherwise, a new version
59
59
from bzrlib import (
60
60
    debug,
61
61
    errors,
62
 
    revision,
 
62
    inventory,
63
63
    tree,
64
64
    )
65
65
from bzrlib.branch import Branch
68
68
                           ConflictsInTree,
69
69
                           StrictCommitFailed
70
70
                           )
71
 
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any,
 
71
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any, 
72
72
                            is_inside_or_parent_of_any,
73
 
                            minimum_path_selection,
74
 
                            quotefn, sha_file, split_lines,
75
 
                            splitpath,
76
 
                            )
 
73
                            quotefn, sha_file, split_lines)
77
74
from bzrlib.testament import Testament
78
 
from bzrlib.trace import mutter, note, warning, is_quiet
 
75
from bzrlib.trace import mutter, note, warning
79
76
from bzrlib.xml5 import serializer_v5
80
 
from bzrlib.inventory import InventoryEntry, make_entry
 
77
from bzrlib.inventory import Inventory, InventoryEntry
81
78
from bzrlib import symbol_versioning
82
79
from bzrlib.symbol_versioning import (deprecated_passed,
83
80
        deprecated_function,
84
81
        DEPRECATED_PARAMETER)
85
82
from bzrlib.workingtree import WorkingTree
86
 
from bzrlib.urlutils import unescape_for_display
87
83
import bzrlib.ui
88
84
 
89
85
 
90
86
class NullCommitReporter(object):
91
87
    """I report on progress of a commit."""
92
88
 
93
 
    def started(self, revno, revid, location=None):
94
 
        if location is None:
95
 
            symbol_versioning.warn("As of bzr 1.0 you must pass a location "
96
 
                                   "to started.", DeprecationWarning,
97
 
                                   stacklevel=2)
98
 
        pass
99
 
 
100
89
    def snapshot_change(self, change, path):
101
90
        pass
102
91
 
115
104
    def renamed(self, change, old_path, new_path):
116
105
        pass
117
106
 
118
 
    def is_verbose(self):
119
 
        return False
120
 
 
121
107
 
122
108
class ReportCommitToLog(NullCommitReporter):
123
109
 
135
121
            return
136
122
        self._note("%s %s", change, path)
137
123
 
138
 
    def started(self, revno, rev_id, location=None):
139
 
        if location is not None:
140
 
            location = ' to: ' + unescape_for_display(location, 'utf-8')
141
 
        else:
142
 
            # When started was added, location was only made optional by
143
 
            # accident.  Matt Nordhoff 20071129
144
 
            symbol_versioning.warn("As of bzr 1.0 you must pass a location "
145
 
                                   "to started.", DeprecationWarning,
146
 
                                   stacklevel=2)
147
 
            location = ''
148
 
        self._note('Committing%s', location)
149
 
 
150
124
    def completed(self, revno, rev_id):
151
125
        self._note('Committed revision %d.', revno)
152
 
 
 
126
    
153
127
    def deleted(self, file_id):
154
128
        self._note('deleted %s', file_id)
155
129
 
162
136
    def renamed(self, change, old_path, new_path):
163
137
        self._note('%s %s => %s', change, old_path, new_path)
164
138
 
165
 
    def is_verbose(self):
166
 
        return True
167
 
 
168
139
 
169
140
class Commit(object):
170
141
    """Task of committing a new revision.
181
152
    def __init__(self,
182
153
                 reporter=None,
183
154
                 config=None):
184
 
        """Create a Commit object.
185
 
 
186
 
        :param reporter: the default reporter to use or None to decide later
187
 
        """
188
 
        self.reporter = reporter
 
155
        if reporter is not None:
 
156
            self.reporter = reporter
 
157
        else:
 
158
            self.reporter = NullCommitReporter()
189
159
        self.config = config
190
 
 
 
160
        
191
161
    def commit(self,
192
162
               message=None,
193
163
               timestamp=None,
204
174
               reporter=None,
205
175
               config=None,
206
176
               message_callback=None,
207
 
               recursive='down',
208
 
               exclude=None):
 
177
               recursive='down'):
209
178
        """Commit working copy as a new revision.
210
179
 
211
180
        :param message: the commit message (it or message_callback is required)
229
198
 
230
199
        :param revprops: Properties for new revision
231
200
        :param local: Perform a local only commit.
232
 
        :param reporter: the reporter to use or None for the default
233
 
        :param verbose: if True and the reporter is not None, report everything
234
201
        :param recursive: If set to 'down', commit in any subtrees that have
235
202
            pending changes of any sort during this commit.
236
 
        :param exclude: None or a list of relative paths to exclude from the
237
 
            commit. Pending changes to excluded files will be ignored by the
238
 
            commit. 
239
203
        """
240
204
        mutter('preparing to commit')
241
205
 
257
221
                               " parameter is required for commit().")
258
222
 
259
223
        self.bound_branch = None
260
 
        self.any_entries_changed = False
261
 
        self.any_entries_deleted = False
262
 
        if exclude is not None:
263
 
            self.exclude = sorted(
264
 
                minimum_path_selection(exclude))
265
 
        else:
266
 
            self.exclude = []
267
224
        self.local = local
268
225
        self.master_branch = None
269
226
        self.master_locked = False
270
 
        self.recursive = recursive
271
227
        self.rev_id = None
272
 
        if specific_files is not None:
273
 
            self.specific_files = sorted(
274
 
                minimum_path_selection(specific_files))
275
 
        else:
276
 
            self.specific_files = None
277
 
        self.specific_file_ids = None
 
228
        self.specific_files = specific_files
278
229
        self.allow_pointless = allow_pointless
279
230
        self.revprops = revprops
280
231
        self.message_callback = message_callback
283
234
        self.committer = committer
284
235
        self.strict = strict
285
236
        self.verbose = verbose
286
 
        # accumulates an inventory delta to the basis entry, so we can make
287
 
        # just the necessary updates to the workingtree's cached basis.
288
 
        self._basis_delta = []
 
237
 
 
238
        if reporter is None and self.reporter is None:
 
239
            self.reporter = NullCommitReporter()
 
240
        elif reporter is not None:
 
241
            self.reporter = reporter
289
242
 
290
243
        self.work_tree.lock_write()
291
244
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
292
 
        self.basis_revid = self.work_tree.last_revision()
293
245
        self.basis_tree = self.work_tree.basis_tree()
294
246
        self.basis_tree.lock_read()
295
247
        try:
303
255
            # Check that the working tree is up to date
304
256
            old_revno, new_revno = self._check_out_of_date_tree()
305
257
 
306
 
            # Complete configuration setup
307
 
            if reporter is not None:
308
 
                self.reporter = reporter
309
 
            elif self.reporter is None:
310
 
                self.reporter = self._select_reporter()
311
258
            if self.config is None:
312
259
                self.config = self.branch.get_config()
313
260
 
314
261
            # If provided, ensure the specified files are versioned
315
 
            if self.specific_files is not None:
316
 
                # Note: This routine is being called because it raises
317
 
                # PathNotVersionedError as a side effect of finding the IDs. We
318
 
                # later use the ids we found as input to the working tree
319
 
                # inventory iterator, so we only consider those ids rather than
320
 
                # examining the whole tree again.
 
262
            if specific_files is not None:
 
263
                # Note: We don't actually need the IDs here. This routine
 
264
                # is being called because it raises PathNotVerisonedError
 
265
                # as a side effect of finding the IDs.
321
266
                # XXX: Dont we have filter_unversioned to do this more
322
267
                # cheaply?
323
 
                self.specific_file_ids = tree.find_ids_across_trees(
324
 
                    specific_files, [self.basis_tree, self.work_tree])
 
268
                tree.find_ids_across_trees(specific_files,
 
269
                                           [self.basis_tree, self.work_tree])
325
270
 
326
271
            # Setup the progress bar. As the number of files that need to be
327
272
            # committed in unknown, progress is reported as stages.
338
283
            self.pb.show_count = True
339
284
            self.pb.show_bar = True
340
285
 
 
286
            # After a merge, a selected file commit is not supported.
 
287
            # See 'bzr help merge' for an explanation as to why.
341
288
            self.basis_inv = self.basis_tree.inventory
342
289
            self._gather_parents()
343
 
            # After a merge, a selected file commit is not supported.
344
 
            # See 'bzr help merge' for an explanation as to why.
345
290
            if len(self.parents) > 1 and self.specific_files:
346
291
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
347
 
            # Excludes are a form of selected file commit.
348
 
            if len(self.parents) > 1 and self.exclude:
349
 
                raise errors.CannotCommitSelectedFileMerge(self.exclude)
350
 
 
 
292
            
351
293
            # Collect the changes
352
294
            self._set_progress_stage("Collecting changes",
353
295
                    entries_title="Directory")
354
296
            self.builder = self.branch.get_commit_builder(self.parents,
355
297
                self.config, timestamp, timezone, committer, revprops, rev_id)
 
298
            # tell the builder about the chosen recursive behaviour
 
299
            self.builder.recursive = recursive
356
300
            
357
301
            try:
358
 
                # find the location being committed to
359
 
                if self.bound_branch:
360
 
                    master_location = self.master_branch.base
361
 
                else:
362
 
                    master_location = self.branch.base
363
 
 
364
 
                # report the start of the commit
365
 
                self.reporter.started(new_revno, self.rev_id, master_location)
366
 
 
367
302
                self._update_builder_with_changes()
368
 
                self._report_and_accumulate_deletes()
369
303
                self._check_pointless()
370
304
 
371
305
                # TODO: Now the new inventory is known, check for conflicts.
377
311
 
378
312
                # Prompt the user for a commit message if none provided
379
313
                message = message_callback(self)
 
314
                assert isinstance(message, unicode), type(message)
380
315
                self.message = message
381
316
                self._escape_commit_message()
382
317
 
392
327
            # Upload revision data to the master.
393
328
            # this will propagate merged revisions too if needed.
394
329
            if self.bound_branch:
395
 
                if not self.master_branch.repository.has_same_location(
396
 
                        self.branch.repository):
397
 
                    self._set_progress_stage("Uploading data to master branch")
398
 
                    self.master_branch.repository.fetch(self.branch.repository,
399
 
                        revision_id=self.rev_id)
 
330
                self._set_progress_stage("Uploading data to master branch")
 
331
                self.master_branch.repository.fetch(self.branch.repository,
 
332
                                                    revision_id=self.rev_id)
400
333
                # now the master has the revision data
401
334
                # 'commit' to the master first so a timeout here causes the
402
335
                # local branch to be out of date
408
341
 
409
342
            # Make the working tree up to date with the branch
410
343
            self._set_progress_stage("Updating the working tree")
411
 
            self.work_tree.update_basis_by_delta(self.rev_id,
412
 
                 self._basis_delta)
 
344
            rev_tree = self.builder.revision_tree()
 
345
            self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
413
346
            self.reporter.completed(new_revno, self.rev_id)
414
347
            self._process_post_hooks(old_revno, new_revno)
415
348
        finally:
416
349
            self._cleanup()
417
350
        return self.rev_id
418
351
 
419
 
    def _select_reporter(self):
420
 
        """Select the CommitReporter to use."""
421
 
        if is_quiet():
422
 
            return NullCommitReporter()
423
 
        return ReportCommitToLog()
 
352
    def _any_real_changes(self):
 
353
        """Are there real changes between new_inventory and basis?
 
354
 
 
355
        For trees without rich roots, inv.root.revision changes every commit.
 
356
        But if that is the only change, we want to treat it as though there
 
357
        are *no* changes.
 
358
        """
 
359
        new_entries = self.builder.new_inventory.iter_entries()
 
360
        basis_entries = self.basis_inv.iter_entries()
 
361
        new_path, new_root_ie = new_entries.next()
 
362
        basis_path, basis_root_ie = basis_entries.next()
 
363
 
 
364
        # This is a copy of InventoryEntry.__eq__ only leaving out .revision
 
365
        def ie_equal_no_revision(this, other):
 
366
            return ((this.file_id == other.file_id)
 
367
                    and (this.name == other.name)
 
368
                    and (this.symlink_target == other.symlink_target)
 
369
                    and (this.text_sha1 == other.text_sha1)
 
370
                    and (this.text_size == other.text_size)
 
371
                    and (this.text_id == other.text_id)
 
372
                    and (this.parent_id == other.parent_id)
 
373
                    and (this.kind == other.kind)
 
374
                    and (this.executable == other.executable)
 
375
                    and (this.reference_revision == other.reference_revision)
 
376
                    )
 
377
        if not ie_equal_no_revision(new_root_ie, basis_root_ie):
 
378
            return True
 
379
 
 
380
        for new_ie, basis_ie in zip(new_entries, basis_entries):
 
381
            if new_ie != basis_ie:
 
382
                return True
 
383
 
 
384
        # No actual changes present
 
385
        return False
424
386
 
425
387
    def _check_pointless(self):
426
388
        if self.allow_pointless:
428
390
        # A merge with no effect on files
429
391
        if len(self.parents) > 1:
430
392
            return
431
 
        # TODO: we could simplify this by using self._basis_delta.
432
 
 
433
 
        # The initial commit adds a root directory, but this in itself is not
434
 
        # a worthwhile commit.
435
 
        if (self.basis_revid == revision.NULL_REVISION and
436
 
            len(self.builder.new_inventory) == 1):
 
393
        # work around the fact that a newly-initted tree does differ from its
 
394
        # basis
 
395
        if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
437
396
            raise PointlessCommit()
 
397
        # Shortcut, if the number of entries changes, then we obviously have
 
398
        # a change
 
399
        if len(self.builder.new_inventory) != len(self.basis_inv):
 
400
            return
438
401
        # If length == 1, then we only have the root entry. Which means
439
402
        # that there is no real difference (only the root could be different)
440
 
        # unless deletes occured, in which case the length is irrelevant.
441
 
        if (self.any_entries_deleted or 
442
 
            (len(self.builder.new_inventory) != 1 and
443
 
             self.any_entries_changed)):
 
403
        if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
444
404
            return
445
405
        raise PointlessCommit()
446
406
 
660
620
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
661
621
        # ADHB 11-07-2006
662
622
 
663
 
        exclude = self.exclude
664
 
        specific_files = self.specific_files or []
 
623
        specific_files = self.specific_files
665
624
        mutter("Selecting files for commit with filter %s", specific_files)
666
625
 
 
626
        # Check and warn about old CommitBuilders
 
627
        if not self.builder.record_root_entry:
 
628
            symbol_versioning.warn('CommitBuilders should support recording'
 
629
                ' the root entry as of bzr 0.10.', DeprecationWarning, 
 
630
                stacklevel=1)
 
631
            self.builder.new_inventory.add(self.basis_inv.root.copy())
 
632
 
667
633
        # Build the new inventory
668
 
        self._populate_from_inventory()
 
634
        self._populate_from_inventory(specific_files)
669
635
 
670
636
        # If specific files are selected, then all un-selected files must be
671
637
        # recorded in their previous state. For more details, see
672
638
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
673
 
        if specific_files or exclude:
674
 
            for path, old_ie in self.basis_inv.iter_entries():
675
 
                if old_ie.file_id in self.builder.new_inventory:
676
 
                    # already added - skip.
677
 
                    continue
678
 
                if (is_inside_any(specific_files, path)
679
 
                    and not is_inside_any(exclude, path)):
680
 
                    # was inside the selected path, and not excluded - if not
681
 
                    # present it has been deleted so skip.
682
 
                    continue
683
 
                # From here down it was either not selected, or was excluded:
684
 
                if old_ie.kind == 'directory':
685
 
                    self._next_progress_entry()
686
 
                # We preserve the entry unaltered.
687
 
                ie = old_ie.copy()
688
 
                # Note: specific file commits after a merge are currently
689
 
                # prohibited. This test is for sanity/safety in case it's
690
 
                # required after that changes.
691
 
                if len(self.parents) > 1:
692
 
                    ie.revision = None
693
 
                delta, version_recorded = self.builder.record_entry_contents(
694
 
                    ie, self.parent_invs, path, self.basis_tree, None)
695
 
                if version_recorded:
696
 
                    self.any_entries_changed = True
697
 
                if delta: self._basis_delta.append(delta)
 
639
        if specific_files:
 
640
            for path, new_ie in self.basis_inv.iter_entries():
 
641
                if new_ie.file_id in self.builder.new_inventory:
 
642
                    continue
 
643
                if is_inside_any(specific_files, path):
 
644
                    continue
 
645
                ie = new_ie.copy()
 
646
                ie.revision = None
 
647
                self.builder.record_entry_contents(ie, self.parent_invs, path,
 
648
                                                   self.basis_tree)
698
649
 
699
 
    def _report_and_accumulate_deletes(self):
700
 
        # XXX: Could the list of deleted paths and ids be instead taken from
701
 
        # _populate_from_inventory?
702
 
        deleted_ids = set(self.basis_inv._byid.keys()) - \
703
 
            set(self.builder.new_inventory._byid.keys())
704
 
        if deleted_ids:
705
 
            self.any_entries_deleted = True
706
 
            deleted = [(self.basis_tree.id2path(file_id), file_id)
707
 
                for file_id in deleted_ids]
708
 
            deleted.sort()
709
 
            # XXX: this is not quite directory-order sorting
710
 
            for path, file_id in deleted:
711
 
                self._basis_delta.append((path, None, file_id, None))
 
650
        # Report what was deleted. We could skip this when no deletes are
 
651
        # detected to gain a performance win, but it arguably serves as a
 
652
        # 'safety check' by informing the user whenever anything disappears.
 
653
        for path, ie in self.basis_inv.iter_entries():
 
654
            if ie.file_id not in self.builder.new_inventory:
712
655
                self.reporter.deleted(path)
713
656
 
714
 
    def _populate_from_inventory(self):
 
657
    def _populate_from_inventory(self, specific_files):
715
658
        """Populate the CommitBuilder by walking the working tree inventory."""
716
659
        if self.strict:
717
660
            # raise an exception as soon as we find a single unknown.
718
661
            for unknown in self.work_tree.unknowns():
719
662
                raise StrictCommitFailed()
720
 
        
721
 
        specific_files = self.specific_files
722
 
        exclude = self.exclude
723
 
        report_changes = self.reporter.is_verbose()
 
663
               
724
664
        deleted_ids = []
725
 
        # A tree of paths that have been deleted. E.g. if foo/bar has been
726
 
        # deleted, then we have {'foo':{'bar':{}}}
727
 
        deleted_paths = {}
728
 
        # XXX: Note that entries may have the wrong kind because the entry does
729
 
        # not reflect the status on disk.
 
665
        deleted_paths = set()
730
666
        work_inv = self.work_tree.inventory
731
 
        # NB: entries will include entries within the excluded ids/paths
732
 
        # because iter_entries_by_dir has no 'exclude' facility today.
733
 
        entries = work_inv.iter_entries_by_dir(
734
 
            specific_file_ids=self.specific_file_ids, yield_parents=True)
 
667
        assert work_inv.root is not None
 
668
        entries = work_inv.iter_entries()
 
669
        # XXX: Note that entries may have the wrong kind.
 
670
        if not self.builder.record_root_entry:
 
671
            entries.next()
735
672
        for path, existing_ie in entries:
736
673
            file_id = existing_ie.file_id
737
674
            name = existing_ie.name
739
676
            kind = existing_ie.kind
740
677
            if kind == 'directory':
741
678
                self._next_progress_entry()
 
679
 
742
680
            # Skip files that have been deleted from the working tree.
743
 
            # The deleted path ids are also recorded so they can be explicitly
744
 
            # unversioned later.
745
 
            if deleted_paths:
746
 
                path_segments = splitpath(path)
747
 
                deleted_dict = deleted_paths
748
 
                for segment in path_segments:
749
 
                    deleted_dict = deleted_dict.get(segment, None)
750
 
                    if not deleted_dict:
751
 
                        # We either took a path not present in the dict
752
 
                        # (deleted_dict was None), or we've reached an empty
753
 
                        # child dir in the dict, so are now a sub-path.
754
 
                        break
755
 
                else:
756
 
                    deleted_dict = None
757
 
                if deleted_dict is not None:
758
 
                    # the path has a deleted parent, do not add it.
759
 
                    continue
760
 
            if exclude and is_inside_any(exclude, path):
761
 
                # Skip excluded paths. Excluded paths are processed by
762
 
                # _update_builder_with_changes.
 
681
            # The deleted files/directories are also recorded so they
 
682
            # can be explicitly unversioned later. Note that when a
 
683
            # filter of specific files is given, we must only skip/record
 
684
            # deleted files matching that filter.
 
685
            if is_inside_any(deleted_paths, path):
763
686
                continue
764
 
            content_summary = self.work_tree.path_content_summary(path)
765
 
            # Note that when a filter of specific files is given, we must only
766
 
            # skip/record deleted files matching that filter.
767
687
            if not specific_files or is_inside_any(specific_files, path):
768
 
                if content_summary[0] == 'missing':
769
 
                    if not deleted_paths:
770
 
                        # path won't have been split yet.
771
 
                        path_segments = splitpath(path)
772
 
                    deleted_dict = deleted_paths
773
 
                    for segment in path_segments:
774
 
                        deleted_dict = deleted_dict.setdefault(segment, {})
 
688
                # TODO: fix double-stat here.
 
689
                if not self.work_tree.has_filename(path):
 
690
                    deleted_paths.add(path)
775
691
                    self.reporter.missing(path)
776
692
                    deleted_ids.append(file_id)
777
693
                    continue
778
694
            # TODO: have the builder do the nested commit just-in-time IF and
779
695
            # only if needed.
780
 
            if content_summary[0] == 'tree-reference':
781
 
                # enforce repository nested tree policy.
782
 
                if (not self.work_tree.supports_tree_reference() or
783
 
                    # repository does not support it either.
784
 
                    not self.branch.repository._format.supports_tree_reference):
785
 
                    content_summary = ('directory',) + content_summary[1:]
786
 
            kind = content_summary[0]
787
 
            # TODO: specific_files filtering before nested tree processing
788
 
            if kind == 'tree-reference':
789
 
                if self.recursive == 'down':
790
 
                    nested_revision_id = self._commit_nested_tree(
791
 
                        file_id, path)
792
 
                    content_summary = content_summary[:3] + (
793
 
                        nested_revision_id,)
794
 
                else:
795
 
                    content_summary = content_summary[:3] + (
796
 
                        self.work_tree.get_reference_revision(file_id),)
 
696
            try:
 
697
                kind = self.work_tree.kind(file_id)
 
698
                # TODO: specific_files filtering before nested tree processing
 
699
                if kind == 'tree-reference' and self.builder.recursive == 'down':
 
700
                    self._commit_nested_tree(file_id, path)
 
701
            except errors.NoSuchFile:
 
702
                pass
797
703
 
798
704
            # Record an entry for this item
799
705
            # Note: I don't particularly want to have the existing_ie
801
707
            # without it thanks to a unicode normalisation issue. :-(
802
708
            definitely_changed = kind != existing_ie.kind
803
709
            self._record_entry(path, file_id, specific_files, kind, name,
804
 
                parent_id, definitely_changed, existing_ie, report_changes,
805
 
                content_summary)
 
710
                parent_id, definitely_changed, existing_ie)
806
711
 
807
712
        # Unversion IDs that were found to be deleted
808
713
        self.work_tree.unversion(deleted_ids)
821
726
            sub_tree.branch.repository = \
822
727
                self.work_tree.branch.repository
823
728
        try:
824
 
            return sub_tree.commit(message=None, revprops=self.revprops,
825
 
                recursive=self.recursive,
 
729
            sub_tree.commit(message=None, revprops=self.revprops,
 
730
                recursive=self.builder.recursive,
826
731
                message_callback=self.message_callback,
827
732
                timestamp=self.timestamp, timezone=self.timezone,
828
733
                committer=self.committer,
830
735
                strict=self.strict, verbose=self.verbose,
831
736
                local=self.local, reporter=self.reporter)
832
737
        except errors.PointlessCommit:
833
 
            return self.work_tree.get_reference_revision(file_id)
 
738
            pass
834
739
 
835
740
    def _record_entry(self, path, file_id, specific_files, kind, name,
836
 
        parent_id, definitely_changed, existing_ie, report_changes,
837
 
        content_summary):
 
741
                      parent_id, definitely_changed, existing_ie=None):
838
742
        "Record the new inventory entry for a path if any."
839
743
        # mutter('check %s {%s}', path, file_id)
840
 
        # mutter('%s selected for commit', path)
841
 
        if definitely_changed or existing_ie is None:
842
 
            ie = make_entry(kind, name, parent_id, file_id)
 
744
        if (not specific_files or 
 
745
            is_inside_or_parent_of_any(specific_files, path)):
 
746
                # mutter('%s selected for commit', path)
 
747
                if definitely_changed or existing_ie is None:
 
748
                    ie = inventory.make_entry(kind, name, parent_id, file_id)
 
749
                else:
 
750
                    ie = existing_ie.copy()
 
751
                    ie.revision = None
843
752
        else:
844
 
            ie = existing_ie.copy()
845
 
            ie.revision = None
846
 
        delta, version_recorded = self.builder.record_entry_contents(ie,
847
 
            self.parent_invs, path, self.work_tree, content_summary)
848
 
        if delta:
849
 
            self._basis_delta.append(delta)
850
 
        if version_recorded:
851
 
            self.any_entries_changed = True
852
 
        if report_changes:
 
753
            # mutter('%s not selected for commit', path)
 
754
            if self.basis_inv.has_id(file_id):
 
755
                ie = self.basis_inv[file_id].copy()
 
756
            else:
 
757
                # this entry is new and not being committed
 
758
                ie = None
 
759
        if ie is not None:
 
760
            self.builder.record_entry_contents(ie, self.parent_invs, 
 
761
                path, self.work_tree)
853
762
            self._report_change(ie, path)
854
763
        return ie
855
764