11
11
from datetime import date
12
12
from datetime import datetime
13
13
from datetime import timezone
14
+ from enum import Enum
14
15
from os .path import samefile
15
16
from pathlib import Path
16
17
from typing import TYPE_CHECKING
52
53
]
53
54
54
55
56
+ class GitPreParse (Enum ):
57
+ """Available git pre-parse functions"""
58
+
59
+ WARN_ON_SHALLOW = "warn_on_shallow"
60
+ FAIL_ON_SHALLOW = "fail_on_shallow"
61
+ FETCH_ON_SHALLOW = "fetch_on_shallow"
62
+ FAIL_ON_MISSING_SUBMODULES = "fail_on_missing_submodules"
63
+
64
+
55
65
def run_git (
56
66
args : Sequence [str | os .PathLike [str ]],
57
67
repo : Path ,
@@ -209,6 +219,65 @@ def fail_on_shallow(wd: GitWorkdir) -> None:
209
219
)
210
220
211
221
222
+ def fail_on_missing_submodules (wd : GitWorkdir ) -> None :
223
+ """
224
+ Fail if submodules are defined but not initialized/cloned.
225
+
226
+ This pre_parse function checks if there are submodules defined in .gitmodules
227
+ but not properly initialized (cloned). This helps prevent packaging incomplete
228
+ projects when submodules are required for a complete build.
229
+ """
230
+ gitmodules_path = wd .path / ".gitmodules"
231
+ if not gitmodules_path .exists ():
232
+ # No submodules defined, nothing to check
233
+ return
234
+
235
+ # Get submodule status - lines starting with '-' indicate uninitialized submodules
236
+ status_result = run_git (["submodule" , "status" ], wd .path )
237
+ if status_result .returncode != 0 :
238
+ # Command failed, might not be in a git repo or other error
239
+ log .debug ("Failed to check submodule status: %s" , status_result .stderr )
240
+ return
241
+
242
+ status_lines = (
243
+ status_result .stdout .strip ().split ("\n " ) if status_result .stdout .strip () else []
244
+ )
245
+ uninitialized_submodules = []
246
+
247
+ for line in status_lines :
248
+ line = line .strip ()
249
+ if line .startswith ("-" ):
250
+ # Extract submodule path (everything after the commit hash)
251
+ parts = line .split ()
252
+ if len (parts ) >= 2 :
253
+ submodule_path = parts [1 ]
254
+ uninitialized_submodules .append (submodule_path )
255
+
256
+ # If .gitmodules exists but git submodule status returns nothing,
257
+ # it means submodules are defined but not properly set up (common after cloning without --recurse-submodules)
258
+ if not status_lines and gitmodules_path .exists ():
259
+ raise ValueError (
260
+ f"Submodules are defined in .gitmodules but not initialized in { wd .path } . "
261
+ f"Please run 'git submodule update --init --recursive' to initialize them."
262
+ )
263
+
264
+ if uninitialized_submodules :
265
+ submodule_list = ", " .join (uninitialized_submodules )
266
+ raise ValueError (
267
+ f"Submodules are not initialized in { wd .path } : { submodule_list } . "
268
+ f"Please run 'git submodule update --init --recursive' to initialize them."
269
+ )
270
+
271
+
272
+ # Mapping from enum items to actual pre_parse functions
273
+ _GIT_PRE_PARSE_FUNCTIONS : dict [GitPreParse , Callable [[GitWorkdir ], None ]] = {
274
+ GitPreParse .WARN_ON_SHALLOW : warn_on_shallow ,
275
+ GitPreParse .FAIL_ON_SHALLOW : fail_on_shallow ,
276
+ GitPreParse .FETCH_ON_SHALLOW : fetch_on_shallow ,
277
+ GitPreParse .FAIL_ON_MISSING_SUBMODULES : fail_on_missing_submodules ,
278
+ }
279
+
280
+
212
281
def get_working_directory (config : Configuration , root : _t .PathT ) -> GitWorkdir | None :
213
282
"""
214
283
Return the working directory (``GitWorkdir``).
@@ -231,16 +300,26 @@ def parse(
231
300
root : _t .PathT ,
232
301
config : Configuration ,
233
302
describe_command : str | list [str ] | None = None ,
234
- pre_parse : Callable [[GitWorkdir ], None ] = warn_on_shallow ,
303
+ pre_parse : Callable [[GitWorkdir ], None ] | None = None ,
235
304
) -> ScmVersion | None :
236
305
"""
237
- :param pre_parse: experimental pre_parse action, may change at any time
306
+ :param pre_parse: experimental pre_parse action, may change at any time.
307
+ Takes precedence over config.git_pre_parse if provided.
238
308
"""
239
309
_require_command ("git" )
240
310
wd = get_working_directory (config , root )
241
311
if wd :
312
+ # Use function parameter first, then config setting, then default
313
+ if pre_parse is not None :
314
+ effective_pre_parse = pre_parse
315
+ else :
316
+ # config.scm.git.pre_parse is always a GitPreParse enum instance
317
+ effective_pre_parse = _GIT_PRE_PARSE_FUNCTIONS .get (
318
+ config .scm .git .pre_parse , warn_on_shallow
319
+ )
320
+
242
321
return _git_parse_inner (
243
- config , wd , describe_command = describe_command , pre_parse = pre_parse
322
+ config , wd , describe_command = describe_command , pre_parse = effective_pre_parse
244
323
)
245
324
else :
246
325
return None
0 commit comments