def read(self) -> None:  # type: ignore[override]
        """Read the data stored in the files we have been initialized with.

        This will ignore files that cannot be read, possibly leaving an empty
        configuration.

        :raise IOError:
            If a file cannot be handled.
        """
        if self._is_initialized:
            return
        self._is_initialized = True

        files_to_read: List[Union[PathLike, IO]] = [""]
        if isinstance(self._file_or_files, (str, os.PathLike)):
            # For str or Path, as str is a type of Sequence.
            files_to_read = [self._file_or_files]
        elif not isinstance(self._file_or_files, (tuple, list, Sequence)):
            # Could merge with above isinstance once runtime type known.
            files_to_read = [self._file_or_files]
        else:  # For lists or tuples.
            files_to_read = list(self._file_or_files)
        # END ensure we have a copy of the paths to handle

        seen = set(files_to_read)
        num_read_include_files = 0
        while files_to_read:
            file_path = files_to_read.pop(0)
            file_ok = False

            if hasattr(file_path, "seek"):
                # Must be a file-object.
                # TODO: Replace cast with assert to narrow type, once sure.
                file_path = cast(IO[bytes], file_path)
                self._read(file_path, file_path.name)
            else:
                # Assume a path if it is not a file-object.
                file_path = cast(PathLike, file_path)
                try:
                    with open(file_path, "rb") as fp:
                        file_ok = True
                        self._read(fp, fp.name)
                except IOError:
                    continue

            # Read includes and append those that we didn't handle yet. We expect all
            # paths to be normalized and absolute (and will ensure that is the case).
            if self._has_includes():
                for _, include_path in self._included_paths():
                    if include_path.startswith("~"):
                        include_path = osp.expanduser(include_path)
                    if not osp.isabs(include_path):
                        if not file_ok:
                            continue
                        # END ignore relative paths if we don't know the configuration file path
                        file_path = cast(PathLike, file_path)
                        assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work"
                        include_path = osp.join(osp.dirname(file_path), include_path)
                    # END make include path absolute
                    include_path = osp.normpath(include_path)
                    if include_path in seen or not os.access(include_path, os.R_OK):
                        continue
                    seen.add(include_path)
                    # Insert included file to the top to be considered first.
                    files_to_read.insert(0, include_path)
                    num_read_include_files += 1
                # END each include path in configuration file
            # END handle includes
        # END for each file object to read

        # If there was no file included, we can safely write back (potentially) the
        # configuration file without altering its meaning.
        if num_read_include_files == 0:
            self._merge_includes = False
Read the data stored in the files we have been initialized with.

        This will ignore files that cannot be read, possibly leaving an empty
        configuration.

        :raise IOError:
            If a file cannot be handled.
        
git/config.py