diff --git a/git/config.py b/git/config.py index 732d3658b..8d011ec8a 100644 --- a/git/config.py +++ b/git/config.py @@ -730,7 +730,12 @@ def _all_items(section: str) -> List[Tuple[str, str]]: value = match.group(2).strip() if keyword in ["gitdir", "gitdir/i"]: - value = osp.expanduser(value) + if sys.platform == "win32" and (value == "~" or value.startswith("~/")): + # Like Git, substitute the home directory with forward slashes, as a + # backslash in the pattern would escape the next character. + value = osp.expanduser("~").replace("\\", "/") + value[1:] + else: + value = osp.expanduser(value) git_dir = os.fspath(self._repo.git_dir) if self._repo.git_dir else None if sys.platform == "win32": git_dir = git_dir.replace("\\", "/") if git_dir else None diff --git a/test/test_config.py b/test/test_config.py index 49b8535c7..f7907e98e 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -989,6 +989,22 @@ def test_conditional_includes_do_not_treat_backslashes_as_separators(self, rw_di with GitConfigParser(config_path, repo=repo, merge_includes=False) as config: assert config._included_paths() == [] + @with_rw_directory + def test_conditional_includes_expand_home_in_git_dir_pattern(self, rw_dir): + git_dir = osp.join(rw_dir, "target", "repo", ".git") + repo = mock.Mock(git_dir=git_dir) + config_path = osp.join(rw_dir, "config") + included_path = osp.join(rw_dir, "included") + + with open(config_path, "w") as stream: + stream.write(f'[includeIf "gitdir:~/target/"]\n path={included_path}\n') + + # On Windows, the home directory has backslashes, but Git matches it as if it + # were written with forward slashes. + with mock.patch.dict(os.environ, {"HOME": rw_dir, "USERPROFILE": rw_dir}): + with GitConfigParser(config_path, repo=repo, merge_includes=False) as config: + assert config._included_paths() == [("path", included_path)] + @with_rw_directory def test_conditional_includes_from_branch_name(self, rw_dir): # Initiate mocked branch.