Пример #1
0
 def _record_unselected(self):
     # If specific files are selected, then all un-selected files must be
     # recorded in their previous state. For more details, see
     # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
     if self.specific_files or self.exclude:
         specific_files = self.specific_files or []
         for path, old_ie in self.basis_inv.iter_entries():
             if self.builder.new_inventory.has_id(old_ie.file_id):
                 # already added - skip.
                 continue
             if (is_inside_any(specific_files, path)
                 and not is_inside_any(self.exclude, path)):
                 # was inside the selected path, and not excluded - if not
                 # present it has been deleted so skip.
                 continue
             # From here down it was either not selected, or was excluded:
             # We preserve the entry unaltered.
             ie = old_ie.copy()
             # Note: specific file commits after a merge are currently
             # prohibited. This test is for sanity/safety in case it's
             # required after that changes.
             if len(self.parents) > 1:
                 ie.revision = None
             self.builder.record_entry_contents(ie, self.parent_invs, path,
                 self.basis_tree, None)
Пример #2
0
 def _record_unselected(self):
     # If specific files are selected, then all un-selected files must be
     # recorded in their previous state. For more details, see
     # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
     if self.specific_files or self.exclude:
         specific_files = self.specific_files or []
         for path, old_ie in self.basis_inv.iter_entries():
             if self.builder.new_inventory.has_id(old_ie.file_id):
                 # already added - skip.
                 continue
             if (is_inside_any(specific_files, path)
                     and not is_inside_any(self.exclude, path)):
                 # was inside the selected path, and not excluded - if not
                 # present it has been deleted so skip.
                 continue
             # From here down it was either not selected, or was excluded:
             # We preserve the entry unaltered.
             ie = old_ie.copy()
             # Note: specific file commits after a merge are currently
             # prohibited. This test is for sanity/safety in case it's
             # required after that changes.
             if len(self.parents) > 1:
                 ie.revision = None
             self.builder.record_entry_contents(ie, self.parent_invs, path,
                                                self.basis_tree, None)
Пример #3
0
    def _update_builder_with_changes(self):
        """Update the commit builder with the data about what has changed.
        """
        # Build the revision inventory.
        #
        # This starts by creating a new empty inventory. Depending on
        # which files are selected for commit, and what is present in the
        # current tree, the new inventory is populated. inventory entries 
        # which are candidates for modification have their revision set to
        # None; inventory entries that are carried over untouched have their
        # revision set to their prior value.
        #
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
        # results to create a new inventory at the same time, which results
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
        # ADHB 11-07-2006

        exclude = self.exclude
        specific_files = self.specific_files or []
        mutter("Selecting files for commit with filter %s", specific_files)

        # Build the new inventory
        self._populate_from_inventory()

        # If specific files are selected, then all un-selected files must be
        # recorded in their previous state. For more details, see
        # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
        if specific_files or exclude:
            for path, old_ie in self.basis_inv.iter_entries():
                if old_ie.file_id in self.builder.new_inventory:
                    # already added - skip.
                    continue
                if (is_inside_any(specific_files, path)
                    and not is_inside_any(exclude, path)):
                    # was inside the selected path, and not excluded - if not
                    # present it has been deleted so skip.
                    continue
                # From here down it was either not selected, or was excluded:
                if old_ie.kind == 'directory':
                    self._next_progress_entry()
                # We preserve the entry unaltered.
                ie = old_ie.copy()
                # Note: specific file commits after a merge are currently
                # prohibited. This test is for sanity/safety in case it's
                # required after that changes.
                if len(self.parents) > 1:
                    ie.revision = None
                delta, version_recorded = self.builder.record_entry_contents(
                    ie, self.parent_invs, path, self.basis_tree, None)
                if version_recorded:
                    self.any_entries_changed = True
                if delta: self._basis_delta.append(delta)
Пример #4
0
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
               kind):
        """Report one change to a file

        :param file_id: The file_id of the file
        :param path: The old and new paths as generated by Tree.iter_changes.
        :param versioned: may be 'added', 'removed', 'unchanged', or
            'unversioned.
        :param renamed: may be True or False
        :param modified: may be 'created', 'deleted', 'kind changed',
            'modified' or 'unchanged'.
        :param exe_change: True if the execute bit has changed
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
            None indicates no file present.
        """
        if is_quiet():
            return
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
            return
        if self.view_files and not osutils.is_inside_any(self.view_files,
            paths[1]):
            return
        if versioned == 'unversioned':
            # skip ignored unversioned files if needed.
            if self.unversioned_filter is not None:
                if self.unversioned_filter(paths[1]):
                    return
            # dont show a content change in the output.
            modified = 'unchanged'
        # we show both paths in the following situations:
        # the file versioning is unchanged AND
        # ( the path is different OR
        #   the kind is different)
        if (versioned == 'unchanged' and
            (renamed or modified == 'kind changed')):
            if renamed:
                # on a rename, we show old and new
                old_path, path = paths
            else:
                # if it's not renamed, we're showing both for kind changes
                # so only show the new path
                old_path, path = paths[1], paths[1]
            # if the file is not missing in the source, we show its kind
            # when we show two paths.
            if kind[0] is not None:
                old_path += self.kind_marker(kind[0])
            old_path += " => "
        elif versioned == 'removed':
            # not present in target
            old_path = ""
            path = paths[0]
        else:
            old_path = ""
            path = paths[1]
        if renamed:
            rename = "R"
        else:
            rename = self.versioned_map[versioned]
        # we show the old kind on the new path when the content is deleted.
        if modified == 'deleted':
            path += self.kind_marker(kind[0])
        # otherwise we always show the current kind when there is one
        elif kind[1] is not None:
            path += self.kind_marker(kind[1])
        if exe_change:
            exe = '*'
        else:
            exe = ' '
        self.output("%s%s%s %s%s", rename, self.modified_map[modified], exe,
                    old_path, path)
Пример #5
0
def check_path_in_view(tree, relpath):
    """If a working tree has a view enabled, check the path is within it."""
    if tree.supports_views():
        view_files = tree.views.lookup_view()
        if view_files and not osutils.is_inside_any(view_files, relpath):
            raise errors.FileOutsideView(relpath, view_files)
Пример #6
0
    def _populate_from_inventory(self):
        """Populate the CommitBuilder by walking the working tree inventory."""
        if self.strict:
            # raise an exception as soon as we find a single unknown.
            for unknown in self.work_tree.unknowns():
                raise StrictCommitFailed()
        
        specific_files = self.specific_files
        exclude = self.exclude
        report_changes = self.reporter.is_verbose()
        deleted_ids = []
        # A tree of paths that have been deleted. E.g. if foo/bar has been
        # deleted, then we have {'foo':{'bar':{}}}
        deleted_paths = {}
        # XXX: Note that entries may have the wrong kind because the entry does
        # not reflect the status on disk.
        work_inv = self.work_tree.inventory
        # NB: entries will include entries within the excluded ids/paths
        # because iter_entries_by_dir has no 'exclude' facility today.
        entries = work_inv.iter_entries_by_dir(
            specific_file_ids=self.specific_file_ids, yield_parents=True)
        for path, existing_ie in entries:
            file_id = existing_ie.file_id
            name = existing_ie.name
            parent_id = existing_ie.parent_id
            kind = existing_ie.kind
            if kind == 'directory':
                self._next_progress_entry()
            # Skip files that have been deleted from the working tree.
            # The deleted path ids are also recorded so they can be explicitly
            # unversioned later.
            if deleted_paths:
                path_segments = splitpath(path)
                deleted_dict = deleted_paths
                for segment in path_segments:
                    deleted_dict = deleted_dict.get(segment, None)
                    if not deleted_dict:
                        # We either took a path not present in the dict
                        # (deleted_dict was None), or we've reached an empty
                        # child dir in the dict, so are now a sub-path.
                        break
                else:
                    deleted_dict = None
                if deleted_dict is not None:
                    # the path has a deleted parent, do not add it.
                    continue
            if exclude and is_inside_any(exclude, path):
                # Skip excluded paths. Excluded paths are processed by
                # _update_builder_with_changes.
                continue
            content_summary = self.work_tree.path_content_summary(path)
            # Note that when a filter of specific files is given, we must only
            # skip/record deleted files matching that filter.
            if not specific_files or is_inside_any(specific_files, path):
                if content_summary[0] == 'missing':
                    if not deleted_paths:
                        # path won't have been split yet.
                        path_segments = splitpath(path)
                    deleted_dict = deleted_paths
                    for segment in path_segments:
                        deleted_dict = deleted_dict.setdefault(segment, {})
                    self.reporter.missing(path)
                    deleted_ids.append(file_id)
                    continue
            # TODO: have the builder do the nested commit just-in-time IF and
            # only if needed.
            if content_summary[0] == 'tree-reference':
                # enforce repository nested tree policy.
                if (not self.work_tree.supports_tree_reference() or
                    # repository does not support it either.
                    not self.branch.repository._format.supports_tree_reference):
                    content_summary = ('directory',) + content_summary[1:]
            kind = content_summary[0]
            # TODO: specific_files filtering before nested tree processing
            if kind == 'tree-reference':
                if self.recursive == 'down':
                    nested_revision_id = self._commit_nested_tree(
                        file_id, path)
                    content_summary = content_summary[:3] + (
                        nested_revision_id,)
                else:
                    content_summary = content_summary[:3] + (
                        self.work_tree.get_reference_revision(file_id),)

            # Record an entry for this item
            # Note: I don't particularly want to have the existing_ie
            # parameter but the test suite currently (28-Jun-07) breaks
            # without it thanks to a unicode normalisation issue. :-(
            definitely_changed = kind != existing_ie.kind
            self._record_entry(path, file_id, specific_files, kind, name,
                parent_id, definitely_changed, existing_ie, report_changes,
                content_summary)

        # Unversion IDs that were found to be deleted
        self.work_tree.unversion(deleted_ids)
Пример #7
0
    def _populate_from_inventory(self):
        """Populate the CommitBuilder by walking the working tree inventory."""
        # Build the revision inventory.
        #
        # This starts by creating a new empty inventory. Depending on
        # which files are selected for commit, and what is present in the
        # current tree, the new inventory is populated. inventory entries
        # which are candidates for modification have their revision set to
        # None; inventory entries that are carried over untouched have their
        # revision set to their prior value.
        #
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
        # results to create a new inventory at the same time, which results
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
        # ADHB 11-07-2006

        specific_files = self.specific_files
        exclude = self.exclude
        report_changes = self.reporter.is_verbose()
        deleted_ids = []
        # A tree of paths that have been deleted. E.g. if foo/bar has been
        # deleted, then we have {'foo':{'bar':{}}}
        deleted_paths = {}
        # XXX: Note that entries may have the wrong kind because the entry does
        # not reflect the status on disk.
        # NB: entries will include entries within the excluded ids/paths
        # because iter_entries_by_dir has no 'exclude' facility today.
        entries = self.work_tree.iter_entries_by_dir(
            specific_file_ids=self.specific_file_ids, yield_parents=True)
        for path, existing_ie in entries:
            file_id = existing_ie.file_id
            name = existing_ie.name
            parent_id = existing_ie.parent_id
            kind = existing_ie.kind
            # Skip files that have been deleted from the working tree.
            # The deleted path ids are also recorded so they can be explicitly
            # unversioned later.
            if deleted_paths:
                path_segments = splitpath(path)
                deleted_dict = deleted_paths
                for segment in path_segments:
                    deleted_dict = deleted_dict.get(segment, None)
                    if not deleted_dict:
                        # We either took a path not present in the dict
                        # (deleted_dict was None), or we've reached an empty
                        # child dir in the dict, so are now a sub-path.
                        break
                else:
                    deleted_dict = None
                if deleted_dict is not None:
                    # the path has a deleted parent, do not add it.
                    continue
            if exclude and is_inside_any(exclude, path):
                # Skip excluded paths. Excluded paths are processed by
                # _update_builder_with_changes.
                continue
            content_summary = self.work_tree.path_content_summary(path)
            kind = content_summary[0]
            # Note that when a filter of specific files is given, we must only
            # skip/record deleted files matching that filter.
            if not specific_files or is_inside_any(specific_files, path):
                if kind == 'missing':
                    if not deleted_paths:
                        # path won't have been split yet.
                        path_segments = splitpath(path)
                    deleted_dict = deleted_paths
                    for segment in path_segments:
                        deleted_dict = deleted_dict.setdefault(segment, {})
                    self.reporter.missing(path)
                    self._next_progress_entry()
                    deleted_ids.append(file_id)
                    continue
            # TODO: have the builder do the nested commit just-in-time IF and
            # only if needed.
            if kind == 'tree-reference':
                # enforce repository nested tree policy.
                if (not self.work_tree.supports_tree_reference() or
                    # repository does not support it either.
                    not self.branch.repository._format.supports_tree_reference):
                    kind = 'directory'
                    content_summary = (kind, None, None, None)
                elif self.recursive == 'down':
                    nested_revision_id = self._commit_nested_tree(
                        file_id, path)
                    content_summary = (kind, None, None, nested_revision_id)
                else:
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
                    content_summary = (kind, None, None, nested_revision_id)

            # Record an entry for this item
            # Note: I don't particularly want to have the existing_ie
            # parameter but the test suite currently (28-Jun-07) breaks
            # without it thanks to a unicode normalisation issue. :-(
            definitely_changed = kind != existing_ie.kind
            self._record_entry(path, file_id, specific_files, kind, name,
                parent_id, definitely_changed, existing_ie, report_changes,
                content_summary)

        # Unversion IDs that were found to be deleted
        self.deleted_ids = deleted_ids
Пример #8
0
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
               kind):
        """Report one change to a file

        :param file_id: The file_id of the file
        :param path: The old and new paths as generated by Tree.iter_changes.
        :param versioned: may be 'added', 'removed', 'unchanged', or
            'unversioned.
        :param renamed: may be True or False
        :param modified: may be 'created', 'deleted', 'kind changed',
            'modified' or 'unchanged'.
        :param exe_change: True if the execute bit has changed
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
            None indicates no file present.
        """
        if is_quiet():
            return
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
            return
        if self.view_files and not osutils.is_inside_any(
                self.view_files, paths[1]):
            return
        if versioned == 'unversioned':
            # skip ignored unversioned files if needed.
            if self.unversioned_filter is not None:
                if self.unversioned_filter(paths[1]):
                    return
            # dont show a content change in the output.
            modified = 'unchanged'
        # we show both paths in the following situations:
        # the file versioning is unchanged AND
        # ( the path is different OR
        #   the kind is different)
        if (versioned == 'unchanged'
                and (renamed or modified == 'kind changed')):
            if renamed:
                # on a rename, we show old and new
                old_path, path = paths
            else:
                # if it's not renamed, we're showing both for kind changes
                # so only show the new path
                old_path, path = paths[1], paths[1]
            # if the file is not missing in the source, we show its kind
            # when we show two paths.
            if kind[0] is not None:
                old_path += self.kind_marker(kind[0])
            old_path += " => "
        elif versioned == 'removed':
            # not present in target
            old_path = ""
            path = paths[0]
        else:
            old_path = ""
            path = paths[1]
        if renamed:
            rename = "R"
        else:
            rename = self.versioned_map[versioned]
        # we show the old kind on the new path when the content is deleted.
        if modified == 'deleted':
            path += self.kind_marker(kind[0])
        # otherwise we always show the current kind when there is one
        elif kind[1] is not None:
            path += self.kind_marker(kind[1])
        if exe_change:
            exe = '*'
        else:
            exe = ' '
        self.output("%s%s%s %s%s", rename, self.modified_map[modified], exe,
                    old_path, path)
Пример #9
0
    def iter_changes(self, include_unchanged=False,
                      specific_files=None, pb=None, extra_trees=[],
                      require_versioned=True, want_unversioned=False):
        """Generate an iterator of changes between trees.

        A tuple is returned:
        (file_id, (path_in_source, path_in_target),
         changed_content, versioned, parent, name, kind,
         executable)

        Changed_content is True if the file's content has changed.  This
        includes changes to its kind, and to a symlink's target.

        versioned, parent, name, kind, executable are tuples of (from, to).
        If a file is missing in a tree, its kind is None.

        Iteration is done in parent-to-child order, relative to the target
        tree.

        There is no guarantee that all paths are in sorted order: the
        requirement to expand the search due to renames may result in children
        that should be found early being found late in the search, after
        lexically later results have been returned.
        :param require_versioned: Raise errors.PathsNotVersionedError if a
            path in the specific_files list is not versioned in one of
            source, target or extra_trees.
        :param want_unversioned: Should unversioned files be returned in the
            output. An unversioned file is defined as one with (False, False)
            for the versioned pair.
        """
        result = []
        lookup_trees = [self.source]
        if extra_trees:
             lookup_trees.extend(extra_trees)
        if specific_files == []:
            specific_file_ids = []
        else:
            specific_file_ids = self.target.paths2ids(specific_files,
                lookup_trees, require_versioned=require_versioned)
        if want_unversioned:
            all_unversioned = sorted([(p.split('/'), p) for p in
                                     self.target.extras()
                if specific_files is None or
                    osutils.is_inside_any(specific_files, p)])
            all_unversioned = deque(all_unversioned)
        else:
            all_unversioned = deque()
        to_paths = {}
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
            specific_file_ids=specific_file_ids))
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
            specific_file_ids=specific_file_ids))
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
        entry_count = 0
        # the unversioned path lookup only occurs on real trees - where there 
        # can be extras. So the fake_entry is solely used to look up
        # executable it values when execute is not supported.
        fake_entry = InventoryFile('unused', 'unused', 'unused')
        for to_path, to_entry in to_entries_by_dir:
            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
                unversioned_path = all_unversioned.popleft()
                to_kind, to_executable, to_stat = \
                    self.target._comparison_data(fake_entry, unversioned_path[1])
                yield (None, (None, unversioned_path[1]), True, (False, False),
                    (None, None),
                    (None, unversioned_path[0][-1]),
                    (None, to_kind),
                    (None, to_executable))
            file_id = to_entry.file_id
            to_paths[file_id] = to_path
            entry_count += 1
            changed_content = False
            from_path, from_entry = from_data.get(file_id, (None, None))
            from_versioned = (from_entry is not None)
            if from_entry is not None:
                from_versioned = True
                from_name = from_entry.name
                from_parent = from_entry.parent_id
                from_kind, from_executable, from_stat = \
                    self.source._comparison_data(from_entry, from_path)
                entry_count += 1
            else:
                from_versioned = False
                from_kind = None
                from_parent = None
                from_name = None
                from_executable = None
            versioned = (from_versioned, True)
            to_kind, to_executable, to_stat = \
                self.target._comparison_data(to_entry, to_path)
            kind = (from_kind, to_kind)
            if kind[0] != kind[1]:
                changed_content = True
            elif from_kind == 'file':
                from_size = self.source._file_size(from_entry, from_stat)
                to_size = self.target._file_size(to_entry, to_stat)
                if from_size != to_size:
                    changed_content = True
                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
                    changed_content = True
            elif from_kind == 'symlink':
                if (self.source.get_symlink_target(file_id) !=
                    self.target.get_symlink_target(file_id)):
                    changed_content = True
                elif from_kind == 'tree-reference':
                    if (self.source.get_reference_revision(file_id, from_path)
                        != self.target.get_reference_revision(file_id, to_path)):
                        changed_content = True 
            parent = (from_parent, to_entry.parent_id)
            name = (from_name, to_entry.name)
            executable = (from_executable, to_executable)
            if pb is not None:
                pb.update('comparing files', entry_count, num_entries)
            if (changed_content is not False or versioned[0] != versioned[1]
                or parent[0] != parent[1] or name[0] != name[1] or 
                executable[0] != executable[1] or include_unchanged):
                yield (file_id, (from_path, to_path), changed_content,
                    versioned, parent, name, kind, executable)

        while all_unversioned:
            # yield any trailing unversioned paths
            unversioned_path = all_unversioned.popleft()
            to_kind, to_executable, to_stat = \
                self.target._comparison_data(fake_entry, unversioned_path[1])
            yield (None, (None, unversioned_path[1]), True, (False, False),
                (None, None),
                (None, unversioned_path[0][-1]),
                (None, to_kind),
                (None, to_executable))

        def get_to_path(to_entry):
            if to_entry.parent_id is None:
                to_path = '' # the root
            else:
                if to_entry.parent_id not in to_paths:
                    # recurse up
                    return get_to_path(self.target.inventory[to_entry.parent_id])
                to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
                                           to_entry.name)
            to_paths[to_entry.file_id] = to_path
            return to_path

        for path, from_entry in from_entries_by_dir:
            file_id = from_entry.file_id
            if file_id in to_paths:
                # already returned
                continue
            if not file_id in self.target.inventory:
                # common case - paths we have not emitted are not present in
                # target.
                to_path = None
            else:
                to_path = get_to_path(self.target.inventory[file_id])
            entry_count += 1
            if pb is not None:
                pb.update('comparing files', entry_count, num_entries)
            versioned = (True, False)
            parent = (from_entry.parent_id, None)
            name = (from_entry.name, None)
            from_kind, from_executable, stat_value = \
                self.source._comparison_data(from_entry, path)
            kind = (from_kind, None)
            executable = (from_executable, None)
            changed_content = True
            # the parent's path is necessarily known at this point.
            yield(file_id, (path, to_path), changed_content, versioned, parent,
                  name, kind, executable)
Пример #10
0
def check_path_in_view(tree, relpath):
    """If a working tree has a view enabled, check the path is within it."""
    if tree.supports_views():
        view_files = tree.views.lookup_view()
        if  view_files and not osutils.is_inside_any(view_files, relpath):
            raise errors.FileOutsideView(relpath, view_files)
Пример #11
0
    def _populate_from_inventory(self):
        """Populate the CommitBuilder by walking the working tree inventory."""
        # Build the revision inventory.
        #
        # This starts by creating a new empty inventory. Depending on
        # which files are selected for commit, and what is present in the
        # current tree, the new inventory is populated. inventory entries
        # which are candidates for modification have their revision set to
        # None; inventory entries that are carried over untouched have their
        # revision set to their prior value.
        #
        # ESEPARATIONOFCONCERNS: this function is diffing and using the diff
        # results to create a new inventory at the same time, which results
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
        # ADHB 11-07-2006

        specific_files = self.specific_files
        exclude = self.exclude
        report_changes = self.reporter.is_verbose()
        deleted_ids = []
        # A tree of paths that have been deleted. E.g. if foo/bar has been
        # deleted, then we have {'foo':{'bar':{}}}
        deleted_paths = {}
        # XXX: Note that entries may have the wrong kind because the entry does
        # not reflect the status on disk.
        # NB: entries will include entries within the excluded ids/paths
        # because iter_entries_by_dir has no 'exclude' facility today.
        entries = self.work_tree.iter_entries_by_dir(
            specific_file_ids=self.specific_file_ids, yield_parents=True)
        for path, existing_ie in entries:
            file_id = existing_ie.file_id
            name = existing_ie.name
            parent_id = existing_ie.parent_id
            kind = existing_ie.kind
            # Skip files that have been deleted from the working tree.
            # The deleted path ids are also recorded so they can be explicitly
            # unversioned later.
            if deleted_paths:
                path_segments = splitpath(path)
                deleted_dict = deleted_paths
                for segment in path_segments:
                    deleted_dict = deleted_dict.get(segment, None)
                    if not deleted_dict:
                        # We either took a path not present in the dict
                        # (deleted_dict was None), or we've reached an empty
                        # child dir in the dict, so are now a sub-path.
                        break
                else:
                    deleted_dict = None
                if deleted_dict is not None:
                    # the path has a deleted parent, do not add it.
                    continue
            if exclude and is_inside_any(exclude, path):
                # Skip excluded paths. Excluded paths are processed by
                # _update_builder_with_changes.
                continue
            content_summary = self.work_tree.path_content_summary(path)
            kind = content_summary[0]
            # Note that when a filter of specific files is given, we must only
            # skip/record deleted files matching that filter.
            if not specific_files or is_inside_any(specific_files, path):
                if kind == 'missing':
                    if not deleted_paths:
                        # path won't have been split yet.
                        path_segments = splitpath(path)
                    deleted_dict = deleted_paths
                    for segment in path_segments:
                        deleted_dict = deleted_dict.setdefault(segment, {})
                    self.reporter.missing(path)
                    self._next_progress_entry()
                    deleted_ids.append(file_id)
                    continue
            # TODO: have the builder do the nested commit just-in-time IF and
            # only if needed.
            if kind == 'tree-reference':
                # enforce repository nested tree policy.
                if (not self.work_tree.supports_tree_reference() or
                        # repository does not support it either.
                        not self.branch.repository._format.
                        supports_tree_reference):
                    kind = 'directory'
                    content_summary = (kind, None, None, None)
                elif self.recursive == 'down':
                    nested_revision_id = self._commit_nested_tree(
                        file_id, path)
                    content_summary = (kind, None, None, nested_revision_id)
                else:
                    nested_revision_id = self.work_tree.get_reference_revision(
                        file_id)
                    content_summary = (kind, None, None, nested_revision_id)

            # Record an entry for this item
            # Note: I don't particularly want to have the existing_ie
            # parameter but the test suite currently (28-Jun-07) breaks
            # without it thanks to a unicode normalisation issue. :-(
            definitely_changed = kind != existing_ie.kind
            self._record_entry(path, file_id, specific_files, kind, name,
                               parent_id, definitely_changed, existing_ie,
                               report_changes, content_summary)

        # Unversion IDs that were found to be deleted
        self.deleted_ids = deleted_ids