~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-02 18:59:18 UTC
  • mto: This revision was merged to the branch mainline in revision 4469.
  • Revision ID: john@arbash-meinel.com-20090602185918-86l9eljnn8z2iljk
Add a VersionedFile.add_text() api.

Similar to VF.add_lines() except it takes a string for the content, rather
than a list of lines.

For now, it just thunks over to VF.add_lines(), but it will be special
cased in the future.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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
49
49
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
50
50
# the rest of the code; add a deprecation of the old name.
51
51
 
 
52
import os
 
53
import re
 
54
import sys
 
55
import time
 
56
 
 
57
from cStringIO import StringIO
 
58
 
52
59
from bzrlib import (
53
60
    debug,
54
61
    errors,
 
62
    revision,
55
63
    trace,
56
64
    tree,
57
 
    ui,
 
65
    xml_serializer,
58
66
    )
59
67
from bzrlib.branch import Branch
60
 
from bzrlib.cleanup import OperationWithCleanups
61
68
import bzrlib.config
62
69
from bzrlib.errors import (BzrError, PointlessCommit,
63
70
                           ConflictsInTree,
64
71
                           StrictCommitFailed
65
72
                           )
66
73
from bzrlib.osutils import (get_user_encoding,
67
 
                            is_inside_any,
 
74
                            kind_marker, isdir,isfile, is_inside_any,
 
75
                            is_inside_or_parent_of_any,
68
76
                            minimum_path_selection,
 
77
                            quotefn, sha_file, split_lines,
69
78
                            splitpath,
70
79
                            )
71
 
from bzrlib.trace import mutter, note, is_quiet
 
80
from bzrlib.testament import Testament
 
81
from bzrlib.trace import mutter, note, warning, is_quiet
72
82
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
73
83
from bzrlib import symbol_versioning
 
84
from bzrlib.symbol_versioning import (deprecated_passed,
 
85
        deprecated_function,
 
86
        DEPRECATED_PARAMETER)
 
87
from bzrlib.workingtree import WorkingTree
74
88
from bzrlib.urlutils import unescape_for_display
 
89
import bzrlib.ui
75
90
 
76
91
 
77
92
class NullCommitReporter(object):
93
108
    def deleted(self, path):
94
109
        pass
95
110
 
 
111
    def escaped(self, escape_count, message):
 
112
        pass
 
113
 
96
114
    def missing(self, path):
97
115
        pass
98
116
 
131
149
 
132
150
    def completed(self, revno, rev_id):
133
151
        self._note('Committed revision %d.', revno)
134
 
        # self._note goes to the console too; so while we want to log the
135
 
        # rev_id, we can't trivially only log it. (See bug 526425). Long
136
 
        # term we should rearrange the reporting structure, but for now
137
 
        # we just mutter seperately. We mutter the revid and revno together
138
 
        # so that concurrent bzr invocations won't lead to confusion.
139
 
        mutter('Committed revid %s as revno %d.', rev_id, revno)
140
152
 
141
153
    def deleted(self, path):
142
154
        self._note('deleted %s', path)
143
155
 
 
156
    def escaped(self, escape_count, message):
 
157
        self._note("replaced %d control characters in message", escape_count)
 
158
 
144
159
    def missing(self, path):
145
160
        self._note('missing %s', path)
146
161
 
173
188
        self.reporter = reporter
174
189
        self.config = config
175
190
 
176
 
    @staticmethod
177
 
    def update_revprops(revprops, branch, authors=None, author=None,
178
 
                        local=False, possible_master_transports=None):
179
 
        if revprops is None:
180
 
            revprops = {}
181
 
        if possible_master_transports is None:
182
 
            possible_master_transports = []
183
 
        if not 'branch-nick' in revprops:
184
 
            revprops['branch-nick'] = branch._get_nick(
185
 
                local,
186
 
                possible_master_transports)
187
 
        if authors is not None:
188
 
            if author is not None:
189
 
                raise AssertionError('Specifying both author and authors '
190
 
                        'is not allowed. Specify just authors instead')
191
 
            if 'author' in revprops or 'authors' in revprops:
192
 
                # XXX: maybe we should just accept one of them?
193
 
                raise AssertionError('author property given twice')
194
 
            if authors:
195
 
                for individual in authors:
196
 
                    if '\n' in individual:
197
 
                        raise AssertionError('\\n is not a valid character '
198
 
                                'in an author identity')
199
 
                revprops['authors'] = '\n'.join(authors)
200
 
        if author is not None:
201
 
            symbol_versioning.warn('The parameter author was deprecated'
202
 
                   ' in version 1.13. Use authors instead',
203
 
                   DeprecationWarning)
204
 
            if 'author' in revprops or 'authors' in revprops:
205
 
                # XXX: maybe we should just accept one of them?
206
 
                raise AssertionError('author property given twice')
207
 
            if '\n' in author:
208
 
                raise AssertionError('\\n is not a valid character '
209
 
                        'in an author identity')
210
 
            revprops['authors'] = author
211
 
        return revprops
212
 
 
213
191
    def commit(self,
214
192
               message=None,
215
193
               timestamp=None,
228
206
               message_callback=None,
229
207
               recursive='down',
230
208
               exclude=None,
231
 
               possible_master_transports=None,
232
 
               lossy=False):
 
209
               possible_master_transports=None):
233
210
        """Commit working copy as a new revision.
234
211
 
235
212
        :param message: the commit message (it or message_callback is required)
236
 
        :param message_callback: A callback: message = message_callback(cmt_obj)
237
213
 
238
214
        :param timestamp: if not None, seconds-since-epoch for a
239
215
            postdated/predated commit.
240
216
 
241
 
        :param specific_files: If not None, commit only those files. An empty
242
 
            list means 'commit no files'.
 
217
        :param specific_files: If true, commit only those files.
243
218
 
244
219
        :param rev_id: If set, use this as the new revision id.
245
220
            Useful for test or import commands that need to tightly
262
237
        :param exclude: None or a list of relative paths to exclude from the
263
238
            commit. Pending changes to excluded files will be ignored by the
264
239
            commit.
265
 
        :param lossy: When committing to a foreign VCS, ignore any
266
 
            data that can not be natively represented.
267
240
        """
268
 
        operation = OperationWithCleanups(self._commit)
269
 
        self.revprops = revprops or {}
270
 
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
271
 
        self.config = config or self.config
272
 
        return operation.run(
273
 
               message=message,
274
 
               timestamp=timestamp,
275
 
               timezone=timezone,
276
 
               committer=committer,
277
 
               specific_files=specific_files,
278
 
               rev_id=rev_id,
279
 
               allow_pointless=allow_pointless,
280
 
               strict=strict,
281
 
               verbose=verbose,
282
 
               working_tree=working_tree,
283
 
               local=local,
284
 
               reporter=reporter,
285
 
               message_callback=message_callback,
286
 
               recursive=recursive,
287
 
               exclude=exclude,
288
 
               possible_master_transports=possible_master_transports,
289
 
               lossy=lossy)
290
 
 
291
 
    def _commit(self, operation, message, timestamp, timezone, committer,
292
 
            specific_files, rev_id, allow_pointless, strict, verbose,
293
 
            working_tree, local, reporter, message_callback, recursive,
294
 
            exclude, possible_master_transports, lossy):
295
241
        mutter('preparing to commit')
296
242
 
297
243
        if working_tree is None:
320
266
            self.exclude = []
321
267
        self.local = local
322
268
        self.master_branch = None
 
269
        self.master_locked = False
323
270
        self.recursive = recursive
324
271
        self.rev_id = None
325
 
        # self.specific_files is None to indicate no filter, or any iterable to
326
 
        # indicate a filter - [] means no files at all, as per iter_changes.
327
272
        if specific_files is not None:
328
273
            self.specific_files = sorted(
329
274
                minimum_path_selection(specific_files))
330
275
        else:
331
276
            self.specific_files = None
332
 
 
 
277
            
333
278
        self.allow_pointless = allow_pointless
 
279
        self.revprops = revprops
334
280
        self.message_callback = message_callback
335
281
        self.timestamp = timestamp
336
282
        self.timezone = timezone
339
285
        self.verbose = verbose
340
286
 
341
287
        self.work_tree.lock_write()
342
 
        operation.add_cleanup(self.work_tree.unlock)
343
288
        self.parents = self.work_tree.get_parent_ids()
344
289
        # We can use record_iter_changes IFF iter_changes is compatible with
345
290
        # the command line parameters, and the repository has fast delta
346
291
        # generation. See bug 347649.
347
292
        self.use_record_iter_changes = (
 
293
            not self.specific_files and
348
294
            not self.exclude and 
349
295
            not self.branch.repository._format.supports_tree_reference and
350
296
            (self.branch.repository._format.fast_deltas or
351
297
             len(self.parents) < 2))
352
 
        self.pb = ui.ui_factory.nested_progress_bar()
353
 
        operation.add_cleanup(self.pb.finished)
 
298
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
354
299
        self.basis_revid = self.work_tree.last_revision()
355
300
        self.basis_tree = self.work_tree.basis_tree()
356
301
        self.basis_tree.lock_read()
357
 
        operation.add_cleanup(self.basis_tree.unlock)
358
 
        # Cannot commit with conflicts present.
359
 
        if len(self.work_tree.conflicts()) > 0:
360
 
            raise ConflictsInTree
361
 
 
362
 
        # Setup the bound branch variables as needed.
363
 
        self._check_bound_branch(operation, possible_master_transports)
364
 
 
365
 
        # Check that the working tree is up to date
366
 
        old_revno, new_revno = self._check_out_of_date_tree()
367
 
 
368
 
        # Complete configuration setup
369
 
        if reporter is not None:
370
 
            self.reporter = reporter
371
 
        elif self.reporter is None:
372
 
            self.reporter = self._select_reporter()
373
 
        if self.config is None:
374
 
            self.config = self.branch.get_config()
375
 
 
376
 
        self._set_specific_file_ids()
377
 
 
378
 
        # Setup the progress bar. As the number of files that need to be
379
 
        # committed in unknown, progress is reported as stages.
380
 
        # We keep track of entries separately though and include that
381
 
        # information in the progress bar during the relevant stages.
382
 
        self.pb_stage_name = ""
383
 
        self.pb_stage_count = 0
384
 
        self.pb_stage_total = 5
385
 
        if self.bound_branch:
386
 
            # 2 extra stages: "Uploading data to master branch" and "Merging
387
 
            # tags to master branch"
388
 
            self.pb_stage_total += 2
389
 
        self.pb.show_pct = False
390
 
        self.pb.show_spinner = False
391
 
        self.pb.show_eta = False
392
 
        self.pb.show_count = True
393
 
        self.pb.show_bar = True
394
 
 
395
 
        self._gather_parents()
396
 
        # After a merge, a selected file commit is not supported.
397
 
        # See 'bzr help merge' for an explanation as to why.
398
 
        if len(self.parents) > 1 and self.specific_files is not None:
399
 
            raise errors.CannotCommitSelectedFileMerge(self.specific_files)
400
 
        # Excludes are a form of selected file commit.
401
 
        if len(self.parents) > 1 and self.exclude:
402
 
            raise errors.CannotCommitSelectedFileMerge(self.exclude)
403
 
 
404
 
        # Collect the changes
405
 
        self._set_progress_stage("Collecting changes", counter=True)
406
 
        self._lossy = lossy
407
 
        self.builder = self.branch.get_commit_builder(self.parents,
408
 
            self.config, timestamp, timezone, committer, self.revprops,
409
 
            rev_id, lossy=lossy)
410
 
        if not self.builder.supports_record_entry_contents and self.exclude:
411
 
            self.builder.abort()
412
 
            raise errors.ExcludesUnsupported(self.branch.repository)
413
 
 
414
302
        try:
415
 
            self.builder.will_record_deletes()
416
 
            # find the location being committed to
417
 
            if self.bound_branch:
418
 
                master_location = self.master_branch.base
419
 
            else:
420
 
                master_location = self.branch.base
421
 
 
422
 
            # report the start of the commit
423
 
            self.reporter.started(new_revno, self.rev_id, master_location)
424
 
 
425
 
            self._update_builder_with_changes()
426
 
            self._check_pointless()
427
 
 
428
 
            # TODO: Now the new inventory is known, check for conflicts.
429
 
            # ADHB 2006-08-08: If this is done, populate_new_inv should not add
430
 
            # weave lines, because nothing should be recorded until it is known
431
 
            # that commit will succeed.
432
 
            self._set_progress_stage("Saving data locally")
433
 
            self.builder.finish_inventory()
434
 
 
435
 
            # Prompt the user for a commit message if none provided
436
 
            message = message_callback(self)
437
 
            self.message = message
438
 
 
439
 
            # Add revision data to the local branch
440
 
            self.rev_id = self.builder.commit(self.message)
441
 
 
442
 
        except Exception, e:
443
 
            mutter("aborting commit write group because of exception:")
444
 
            trace.log_exception_quietly()
445
 
            self.builder.abort()
446
 
            raise
447
 
 
448
 
        self._process_pre_hooks(old_revno, new_revno)
449
 
 
450
 
        # Upload revision data to the master.
451
 
        # this will propagate merged revisions too if needed.
452
 
        if self.bound_branch:
453
 
            self._set_progress_stage("Uploading data to master branch")
454
 
            # 'commit' to the master first so a timeout here causes the
455
 
            # local branch to be out of date
456
 
            (new_revno, self.rev_id) = self.master_branch.import_last_revision_info_and_tags(
457
 
                self.branch, new_revno, self.rev_id, lossy=lossy)
458
 
            if lossy:
459
 
                self.branch.fetch(self.master_branch, self.rev_id)
460
 
 
461
 
        # and now do the commit locally.
462
 
        self.branch.set_last_revision_info(new_revno, self.rev_id)
463
 
 
464
 
        # Merge local tags to remote
465
 
        if self.bound_branch:
466
 
            self._set_progress_stage("Merging tags to master branch")
467
 
            tag_conflicts = self.branch.tags.merge_to(self.master_branch.tags)
468
 
            if tag_conflicts:
469
 
                warning_lines = ['    ' + name for name, _, _ in tag_conflicts]
470
 
                note("Conflicting tags in bound branch:\n" +
471
 
                    "\n".join(warning_lines))
472
 
 
473
 
        # Make the working tree be up to date with the branch. This
474
 
        # includes automatic changes scheduled to be made to the tree, such
475
 
        # as updating its basis and unversioning paths that were missing.
476
 
        self.work_tree.unversion(self.deleted_ids)
477
 
        self._set_progress_stage("Updating the working tree")
478
 
        self.work_tree.update_basis_by_delta(self.rev_id,
479
 
             self.builder.get_basis_delta())
480
 
        self.reporter.completed(new_revno, self.rev_id)
481
 
        self._process_post_hooks(old_revno, new_revno)
 
303
            # Cannot commit with conflicts present.
 
304
            if len(self.work_tree.conflicts()) > 0:
 
305
                raise ConflictsInTree
 
306
 
 
307
            # Setup the bound branch variables as needed.
 
308
            self._check_bound_branch(possible_master_transports)
 
309
 
 
310
            # Check that the working tree is up to date
 
311
            old_revno, new_revno = self._check_out_of_date_tree()
 
312
 
 
313
            # Complete configuration setup
 
314
            if reporter is not None:
 
315
                self.reporter = reporter
 
316
            elif self.reporter is None:
 
317
                self.reporter = self._select_reporter()
 
318
            if self.config is None:
 
319
                self.config = self.branch.get_config()
 
320
 
 
321
            self._set_specific_file_ids()
 
322
 
 
323
            # Setup the progress bar. As the number of files that need to be
 
324
            # committed in unknown, progress is reported as stages.
 
325
            # We keep track of entries separately though and include that
 
326
            # information in the progress bar during the relevant stages.
 
327
            self.pb_stage_name = ""
 
328
            self.pb_stage_count = 0
 
329
            self.pb_stage_total = 5
 
330
            if self.bound_branch:
 
331
                self.pb_stage_total += 1
 
332
            self.pb.show_pct = False
 
333
            self.pb.show_spinner = False
 
334
            self.pb.show_eta = False
 
335
            self.pb.show_count = True
 
336
            self.pb.show_bar = True
 
337
 
 
338
            self._gather_parents()
 
339
            # After a merge, a selected file commit is not supported.
 
340
            # See 'bzr help merge' for an explanation as to why.
 
341
            if len(self.parents) > 1 and self.specific_files:
 
342
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
 
343
            # Excludes are a form of selected file commit.
 
344
            if len(self.parents) > 1 and self.exclude:
 
345
                raise errors.CannotCommitSelectedFileMerge(self.exclude)
 
346
 
 
347
            # Collect the changes
 
348
            self._set_progress_stage("Collecting changes", counter=True)
 
349
            self.builder = self.branch.get_commit_builder(self.parents,
 
350
                self.config, timestamp, timezone, committer, revprops, rev_id)
 
351
 
 
352
            try:
 
353
                self.builder.will_record_deletes()
 
354
                # find the location being committed to
 
355
                if self.bound_branch:
 
356
                    master_location = self.master_branch.base
 
357
                else:
 
358
                    master_location = self.branch.base
 
359
 
 
360
                # report the start of the commit
 
361
                self.reporter.started(new_revno, self.rev_id, master_location)
 
362
 
 
363
                self._update_builder_with_changes()
 
364
                self._check_pointless()
 
365
 
 
366
                # TODO: Now the new inventory is known, check for conflicts.
 
367
                # ADHB 2006-08-08: If this is done, populate_new_inv should not add
 
368
                # weave lines, because nothing should be recorded until it is known
 
369
                # that commit will succeed.
 
370
                self._set_progress_stage("Saving data locally")
 
371
                self.builder.finish_inventory()
 
372
 
 
373
                # Prompt the user for a commit message if none provided
 
374
                message = message_callback(self)
 
375
                self.message = message
 
376
                self._escape_commit_message()
 
377
 
 
378
                # Add revision data to the local branch
 
379
                self.rev_id = self.builder.commit(self.message)
 
380
 
 
381
            except Exception, e:
 
382
                mutter("aborting commit write group because of exception:")
 
383
                trace.log_exception_quietly()
 
384
                note("aborting commit write group: %r" % (e,))
 
385
                self.builder.abort()
 
386
                raise
 
387
 
 
388
            self._process_pre_hooks(old_revno, new_revno)
 
389
 
 
390
            # Upload revision data to the master.
 
391
            # this will propagate merged revisions too if needed.
 
392
            if self.bound_branch:
 
393
                self._set_progress_stage("Uploading data to master branch")
 
394
                # 'commit' to the master first so a timeout here causes the
 
395
                # local branch to be out of date
 
396
                self.master_branch.import_last_revision_info(
 
397
                    self.branch.repository, new_revno, self.rev_id)
 
398
 
 
399
            # and now do the commit locally.
 
400
            self.branch.set_last_revision_info(new_revno, self.rev_id)
 
401
 
 
402
            # Make the working tree up to date with the branch
 
403
            self._set_progress_stage("Updating the working tree")
 
404
            self.work_tree.update_basis_by_delta(self.rev_id,
 
405
                 self.builder.get_basis_delta())
 
406
            self.reporter.completed(new_revno, self.rev_id)
 
407
            self._process_post_hooks(old_revno, new_revno)
 
408
        finally:
 
409
            self._cleanup()
482
410
        return self.rev_id
483
411
 
484
412
    def _select_reporter(self):
493
421
        # A merge with no effect on files
494
422
        if len(self.parents) > 1:
495
423
            return
 
424
        # TODO: we could simplify this by using self.builder.basis_delta.
 
425
 
 
426
        # The initial commit adds a root directory, but this in itself is not
 
427
        # a worthwhile commit.
 
428
        if (self.basis_revid == revision.NULL_REVISION and
 
429
            ((self.builder.new_inventory is not None and
 
430
             len(self.builder.new_inventory) == 1) or
 
431
            len(self.builder._basis_delta) == 1)):
 
432
            raise PointlessCommit()
496
433
        if self.builder.any_changes():
497
434
            return
498
435
        raise PointlessCommit()
499
436
 
500
 
    def _check_bound_branch(self, operation, possible_master_transports=None):
 
437
    def _check_bound_branch(self, possible_master_transports=None):
501
438
        """Check to see if the local branch is bound.
502
439
 
503
440
        If it is bound, then most of the commit will actually be
538
475
        # so grab the lock
539
476
        self.bound_branch = self.branch
540
477
        self.master_branch.lock_write()
541
 
        operation.add_cleanup(self.master_branch.unlock)
 
478
        self.master_locked = True
542
479
 
543
480
    def _check_out_of_date_tree(self):
544
481
        """Check that the working tree is up to date.
629
566
                     old_revno, old_revid, new_revno, self.rev_id,
630
567
                     tree_delta, future_tree)
631
568
 
 
569
    def _cleanup(self):
 
570
        """Cleanup any open locks, progress bars etc."""
 
571
        cleanups = [self._cleanup_bound_branch,
 
572
                    self.basis_tree.unlock,
 
573
                    self.work_tree.unlock,
 
574
                    self.pb.finished]
 
575
        found_exception = None
 
576
        for cleanup in cleanups:
 
577
            try:
 
578
                cleanup()
 
579
            # we want every cleanup to run no matter what.
 
580
            # so we have a catchall here, but we will raise the
 
581
            # last encountered exception up the stack: and
 
582
            # typically this will be useful enough.
 
583
            except Exception, e:
 
584
                found_exception = e
 
585
        if found_exception is not None:
 
586
            # don't do a plan raise, because the last exception may have been
 
587
            # trashed, e is our sure-to-work exception even though it loses the
 
588
            # full traceback. XXX: RBC 20060421 perhaps we could check the
 
589
            # exc_info and if its the same one do a plain raise otherwise
 
590
            # 'raise e' as we do now.
 
591
            raise e
 
592
 
 
593
    def _cleanup_bound_branch(self):
 
594
        """Executed at the end of a try/finally to cleanup a bound branch.
 
595
 
 
596
        If the branch wasn't bound, this is a no-op.
 
597
        If it was, it resents self.branch to the local branch, instead
 
598
        of being the master.
 
599
        """
 
600
        if not self.bound_branch:
 
601
            return
 
602
        if self.master_locked:
 
603
            self.master_branch.unlock()
 
604
 
 
605
    def _escape_commit_message(self):
 
606
        """Replace xml-incompatible control characters."""
 
607
        # FIXME: RBC 20060419 this should be done by the revision
 
608
        # serialiser not by commit. Then we can also add an unescaper
 
609
        # in the deserializer and start roundtripping revision messages
 
610
        # precisely. See repository_implementations/test_repository.py
 
611
        self.message, escape_count = xml_serializer.escape_invalid_chars(
 
612
            self.message)
 
613
        if escape_count:
 
614
            self.reporter.escaped(escape_count, self.message)
 
615
 
632
616
    def _gather_parents(self):
633
617
        """Record the parents of a merge for merge detection."""
634
618
        # TODO: Make sure that this list doesn't contain duplicate
649
633
        """Update the commit builder with the data about what has changed.
650
634
        """
651
635
        exclude = self.exclude
652
 
        specific_files = self.specific_files
 
636
        specific_files = self.specific_files or []
653
637
        mutter("Selecting files for commit with filter %s", specific_files)
654
638
 
655
639
        self._check_strict()
656
640
        if self.use_record_iter_changes:
657
 
            iter_changes = self.work_tree.iter_changes(self.basis_tree,
658
 
                specific_files=specific_files)
 
641
            iter_changes = self.work_tree.iter_changes(self.basis_tree)
659
642
            iter_changes = self._filter_iter_changes(iter_changes)
660
643
            for file_id, path, fs_hash in self.builder.record_iter_changes(
661
644
                self.work_tree, self.basis_revid, iter_changes):
714
697
                            reporter.snapshot_change('modified', new_path)
715
698
            self._next_progress_entry()
716
699
        # Unversion IDs that were found to be deleted
717
 
        self.deleted_ids = deleted_ids
 
700
        self.work_tree.unversion(deleted_ids)
718
701
 
719
702
    def _record_unselected(self):
720
703
        # If specific files are selected, then all un-selected files must be
723
706
        if self.specific_files or self.exclude:
724
707
            specific_files = self.specific_files or []
725
708
            for path, old_ie in self.basis_inv.iter_entries():
726
 
                if self.builder.new_inventory.has_id(old_ie.file_id):
 
709
                if old_ie.file_id in self.builder.new_inventory:
727
710
                    # already added - skip.
728
711
                    continue
729
712
                if (is_inside_any(specific_files, path)
833
816
                # _update_builder_with_changes.
834
817
                continue
835
818
            content_summary = self.work_tree.path_content_summary(path)
836
 
            kind = content_summary[0]
837
819
            # Note that when a filter of specific files is given, we must only
838
820
            # skip/record deleted files matching that filter.
839
821
            if not specific_files or is_inside_any(specific_files, path):
840
 
                if kind == 'missing':
 
822
                if content_summary[0] == 'missing':
841
823
                    if not deleted_paths:
842
824
                        # path won't have been split yet.
843
825
                        path_segments = splitpath(path)
850
832
                    continue
851
833
            # TODO: have the builder do the nested commit just-in-time IF and
852
834
            # only if needed.
853
 
            if kind == 'tree-reference':
 
835
            if content_summary[0] == 'tree-reference':
854
836
                # enforce repository nested tree policy.
855
837
                if (not self.work_tree.supports_tree_reference() or
856
838
                    # repository does not support it either.
857
839
                    not self.branch.repository._format.supports_tree_reference):
858
 
                    kind = 'directory'
859
 
                    content_summary = (kind, None, None, None)
860
 
                elif self.recursive == 'down':
 
840
                    content_summary = ('directory',) + content_summary[1:]
 
841
            kind = content_summary[0]
 
842
            # TODO: specific_files filtering before nested tree processing
 
843
            if kind == 'tree-reference':
 
844
                if self.recursive == 'down':
861
845
                    nested_revision_id = self._commit_nested_tree(
862
846
                        file_id, path)
863
 
                    content_summary = (kind, None, None, nested_revision_id)
 
847
                    content_summary = content_summary[:3] + (
 
848
                        nested_revision_id,)
864
849
                else:
865
 
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
866
 
                    content_summary = (kind, None, None, nested_revision_id)
 
850
                    content_summary = content_summary[:3] + (
 
851
                        self.work_tree.get_reference_revision(file_id),)
867
852
 
868
853
            # Record an entry for this item
869
854
            # Note: I don't particularly want to have the existing_ie
875
860
                content_summary)
876
861
 
877
862
        # Unversion IDs that were found to be deleted
878
 
        self.deleted_ids = deleted_ids
 
863
        self.work_tree.unversion(deleted_ids)
879
864
 
880
865
    def _commit_nested_tree(self, file_id, path):
881
866
        "Commit a nested tree."