1414
1515from git import GitConfigParser
1616from git .config import _OMD , cp
17- from git .util import rmfile
17+ from git .util import cwd , rmfile
1818
1919from test .lib import SkipTest , TestCase , fixture_path , with_rw_directory
2020
@@ -374,6 +374,22 @@ def test_config_relative_path_include(self, rw_dir):
374374 with GitConfigParser (relative_config_path , read_only = True ) as cr :
375375 assert cr .get_value ("included" , "value" ) == "included"
376376
377+ @pytest .mark .skipif (os .name != "nt" , reason = "Specifically for Windows drive-rooted paths." )
378+ @with_rw_directory
379+ def test_config_drive_rooted_path_include (self , rw_dir ):
380+ with cwd (rw_dir ):
381+ included_path = osp .join (rw_dir , "included" )
382+ with GitConfigParser (included_path , read_only = False ) as cw :
383+ cw .set_value ("included" , "value" , "included" )
384+
385+ _drive , rooted_included_path = osp .splitdrive (included_path )
386+ config_path = osp .join (rw_dir , "config" )
387+ with GitConfigParser (config_path , read_only = False ) as cw :
388+ cw .set_value ("include" , "path" , rooted_included_path )
389+
390+ with GitConfigParser (config_path , read_only = True ) as cr :
391+ assert cr .get_value ("included" , "value" ) == "included"
392+
377393 @with_rw_directory
378394 def test_multiple_include_paths_with_same_key (self , rw_dir ):
379395 """Test that multiple 'path' entries under [include] are all respected.
@@ -411,15 +427,11 @@ def test_multiple_include_paths_with_same_key(self, rw_dir):
411427 assert cr .get_value ("user" , "name" ) == "from-inc1"
412428 assert cr .get_value ("core" , "bar" ) == "from-inc2"
413429
414- @pytest .mark .xfail (
415- sys .platform == "win32" ,
416- reason = 'Second config._has_includes() assertion fails (for "config is included if path is matching git_dir")' ,
417- raises = AssertionError ,
418- )
419430 @with_rw_directory
420431 def test_conditional_includes_from_git_dir (self , rw_dir ):
421432 # Initiate repository path.
422433 git_dir = osp .join (rw_dir , "target1" , "repo1" )
434+ git_dir_pattern = git_dir .replace ("\\ " , "/" )
423435 os .makedirs (git_dir )
424436
425437 # Initiate mocked repository.
@@ -431,29 +443,42 @@ def test_conditional_includes_from_git_dir(self, rw_dir):
431443 template = '[includeIf "{}:{}"]\n path={}\n '
432444
433445 with open (path1 , "w" ) as stream :
446+ # on Windows, this writes a backslash pattern.
434447 stream .write (template .format ("gitdir" , git_dir , path2 ))
435448
436449 # Ensure that config is ignored if no repo is set.
437450 with GitConfigParser (path1 ) as config :
438451 assert not config ._has_includes ()
439452 assert config ._included_paths () == []
440453
441- # Ensure that config is included if path is matching git_dir.
454+ # Git uses forward slashes in gitdir patterns on every platform:
455+ # backslashes escape the next pattern character rather than separate
456+ # path components. On Windows, GitPython therefore normalizes git_dir
457+ # to forward slashes but leaves this backslash pattern unchanged, so
458+ # the two do not match and no path is included.
459+ with GitConfigParser (path1 , repo = repo , merge_includes = False ) as config :
460+ expected_paths = [] if sys .platform == "win32" else [("path" , path2 )]
461+ assert config ._included_paths () == expected_paths
462+
463+ # Ensure that Git's forward-slash syntax matches native Windows paths.
464+ with open (path1 , "w" ) as stream :
465+ stream .write (template .format ("gitdir" , git_dir_pattern , path2 ))
466+
442467 with GitConfigParser (path1 , repo = repo ) as config :
443468 assert config ._has_includes ()
444469 assert config ._included_paths () == [("path" , path2 )]
445470
446471 # Ensure that config is ignored if case is incorrect.
447472 with open (path1 , "w" ) as stream :
448- stream .write (template .format ("gitdir" , git_dir .upper (), path2 ))
473+ stream .write (template .format ("gitdir" , git_dir_pattern .upper (), path2 ))
449474
450475 with GitConfigParser (path1 , repo = repo ) as config :
451476 assert not config ._has_includes ()
452477 assert config ._included_paths () == []
453478
454479 # Ensure that config is included if case is ignored.
455480 with open (path1 , "w" ) as stream :
456- stream .write (template .format ("gitdir/i" , git_dir .upper (), path2 ))
481+ stream .write (template .format ("gitdir/i" , git_dir_pattern .upper (), path2 ))
457482
458483 with GitConfigParser (path1 , repo = repo ) as config :
459484 assert config ._has_includes ()
@@ -483,6 +508,20 @@ def test_conditional_includes_from_git_dir(self, rw_dir):
483508 assert config ._has_includes ()
484509 assert config ._included_paths () == [("path" , path2 )]
485510
511+ @with_rw_directory
512+ def test_conditional_includes_do_not_treat_backslashes_as_separators (self , rw_dir ):
513+ git_dir = osp .join (rw_dir , "target" , "repo" )
514+ repo = mock .Mock (git_dir = git_dir )
515+ config_path = osp .join (rw_dir , "config" )
516+ included_path = osp .join (rw_dir , "included" )
517+ pattern = git_dir .replace ("\\ " , "/" ).replace ("/target/repo" , R"/target\repo" )
518+
519+ with open (config_path , "w" ) as stream :
520+ stream .write (f'[includeIf "gitdir:{ pattern } "]\n path={ included_path } \n ' )
521+
522+ with GitConfigParser (config_path , repo = repo , merge_includes = False ) as config :
523+ assert config ._included_paths () == []
524+
486525 @with_rw_directory
487526 def test_conditional_includes_from_branch_name (self , rw_dir ):
488527 # Initiate mocked branch.
0 commit comments