1111from datetime import date
1212from datetime import datetime
1313from datetime import timezone
14+ from enum import Enum
1415from os .path import samefile
1516from pathlib import Path
1617from typing import TYPE_CHECKING
5253]
5354
5455
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+
5565def run_git (
5666 args : Sequence [str | os .PathLike [str ]],
5767 repo : Path ,
@@ -209,6 +219,65 @@ def fail_on_shallow(wd: GitWorkdir) -> None:
209219 )
210220
211221
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+
212281def get_working_directory (config : Configuration , root : _t .PathT ) -> GitWorkdir | None :
213282 """
214283 Return the working directory (``GitWorkdir``).
@@ -231,16 +300,26 @@ def parse(
231300 root : _t .PathT ,
232301 config : Configuration ,
233302 describe_command : str | list [str ] | None = None ,
234- pre_parse : Callable [[GitWorkdir ], None ] = warn_on_shallow ,
303+ pre_parse : Callable [[GitWorkdir ], None ] | None = None ,
235304) -> ScmVersion | None :
236305 """
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.
238308 """
239309 _require_command ("git" )
240310 wd = get_working_directory (config , root )
241311 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+
242321 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
244323 )
245324 else :
246325 return None
0 commit comments