Compare commits

...

3 Commits

Author SHA1 Message Date
Josef Malmström 978adb7ea5 sync: Add CLI flag for globally disabling submodule fetch
A global setting for disabling fetching of submodules is useful
since this can currently otherwise only be done by modifying
the manifest, or by explicitly providing projects on command line.

Add this setting as --no-fetch-submodules to mirror the existing
--fetch-submodules.

Change-Id: Ic727c54f11a594aa52315751284b87138cf246bb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/607641
Commit-Queue: Josef Malmstrom <Josef.Malmstrom@arm.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Tested-by: Josef Malmstrom <Josef.Malmstrom@arm.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2026-07-16 01:19:51 -07:00
Brian Gan 0398c6718e color: Replace anonymous sentinel with named class
Replace the bare `object()` sentinel used for `_CHECK_CONSOLE` with an
instance of a dedicated `_CheckConsoleSentinel` class. This gives the
sentinel a meaningful repr and type, making it easier to identify in
debugging output and type checks compared to an opaque `<object>`.

Change-Id: I916521d47ba13207e29a2412e00f3576aff8afff
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/605021
Commit-Queue: Brian Gan <brgan@google.com>
Tested-by: Brian Gan <brgan@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
2026-07-07 14:55:56 -07:00
Gavin Mak 3bb4871c44 rebase: Resolve revisionExpr to tracking branch for --onto-manifest
When running `repo rebase -m` (`--onto-manifest`), the command uses the
raw `revisionExpr` from the manifest (e.g. `main`) directly as the
`--onto` target. This can fail or behave incorrectly if it should
reference the local tracking branch (e.g. `refs/remotes/<remote>/main`).

Resolve `project.revisionExpr` to its local tracking branch using
`project.GetRemote().ToLocal()`. Fall back to using the raw
`revisionExpr` value if the resolution fails (raising a `GitError`).

Bug: 532028666
Change-Id: I4c1bca1374a5842688be227f6aa2afffcdad5397
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/604941
Reviewed-by: Brian Gan <brgan@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
2026-07-07 13:40:52 -07:00
10 changed files with 153 additions and 10 deletions
+6 -1
View File
@@ -84,9 +84,14 @@ def _Color(fg=None, bg=None, attr=None):
DEFAULT = None DEFAULT = None
class _CheckConsoleSentinel:
"""Sentinel for checking console coloring."""
# Placholder value that indicates we need to check if the user is in an # Placholder value that indicates we need to check if the user is in an
# interactive terminal session to determine if we turn on color or not. # interactive terminal session to determine if we turn on color or not.
_CHECK_CONSOLE = object() _CHECK_CONSOLE = _CheckConsoleSentinel()
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-colorui # https://git-scm.com/docs/git-config#Documentation/git-config.txt-colorui
_CONFIG_TO_COLOR_SETTING = { _CONFIG_TO_COLOR_SETTING = {
+16 -4
View File
@@ -17,6 +17,7 @@ import multiprocessing
import optparse import optparse
import os import os
import re import re
from typing import TYPE_CHECKING
from error import InvalidProjectGroupsError from error import InvalidProjectGroupsError
from error import NoSuchProjectError from error import NoSuchProjectError
@@ -25,6 +26,10 @@ from event_log import EventLog
import progress import progress
if TYPE_CHECKING:
from project import Project
# Are we generating man-pages? # Are we generating man-pages?
GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! " GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! "
@@ -375,7 +380,7 @@ class Command:
manifest=None, manifest=None,
groups="", groups="",
missing_ok=False, missing_ok=False,
submodules_ok=False, submodules_ok=None,
all_manifests=False, all_manifests=False,
): ):
"""A list of projects that match the arguments. """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. manifest: an XmlManifest, the manifest to use, or None for default.
groups: a string, the manifest groups in use. groups: a string, the manifest groups in use.
missing_ok: a boolean, whether to allow missing projects. 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 all_manifests: a boolean, if True then all manifests and
submanifests are used. If False, then only the local submanifests are used. If False, then only the local
(sub)manifest is used. (sub)manifest is used.
@@ -403,6 +410,11 @@ class Command:
all_projects_list = manifest.projects all_projects_list = manifest.projects
result = [] result = []
def should_include_submodules(project: "Project") -> bool:
if submodules_ok is None:
return project.sync_s
return submodules_ok
if not groups: if not groups:
groups = manifest.GetManifestGroupsStr() groups = manifest.GetManifestGroupsStr()
groups = [x for x in re.split(r"[,\s]+", groups) if x] groups = [x for x in re.split(r"[,\s]+", groups) if x]
@@ -410,7 +422,7 @@ class Command:
if not args: if not args:
derived_projects = {} derived_projects = {}
for project in all_projects_list: for project in all_projects_list:
if submodules_ok or project.sync_s: if should_include_submodules(project):
derived_projects.update( derived_projects.update(
(p.RelPath(local=False), p) (p.RelPath(local=False), p)
for p in project.GetDerivedSubprojects() for p in project.GetDerivedSubprojects()
@@ -452,7 +464,7 @@ class Command:
if ( if (
project project
and not project.Derived and not project.Derived
and (submodules_ok or project.sync_s) and should_include_submodules(project)
): ):
search_again = False search_again = False
for subproject in project.GetDerivedSubprojects(): for subproject in project.GetDerivedSubprojects():
+1
View File
@@ -323,6 +323,7 @@ _repo() {
'(-u --manifest-server-username)'{-u,--manifest-server-username=}'[Username for manifest server]:username:' \ '(-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:' \ '(-p --manifest-server-password)'{-p,--manifest-server-password=}'[Password for manifest server]:password:' \
'--fetch-submodules[Fetch submodules]' \ '--fetch-submodules[Fetch submodules]' \
'--no-fetch-submodules[Do not fetch submodules]' \
'--use-superproject[Use superproject]' \ '--use-superproject[Use superproject]' \
'--no-use-superproject[Do not use superproject]' \ '--no-use-superproject[Do not use superproject]' \
'--tags[Sync tags]' \ '--tags[Sync tags]' \
+3
View File
@@ -83,6 +83,9 @@ password to authenticate with the manifest server
\fB\-\-fetch\-submodules\fR \fB\-\-fetch\-submodules\fR
fetch submodules from server fetch submodules from server
.TP .TP
\fB\-\-no\-fetch\-submodules\fR
don't fetch submodules from server
.TP
\fB\-\-use\-superproject\fR \fB\-\-use\-superproject\fR
use the manifest superproject to sync projects; implies \fB\-c\fR use the manifest superproject to sync projects; implies \fB\-c\fR
.TP .TP
+7 -3
View File
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" 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 .SH NAME
repo \- repo sync - manual page for repo sync repo \- repo sync - manual page for repo sync
.SH SYNOPSIS .SH SYNOPSIS
@@ -83,6 +83,9 @@ password to authenticate with the manifest server
\fB\-\-fetch\-submodules\fR \fB\-\-fetch\-submodules\fR
fetch submodules from server fetch submodules from server
.TP .TP
\fB\-\-no\-fetch\-submodules\fR
don't fetch submodules from server
.TP
\fB\-\-use\-superproject\fR \fB\-\-use\-superproject\fR
use the manifest superproject to sync projects; implies \fB\-c\fR use the manifest superproject to sync projects; implies \fB\-c\fR
.TP .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 delivery network. This may be necessary if there are problems with the local
Python HTTP client or proxy configuration, but the Git binary works. Python HTTP client or proxy configuration, but the Git binary works.
.PP .PP
The \fB\-\-fetch\-submodules\fR option enables fetching Git submodules of a project from The \fB\-\-fetch\-submodules\fR option enables fetching Git submodules of all projects
server. 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 .PP
The \fB\-c\fR/\-\-current\-branch option can be used to only fetch objects that are on the 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. branch specified by a project's revision.
+14 -1
View File
@@ -16,7 +16,9 @@ import sys
from color import Coloring from color import Coloring
from command import Command from command import Command
from error import GitError
from git_command import GitCommand from git_command import GitCommand
from project import Project
from repo_logging import RepoLogger from repo_logging import RepoLogger
@@ -30,6 +32,17 @@ class RebaseColoring(Coloring):
self.fail = self.printer("fail", fg="red") self.fail = self.printer("fail", fg="red")
def _ResolveOntoManifest(project: Project) -> str:
"""Resolve project's revisionExpr to a local tracking branch.
Falls back to the raw revisionExpr if ToLocal fails or raises GitError.
"""
try:
return project.GetRemote().ToLocal(project.revisionExpr)
except GitError:
return project.revisionExpr
class Rebase(Command): class Rebase(Command):
COMMON = True COMMON = True
helpSummary = "Rebase local branches on upstream branch" helpSummary = "Rebase local branches on upstream branch"
@@ -162,7 +175,7 @@ branch but need to incorporate new upstream changes "underneath" them.
args = common_args[:] args = common_args[:]
if opt.onto_manifest: if opt.onto_manifest:
args.append("--onto") args.append("--onto")
args.append(project.revisionExpr) args.append(_ResolveOntoManifest(project))
args.append(upbranch.LocalMerge) args.append(upbranch.LocalMerge)
+8 -1
View File
@@ -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. HTTP client or proxy configuration, but the Git binary works.
The --fetch-submodules option enables fetching Git submodules 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 The -c/--current-branch option can be used to only fetch objects that
are on the branch specified by a project's revision. 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", action="store_true",
help="fetch submodules from server", 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( p.add_option(
"--use-superproject", "--use-superproject",
action="store_true", action="store_true",
+31
View File
@@ -14,6 +14,8 @@
"""Unittests for the command.py module.""" """Unittests for the command.py module."""
import pytest
from command import Command from command import Command
@@ -86,3 +88,32 @@ def test_get_projects_keeps_derived_subprojects_for_repeated_repo():
projects = cmd.GetProjects([]) projects = cmd.GetProjects([])
assert set(projects) == {project_a, project_b, submodule_a, submodule_b} 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
+50
View File
@@ -0,0 +1,50 @@
# Copyright (C) 2026 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the subcmds/rebase.py module."""
from unittest import mock
from error import GitError
from subcmds import rebase
def test_resolve_onto_manifest_success() -> None:
"""Test _ResolveOntoManifest when ToLocal succeeds."""
project = mock.MagicMock()
project.revisionExpr = "main"
remote = mock.MagicMock()
remote.ToLocal.return_value = "refs/remotes/goog/main"
project.GetRemote.return_value = remote
res = rebase._ResolveOntoManifest(project)
assert res == "refs/remotes/goog/main"
project.GetRemote.assert_called_once()
remote.ToLocal.assert_called_once_with("main")
def test_resolve_onto_manifest_fallback() -> None:
"""Test _ResolveOntoManifest when ToLocal raises GitError."""
project = mock.MagicMock()
project.revisionExpr = "main"
remote = mock.MagicMock()
remote.ToLocal.side_effect = GitError("Failed to resolve")
project.GetRemote.return_value = remote
res = rebase._ResolveOntoManifest(project)
assert res == "main"
project.GetRemote.assert_called_once()
remote.ToLocal.assert_called_once_with("main")
+17
View File
@@ -30,6 +30,23 @@ from project import SyncNetworkHalfResult
from subcmds import sync 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( @pytest.mark.parametrize(
"use_superproject, cli_args, result", "use_superproject, cli_args, result",
[ [