Skip to content

Commit 126d91c

Browse files
committed
Fetching any submodule in a subproject with submodules
Fixes #1013
1 parent d8017f5 commit 126d91c

6 files changed

Lines changed: 70 additions & 9 deletions

File tree

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
Release 0.13.0 (unreleased)
2+
====================================
3+
4+
* Fetch git submodules in git subproject at pinned revision (#1015)
5+
16
Release 0.12.1 (released 2026-02-24)
27
====================================
38

dfetch/project/gitsubproject.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from dfetch.manifest.project import ProjectEntry
99
from dfetch.manifest.version import Version
1010
from dfetch.project.subproject import SubProject
11-
from dfetch.util.util import safe_rmtree
11+
from dfetch.util.util import safe_rm, safe_rmtree
1212
from dfetch.vcs.git import GitLocalRepo, GitRemote, get_git_version
1313

1414
logger = get_logger(__name__)
@@ -69,15 +69,22 @@ def _fetch_impl(self, version: Version) -> Version:
6969
]
7070

7171
local_repo = GitLocalRepo(self.local_path)
72-
fetched_sha = local_repo.checkout_version(
72+
fetched_sha, submodules = local_repo.checkout_version(
7373
remote=self.remote,
7474
version=rev_or_branch_or_tag,
7575
src=self.source,
76-
must_keeps=license_globs,
76+
must_keeps=license_globs + [".gitmodules"],
7777
ignore=self.ignore,
7878
)
7979

80+
for submodule in submodules:
81+
self._log_project(
82+
f'Found & fetched submodule "./{submodule.path}" '
83+
f" ({submodule.url} @ {Version(tag=submodule.tag, branch=submodule.branch, revision=submodule.sha)})",
84+
)
85+
8086
safe_rmtree(os.path.join(self.local_path, local_repo.METADATA_DIR))
87+
safe_rm(os.path.join(self.local_path, local_repo.GIT_MODULES_FILE))
8188

8289
return self._determine_fetched_version(version, fetched_sha)
8390

dfetch/util/util.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ def find_matching_files(directory: str, patterns: Sequence[str]) -> Iterator[Pat
4444

4545
def safe_rm(path: str | Path) -> None:
4646
"""Delete an file or directory safely."""
47-
if os.path.isdir(path):
48-
safe_rmtree(str(path))
49-
else:
50-
os.remove(path)
47+
if os.path.exists(path):
48+
if os.path.isdir(path):
49+
safe_rmtree(str(path))
50+
else:
51+
os.remove(path)
5152

5253

5354
def safe_rmtree(path: str) -> None:

dfetch/vcs/git.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ class GitLocalRepo:
233233
"""A git repository."""
234234

235235
METADATA_DIR = ".git"
236+
GIT_MODULES_FILE = ".gitmodules"
236237

237238
def __init__(self, path: str | Path = ".") -> None:
238239
"""Create a local git repo."""
@@ -258,7 +259,7 @@ def checkout_version( # pylint: disable=too-many-arguments
258259
src: str | None = None,
259260
must_keeps: list[str] | None = None,
260261
ignore: Sequence[str] | None = None,
261-
) -> str:
262+
) -> tuple[str, list[Submodule]]:
262263
"""Checkout a specific version from a given remote.
263264
264265
Args:
@@ -295,6 +296,14 @@ def checkout_version( # pylint: disable=too-many-arguments
295296
)
296297
run_on_cmdline(logger, ["git", "reset", "--hard", "FETCH_HEAD"])
297298

299+
run_on_cmdline(
300+
logger,
301+
["git", "submodule", "update", "--init", "--recursive"],
302+
env=_extend_env_for_non_interactive_mode(),
303+
)
304+
305+
submodules = self.submodules()
306+
298307
current_sha = (
299308
run_on_cmdline(logger, ["git", "rev-parse", "HEAD"])
300309
.stdout.decode()
@@ -304,7 +313,7 @@ def checkout_version( # pylint: disable=too-many-arguments
304313
if src:
305314
self.move_src_folder_up(remote, src)
306315

307-
return str(current_sha)
316+
return str(current_sha), submodules
308317

309318
def move_src_folder_up(self, remote: str, src: str) -> None:
310319
"""Move the files from the src folder into the root of the project.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Feature: Fetch projects with nested VCS dependencies
2+
3+
Some projects include nested version control dependencies
4+
such as Git submodules or other externals
5+
These dependencies must be fetched at the exact revision
6+
pinned by the parent repository to ensure reproducibility
7+
8+
Scenario: A project with a git submodule is fetched at the pinned revision
9+
Given the manifest 'dfetch.yaml' in MyProject
10+
"""
11+
manifest:
12+
version: 0.0
13+
projects:
14+
- name: TF-PSA-Crypto
15+
url: https://github.com/Mbed-TLS/TF-PSA-Crypto.git
16+
tag: v1.0.0
17+
dst: ext/TF-PSA-Crypto
18+
ignore:
19+
- tests
20+
- scripts
21+
- programs
22+
- drivers
23+
- doxygen
24+
- docs
25+
"""
26+
When I run "dfetch update"
27+
Then the output shows
28+
"""
29+
Dfetch (0.12.1)
30+
TF-PSA-Crypto:
31+
> Found & fetched submodule "./framework" (https://github.com/Mbed-TLS/mbedtls-framework @ mbedtls-4.0.0_tf-psa-crypto-1.0.0)
32+
> Fetched v1.0.0
33+
"""
34+
Then 'MyProject/ext/TF-PSA-Crypto/framework/LICENSE' exists

features/steps/generic_steps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ def step_impl(context, name):
330330
check_file(name, context.text)
331331

332332

333+
@then("'{name}' exists")
334+
def step_impl(_, name):
335+
check_file_exists(name)
336+
337+
333338
def multisub(patterns: List[Tuple[Pattern[str], str]], text: str) -> str:
334339
"""Apply a list of tuples that each contain a regex + replace string."""
335340
for pattern, replace in patterns:

0 commit comments

Comments
 (0)