Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions git/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,11 +549,21 @@ def _included_paths(self) -> List[Tuple[str, str]]:
:return:
The list of paths, where each path is a tuple of (option, value).
"""

def _all_items(section: str) -> List[Tuple[str, str]]:
"""Return all (key, value) pairs for a section, including duplicate keys."""
return [
(key, value)
for key, values in self._sections[section].items_all()
if key != "__name__"
for value in values
]

paths = []

for section in self.sections():
if section == "include":
paths += self.items(section)
paths += _all_items(section)

match = CONDITIONAL_INCLUDE_REGEXP.search(section)
if match is None or self._repo is None:
Expand All @@ -579,7 +589,7 @@ def _included_paths(self) -> List[Tuple[str, str]]:
)
if self._repo.git_dir:
if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
paths += self.items(section)
paths += _all_items(section)

elif keyword == "onbranch":
try:
Expand All @@ -589,11 +599,11 @@ def _included_paths(self) -> List[Tuple[str, str]]:
continue

if fnmatch.fnmatchcase(branch_name, value):
paths += self.items(section)
paths += _all_items(section)
elif keyword == "hasconfig:remote.*.url":
for remote in self._repo.remotes:
if fnmatch.fnmatchcase(remote.url, value):
paths += self.items(section)
paths += _all_items(section)
break
return paths

Expand Down
37 changes: 37 additions & 0 deletions test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,43 @@ def check_test_value(cr, value):
with GitConfigParser(fpa, read_only=True) as cr:
check_test_value(cr, tv)

@with_rw_directory
def test_multiple_include_paths_with_same_key(self, rw_dir):
"""Test that multiple 'path' entries under [include] are all respected.

Regression test for https://github.com/gitpython-developers/GitPython/issues/2099.
Git config allows multiple ``path`` values under ``[include]``, e.g.::

[include]
path = file1
path = file2

Previously only one of these was included because _OMD.items() returns
only the last value for each key.
"""
# Create two config files to be included.
fp_inc1 = osp.join(rw_dir, "inc1.cfg")
fp_inc2 = osp.join(rw_dir, "inc2.cfg")
fp_main = osp.join(rw_dir, "main.cfg")

with GitConfigParser(fp_inc1, read_only=False) as cw:
cw.set_value("user", "name", "from-inc1")

with GitConfigParser(fp_inc2, read_only=False) as cw:
cw.set_value("core", "bar", "from-inc2")

# Write a config with two path entries under a single [include] section.
# We write it manually because set_value would overwrite the key.
with open(fp_main, "w") as f:
f.write("[include]\n")
f.write(f"\tpath = {fp_inc1}\n")
f.write(f"\tpath = {fp_inc2}\n")

with GitConfigParser(fp_main, read_only=True) as cr:
# Both included files should be loaded.
assert cr.get_value("user", "name") == "from-inc1"
assert cr.get_value("core", "bar") == "from-inc2"

@pytest.mark.xfail(
sys.platform == "win32",
reason='Second config._has_includes() assertion fails (for "config is included if path is matching git_dir")',
Expand Down
Loading