@@ -1059,3 +1059,137 @@ def test_update_repo_rev_list_head_failure_returns_sync_result(
10591059 assert result .errors [0 ].step == "rev-list-head"
10601060 assert result .errors [0 ].exception is not None
10611061 assert isinstance (result .errors [0 ].exception , exc .CommandError )
1062+
1063+
1064+ @pytest .mark .xfail (strict = True , reason = "submodule.update not wrapped in try/except" )
1065+ def test_update_repo_submodule_failure_recorded (
1066+ create_git_remote_bare_repo : CreateRepoPytestFixtureFn ,
1067+ tmp_path : pathlib .Path ,
1068+ ) -> None :
1069+ """Test that submodule.update() failure is recorded in SyncResult.
1070+
1071+ When .gitmodules references a non-existent submodule URL, the
1072+ ``git submodule update`` call in update_repo() fails. Currently
1073+ this is not wrapped in try/except, so the exception propagates
1074+ instead of being recorded in SyncResult.
1075+ """
1076+ git_server = create_git_remote_bare_repo ()
1077+ git_repo = GitSync (
1078+ path = tmp_path / "myrepo" ,
1079+ url = git_server .as_uri (),
1080+ )
1081+ git_repo .obtain ()
1082+
1083+ # Make an initial commit and push so update_repo has a valid HEAD
1084+ initial_file = git_repo .path / "initial_file"
1085+ initial_file .write_text ("content" , encoding = "utf-8" )
1086+ git_repo .run (["add" , str (initial_file )])
1087+ git_repo .run (["commit" , "-m" , "initial commit" ])
1088+ git_repo .run (["push" ])
1089+
1090+ # Add a .gitmodules file that references a non-existent submodule URL
1091+ gitmodules = git_repo .path / ".gitmodules"
1092+ gitmodules .write_text (
1093+ '[submodule "broken"]\n \t path = broken\n \t url = file:///nonexistent/repo.git\n ' ,
1094+ encoding = "utf-8" ,
1095+ )
1096+ git_repo .run (["add" , ".gitmodules" ])
1097+ git_repo .run (["commit" , "-m" , "add broken submodule ref" ])
1098+ git_repo .run (["push" ])
1099+
1100+ # Reset behind so update_repo triggers a fetch+checkout cycle
1101+ git_repo .run (["reset" , "--hard" , "HEAD^" ])
1102+
1103+ result = git_repo .update_repo ()
1104+
1105+ assert isinstance (result , SyncResult )
1106+ assert result .ok is False
1107+ assert len (result .errors ) > 0
1108+ assert any (e .step == "submodule-update" for e in result .errors )
1109+
1110+
1111+ @pytest .mark .xfail (strict = True , reason = "symbolic_ref not wrapped in try/except" )
1112+ def test_update_repo_symbolic_ref_failure_recorded (
1113+ create_git_remote_bare_repo : CreateRepoPytestFixtureFn ,
1114+ tmp_path : pathlib .Path ,
1115+ ) -> None :
1116+ """Test that symbolic_ref failure on detached HEAD is recorded in SyncResult.
1117+
1118+ When a repo is in detached HEAD state and no ``rev`` is set,
1119+ ``symbolic_ref --short HEAD`` fails. Currently this is not wrapped
1120+ in try/except, so the exception propagates instead of being
1121+ recorded in SyncResult.
1122+ """
1123+ git_server = create_git_remote_bare_repo ()
1124+ git_repo = GitSync (
1125+ path = tmp_path / "myrepo" ,
1126+ url = git_server .as_uri (),
1127+ )
1128+ git_repo .obtain ()
1129+
1130+ # Make a commit and push so the repo has a valid HEAD
1131+ initial_file = git_repo .path / "initial_file"
1132+ initial_file .write_text ("content" , encoding = "utf-8" )
1133+ git_repo .run (["add" , str (initial_file )])
1134+ git_repo .run (["commit" , "-m" , "initial commit" ])
1135+ git_repo .run (["push" ])
1136+
1137+ # Detach HEAD — symbolic_ref will fail
1138+ head_sha = git_repo .run (["rev-parse" , "HEAD" ]).strip ()
1139+ git_repo .run (["checkout" , head_sha ])
1140+
1141+ # Ensure no rev is set so the code path hits symbolic_ref
1142+ git_repo .rev = None # type: ignore[assignment]
1143+
1144+ result = git_repo .update_repo ()
1145+
1146+ assert isinstance (result , SyncResult )
1147+ assert result .ok is False
1148+ assert len (result .errors ) > 0
1149+ assert any (e .step == "symbolic-ref" for e in result .errors )
1150+
1151+
1152+ @pytest .mark .xfail (
1153+ strict = True ,
1154+ reason = "GitRemoteRefNotFound raised instead of recorded in SyncResult" ,
1155+ )
1156+ def test_update_repo_remote_ref_not_found_recorded (
1157+ create_git_remote_bare_repo : CreateRepoPytestFixtureFn ,
1158+ tmp_path : pathlib .Path ,
1159+ ) -> None :
1160+ """Test that GitRemoteRefNotFound is caught and recorded in SyncResult.
1161+
1162+ When show-ref output contains ``refs/remotes/<tag>`` but the regex
1163+ match fails, ``GitRemoteRefNotFound`` is raised with a bare ``raise``.
1164+ It should instead be caught and recorded in SyncResult.
1165+ """
1166+ from unittest .mock import patch
1167+
1168+ git_server = create_git_remote_bare_repo ()
1169+ git_repo = GitSync (
1170+ path = tmp_path / "myrepo" ,
1171+ url = git_server .as_uri (),
1172+ )
1173+ git_repo .obtain ()
1174+
1175+ # Make a commit and push so the repo has a valid HEAD
1176+ initial_file = git_repo .path / "initial_file"
1177+ initial_file .write_text ("content" , encoding = "utf-8" )
1178+ git_repo .run (["add" , str (initial_file )])
1179+ git_repo .run (["commit" , "-m" , "initial commit" ])
1180+ git_repo .run (["push" ])
1181+
1182+ # Set rev so symbolic_ref is skipped
1183+ git_repo .rev = "master"
1184+
1185+ # Patch show_ref to return output that contains "refs/remotes/master"
1186+ # but in a format that the regex won't match, triggering
1187+ # GitRemoteRefNotFound
1188+ malformed_show_ref = "not-a-sha refs/remotes/master"
1189+ with patch .object (git_repo .cmd , "show_ref" , return_value = malformed_show_ref ):
1190+ result = git_repo .update_repo ()
1191+
1192+ assert isinstance (result , SyncResult )
1193+ assert result .ok is False
1194+ assert len (result .errors ) > 0
1195+ assert any (e .step == "remote-ref-not-found" for e in result .errors )
0 commit comments