diff --git a/git/config.py b/git/config.py index c6eaf8f7b..b60df2ca8 100644 --- a/git/config.py +++ b/git/config.py @@ -32,6 +32,7 @@ List, Dict, Sequence, + Set, TYPE_CHECKING, Tuple, TypeVar, @@ -631,11 +632,17 @@ def read(self) -> None: # type: ignore[override] files_to_read = list(self._file_or_files) # END ensure we have a copy of the paths to handle - seen = set(files_to_read) + def path_key(file_path: Union[PathLike, IO]) -> Union[str, IO]: + if isinstance(file_path, (str, os.PathLike)): + return osp.normpath(osp.abspath(file_path)) + return file_path + + seen: Set[Union[str, IO]] = {path_key(file_path) for file_path in files_to_read} num_read_include_files = 0 while files_to_read: file_path = files_to_read.pop(0) file_ok = False + abs_file_path: Union[str, None] = None if hasattr(file_path, "seek"): # Must be a file-object. @@ -644,6 +651,7 @@ def read(self) -> None: # type: ignore[override] self._read(file_path, file_path.name) else: try: + abs_file_path = osp.normpath(osp.abspath(file_path)) with open(file_path, "rb") as fp: file_ok = True self._read(fp, fp.name) @@ -660,9 +668,8 @@ def read(self) -> None: # type: ignore[override] 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) + assert abs_file_path is not None, "Need a source path to resolve relative include paths" + include_path = osp.join(osp.dirname(abs_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): diff --git a/test/test_config.py b/test/test_config.py index 11ea52d16..eb7ec27f2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -246,6 +246,24 @@ def check_test_value(cr, value): with GitConfigParser(fpa, read_only=True) as cr: check_test_value(cr, tv) + @with_rw_directory + def test_config_include_from_relative_config_path(self, rw_dir): + fpa = osp.join(rw_dir, "a") + fpb = osp.join(rw_dir, "b") + + with GitConfigParser(fpa, read_only=False) as cw: + cw.set_value("a", "value", "a") + cw.set_value("include", "path", "b") + + with GitConfigParser(fpb, read_only=False) as cw: + cw.set_value("b", "value", "b") + cw.set_value("include", "path", "a") + + with GitConfigParser(osp.relpath(fpa), read_only=True) as cr: + assert cr.get_value("a", "value") == "a" + assert cr.get_value("b", "value") == "b" + assert cr.get_values("include", "path") == ["b", "a"] + @with_rw_directory def test_multiple_include_paths_with_same_key(self, rw_dir): """Test that multiple 'path' entries under [include] are all respected.