diff --git a/command.py b/command.py index 7da063a25..bfafcd449 100644 --- a/command.py +++ b/command.py @@ -17,6 +17,7 @@ import multiprocessing import optparse import os import re +from typing import TYPE_CHECKING from error import InvalidProjectGroupsError from error import NoSuchProjectError @@ -25,6 +26,10 @@ from event_log import EventLog import progress +if TYPE_CHECKING: + from project import Project + + # Are we generating man-pages? GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! " @@ -375,7 +380,7 @@ class Command: manifest=None, groups="", missing_ok=False, - submodules_ok=False, + submodules_ok=None, all_manifests=False, ): """A list of projects that match the arguments. @@ -385,7 +390,9 @@ class Command: manifest: an XmlManifest, the manifest to use, or None for default. groups: a string, the manifest groups in use. missing_ok: a boolean, whether to allow missing projects. - submodules_ok: a boolean, whether to allow submodules. + submodules_ok: whether to allow submodules. True allows them for + all projects, False disallows them for all projects, and None + defers to each project's sync-s setting. all_manifests: a boolean, if True then all manifests and submanifests are used. If False, then only the local (sub)manifest is used. @@ -403,6 +410,11 @@ class Command: all_projects_list = manifest.projects result = [] + def should_include_submodules(project: "Project") -> bool: + if submodules_ok is None: + return project.sync_s + return submodules_ok + if not groups: groups = manifest.GetManifestGroupsStr() groups = [x for x in re.split(r"[,\s]+", groups) if x] @@ -410,7 +422,7 @@ class Command: if not args: derived_projects = {} for project in all_projects_list: - if submodules_ok or project.sync_s: + if should_include_submodules(project): derived_projects.update( (p.RelPath(local=False), p) for p in project.GetDerivedSubprojects() @@ -452,7 +464,7 @@ class Command: if ( project and not project.Derived - and (submodules_ok or project.sync_s) + and should_include_submodules(project) ): search_again = False for subproject in project.GetDerivedSubprojects(): diff --git a/completion.zsh b/completion.zsh index 507ed2bb2..8c0322f77 100644 --- a/completion.zsh +++ b/completion.zsh @@ -323,6 +323,7 @@ _repo() { '(-u --manifest-server-username)'{-u,--manifest-server-username=}'[Username for manifest server]:username:' \ '(-p --manifest-server-password)'{-p,--manifest-server-password=}'[Password for manifest server]:password:' \ '--fetch-submodules[Fetch submodules]' \ + '--no-fetch-submodules[Do not fetch submodules]' \ '--use-superproject[Use superproject]' \ '--no-use-superproject[Do not use superproject]' \ '--tags[Sync tags]' \ diff --git a/man/repo-smartsync.1 b/man/repo-smartsync.1 index ef4c112df..07bab536b 100644 --- a/man/repo-smartsync.1 +++ b/man/repo-smartsync.1 @@ -83,6 +83,9 @@ password to authenticate with the manifest server \fB\-\-fetch\-submodules\fR fetch submodules from server .TP +\fB\-\-no\-fetch\-submodules\fR +don't fetch submodules from server +.TP \fB\-\-use\-superproject\fR use the manifest superproject to sync projects; implies \fB\-c\fR .TP diff --git a/man/repo-sync.1 b/man/repo-sync.1 index 43d959500..68d1e5e6a 100644 --- a/man/repo-sync.1 +++ b/man/repo-sync.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man. -.TH REPO "1" "June 2026" "repo sync" "Repo Manual" +.TH REPO "1" "July 2026" "repo sync" "Repo Manual" .SH NAME repo \- repo sync - manual page for repo sync .SH SYNOPSIS @@ -83,6 +83,9 @@ password to authenticate with the manifest server \fB\-\-fetch\-submodules\fR fetch submodules from server .TP +\fB\-\-no\-fetch\-submodules\fR +don't fetch submodules from server +.TP \fB\-\-use\-superproject\fR use the manifest superproject to sync projects; implies \fB\-c\fR .TP @@ -212,8 +215,9 @@ bootstrap a new Git repository from a resumeable bundle file on a content delivery network. This may be necessary if there are problems with the local Python HTTP client or proxy configuration, but the Git binary works. .PP -The \fB\-\-fetch\-submodules\fR option enables fetching Git submodules of a project from -server. +The \fB\-\-fetch\-submodules\fR option enables fetching Git submodules of all projects +from the server. The \fB\-\-no\-fetch\-submodules\fR option disables fetching Git +submodules, even when a project has sync\-s="true" in the manifest. .PP The \fB\-c\fR/\-\-current\-branch option can be used to only fetch objects that are on the branch specified by a project's revision. diff --git a/subcmds/sync.py b/subcmds/sync.py index 2b9cafe2d..5a635fa97 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -379,7 +379,8 @@ may be necessary if there are problems with the local Python HTTP client or proxy configuration, but the Git binary works. The --fetch-submodules option enables fetching Git submodules -of a project from server. +of all projects from the server. The --no-fetch-submodules option disables +fetching Git submodules, even when a project has sync-s="true" in the manifest. The -c/--current-branch option can be used to only fetch objects that are on the branch specified by a project's revision. @@ -573,6 +574,12 @@ later is required to fix a server side protocol bug. action="store_true", help="fetch submodules from server", ) + p.add_option( + "--no-fetch-submodules", + dest="fetch_submodules", + action="store_false", + help="don't fetch submodules from server", + ) p.add_option( "--use-superproject", action="store_true", diff --git a/tests/test_command.py b/tests/test_command.py index bd2a47f71..d7938c5f2 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -14,6 +14,8 @@ """Unittests for the command.py module.""" +import pytest + from command import Command @@ -86,3 +88,32 @@ def test_get_projects_keeps_derived_subprojects_for_repeated_repo(): projects = cmd.GetProjects([]) assert set(projects) == {project_a, project_b, submodule_a, submodule_b} + + +@pytest.mark.parametrize( + "submodules_ok, sync_s, includes_submodule", + [ + (None, False, False), + (None, True, True), + (True, False, True), + (True, True, True), + (False, False, False), + (False, True, False), + ], +) +def test_get_projects_submodule_override( + submodules_ok, sync_s, includes_submodule +): + """The CLI override takes precedence over a project's sync-s setting.""" + submodule = FakeProject("submodule", "project/submodule") + project = FakeProject( + "project", + "project", + derived_subprojects=[submodule], + sync_s=sync_s, + ) + cmd = Command(manifest=FakeManifest([project])) + + projects = cmd.GetProjects([], submodules_ok=submodules_ok) + + assert (submodule in projects) is includes_submodule diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index f8588465c..1fb462a60 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py @@ -30,6 +30,23 @@ from project import SyncNetworkHalfResult from subcmds import sync +@pytest.mark.parametrize( + "cli_args, expected", + [ + ([], None), + (["--fetch-submodules"], True), + (["--no-fetch-submodules"], False), + ], +) +def test_fetch_submodules_option(cli_args, expected): + """The fetch-submodules flags preserve an unset manifest-driven state.""" + cmd = sync.Sync() + + opts, _ = cmd.OptionParser.parse_args(cli_args) + + assert opts.fetch_submodules is expected + + @pytest.mark.parametrize( "use_superproject, cli_args, result", [