Skip to content

Commit 424760d

Browse files
config: resolve relative includes from relative config paths
Normalize path-based config inputs before using them for include cycle checks. This lets GitConfigParser resolve relative include.path values when the root config file was provided as a relative path, without re-reading the same config under a different spelling. Add regression coverage with a relative root config and a relative include cycle.
1 parent 5937d14 commit 424760d

File tree

2 files changed

+29
-4
lines changed

2 files changed

+29
-4
lines changed

git/config.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
List,
3333
Dict,
3434
Sequence,
35+
Set,
3536
TYPE_CHECKING,
3637
Tuple,
3738
TypeVar,
@@ -631,11 +632,17 @@ def read(self) -> None: # type: ignore[override]
631632
files_to_read = list(self._file_or_files)
632633
# END ensure we have a copy of the paths to handle
633634

634-
seen = set(files_to_read)
635+
def path_key(file_path: Union[PathLike, IO]) -> Union[str, IO]:
636+
if isinstance(file_path, (str, os.PathLike)):
637+
return osp.normpath(osp.abspath(file_path))
638+
return file_path
639+
640+
seen: Set[Union[str, IO]] = {path_key(file_path) for file_path in files_to_read}
635641
num_read_include_files = 0
636642
while files_to_read:
637643
file_path = files_to_read.pop(0)
638644
file_ok = False
645+
abs_file_path: Union[str, None] = None
639646

640647
if hasattr(file_path, "seek"):
641648
# Must be a file-object.
@@ -644,6 +651,7 @@ def read(self) -> None: # type: ignore[override]
644651
self._read(file_path, file_path.name)
645652
else:
646653
try:
654+
abs_file_path = osp.normpath(osp.abspath(file_path))
647655
with open(file_path, "rb") as fp:
648656
file_ok = True
649657
self._read(fp, fp.name)
@@ -660,9 +668,8 @@ def read(self) -> None: # type: ignore[override]
660668
if not file_ok:
661669
continue
662670
# END ignore relative paths if we don't know the configuration file path
663-
file_path = cast(PathLike, file_path)
664-
assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work"
665-
include_path = osp.join(osp.dirname(file_path), include_path)
671+
assert abs_file_path is not None, "Need a source path to resolve relative include paths"
672+
include_path = osp.join(osp.dirname(abs_file_path), include_path)
666673
# END make include path absolute
667674
include_path = osp.normpath(include_path)
668675
if include_path in seen or not os.access(include_path, os.R_OK):

test/test_config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,24 @@ def check_test_value(cr, value):
246246
with GitConfigParser(fpa, read_only=True) as cr:
247247
check_test_value(cr, tv)
248248

249+
@with_rw_directory
250+
def test_config_include_from_relative_config_path(self, rw_dir):
251+
fpa = osp.join(rw_dir, "a")
252+
fpb = osp.join(rw_dir, "b")
253+
254+
with GitConfigParser(fpa, read_only=False) as cw:
255+
cw.set_value("a", "value", "a")
256+
cw.set_value("include", "path", "b")
257+
258+
with GitConfigParser(fpb, read_only=False) as cw:
259+
cw.set_value("b", "value", "b")
260+
cw.set_value("include", "path", "a")
261+
262+
with GitConfigParser(osp.relpath(fpa), read_only=True) as cr:
263+
assert cr.get_value("a", "value") == "a"
264+
assert cr.get_value("b", "value") == "b"
265+
assert cr.get_values("include", "path") == ["b", "a"]
266+
249267
@with_rw_directory
250268
def test_multiple_include_paths_with_same_key(self, rw_dir):
251269
"""Test that multiple 'path' entries under [include] are all respected.

0 commit comments

Comments
 (0)