mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-07-23 20:38:07 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b82311632 | |||
| 06c4f9e1cc | |||
| dd1130352b | |||
| eeba6f268d | |||
| 1729aaebae | |||
| 978adb7ea5 | |||
| 0398c6718e | |||
| 3bb4871c44 |
@@ -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
@@ -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():
|
||||||
|
|||||||
+4
-1
@@ -322,7 +322,10 @@ _repo() {
|
|||||||
'--no-clone-bundle[Do not use clone bundle]' \
|
'--no-clone-bundle[Do not use clone bundle]' \
|
||||||
'(-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]' \
|
'--recurse-submodules[Sync submodules]' \
|
||||||
|
'--no-recurse-submodules[Do not sync submodules]' \
|
||||||
|
'--fetch-submodules[Deprecated alias for --recurse-submodules]' \
|
||||||
|
'--no-fetch-submodules[Deprecated alias for --no-recurse-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]' \
|
||||||
|
|||||||
+10
-1
@@ -83,6 +83,13 @@ then check it directly. Hooks should not normally modify the active git repo
|
|||||||
the user. Although user interaction is discouraged in the common case, it can
|
the user. Although user interaction is discouraged in the common case, it can
|
||||||
be useful when deploying automatic fixes.
|
be useful when deploying automatic fixes.
|
||||||
|
|
||||||
|
### Safe Prompts
|
||||||
|
|
||||||
|
If the repo command that triggered the hook supports a "yes" option (e.g.,
|
||||||
|
`repo upload --yes`), this option is propagated to the hook's `main` function
|
||||||
|
as `yes` parameter (defaulting to `False`). Hooks can use this to bypass
|
||||||
|
interactive confirmation prompts when they can automatically fix issues.
|
||||||
|
|
||||||
### Shebang Handling
|
### Shebang Handling
|
||||||
|
|
||||||
*** note
|
*** note
|
||||||
@@ -119,7 +126,7 @@ This hook runs when people run `repo upload`.
|
|||||||
The `pre-upload.py` file should be defined like:
|
The `pre-upload.py` file should be defined like:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def main(project_list, worktree_list=None, **kwargs):
|
def main(project_list, worktree_list=None, yes=False, **kwargs):
|
||||||
"""Main function invoked directly by repo.
|
"""Main function invoked directly by repo.
|
||||||
|
|
||||||
We must use the name "main" as that is what repo requires.
|
We must use the name "main" as that is what repo requires.
|
||||||
@@ -130,6 +137,8 @@ def main(project_list, worktree_list=None, **kwargs):
|
|||||||
project_list, so that each entry in project_list matches with a
|
project_list, so that each entry in project_list matches with a
|
||||||
directory in worktree_list. If None, we will attempt to calculate
|
directory in worktree_list. If None, we will attempt to calculate
|
||||||
the directories automatically.
|
the directories automatically.
|
||||||
|
yes: Whether to answer yes to all safe prompts (see
|
||||||
|
[Safe Prompts](#safe-prompts)).
|
||||||
kwargs: Leave this here for forward-compatibility.
|
kwargs: Leave this here for forward-compatibility.
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -473,7 +473,7 @@ class Superproject:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
manifest_str = self._manifest.ToXml(
|
manifest_str = self._manifest.ToXml(
|
||||||
filter_groups=self._manifest.GetManifestGroupsStr(),
|
filter_groups="all",
|
||||||
omit_local=True,
|
omit_local=True,
|
||||||
).toxml()
|
).toxml()
|
||||||
manifest_path = self._manifest_path
|
manifest_path = self._manifest_path
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class RepoHook:
|
|||||||
allow_all_hooks=False,
|
allow_all_hooks=False,
|
||||||
ignore_hooks=False,
|
ignore_hooks=False,
|
||||||
abort_if_user_denies=False,
|
abort_if_user_denies=False,
|
||||||
|
yes=False,
|
||||||
):
|
):
|
||||||
"""RepoHook constructor.
|
"""RepoHook constructor.
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ class RepoHook:
|
|||||||
ignore_hooks: If True, then 'Do not abort action if hooks fail'.
|
ignore_hooks: If True, then 'Do not abort action if hooks fail'.
|
||||||
abort_if_user_denies: If True, we'll abort running the hook if the
|
abort_if_user_denies: If True, we'll abort running the hook if the
|
||||||
user doesn't allow us to run the hook.
|
user doesn't allow us to run the hook.
|
||||||
|
yes: If True, then 'Yes' is assumed for any prompts.
|
||||||
"""
|
"""
|
||||||
self._hook_type = hook_type
|
self._hook_type = hook_type
|
||||||
self._hooks_project = hooks_project
|
self._hooks_project = hooks_project
|
||||||
@@ -99,6 +101,7 @@ class RepoHook:
|
|||||||
self._allow_all_hooks = allow_all_hooks
|
self._allow_all_hooks = allow_all_hooks
|
||||||
self._ignore_hooks = ignore_hooks
|
self._ignore_hooks = ignore_hooks
|
||||||
self._abort_if_user_denies = abort_if_user_denies
|
self._abort_if_user_denies = abort_if_user_denies
|
||||||
|
self._yes = yes
|
||||||
|
|
||||||
# Store the full path to the script for convenience.
|
# Store the full path to the script for convenience.
|
||||||
self._script_fullpath = None
|
self._script_fullpath = None
|
||||||
@@ -374,8 +377,11 @@ class RepoHook:
|
|||||||
# def main(project_list, **kwargs):
|
# def main(project_list, **kwargs):
|
||||||
#
|
#
|
||||||
# This allows us to later expand the API without breaking old hooks.
|
# This allows us to later expand the API without breaking old hooks.
|
||||||
kwargs = kwargs.copy()
|
kwargs = {
|
||||||
kwargs["hook_should_take_kwargs"] = True
|
**kwargs,
|
||||||
|
"hook_should_take_kwargs": True,
|
||||||
|
"yes": self._yes,
|
||||||
|
}
|
||||||
|
|
||||||
# See what version of python the hook has been written against.
|
# See what version of python the hook has been written against.
|
||||||
data = open(self._script_fullpath).read()
|
data = open(self._script_fullpath).read()
|
||||||
@@ -497,6 +503,7 @@ class RepoHook:
|
|||||||
"origin"
|
"origin"
|
||||||
).url,
|
).url,
|
||||||
"bug_url": manifest.contactinfo.bugurl,
|
"bug_url": manifest.contactinfo.bugurl,
|
||||||
|
"yes": getattr(opt, "yes", False),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return cls(*args, **kwargs)
|
return cls(*args, **kwargs)
|
||||||
|
|||||||
@@ -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 smartsync" "Repo Manual"
|
.TH REPO "1" "July 2026" "repo smartsync" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo smartsync - manual page for repo smartsync
|
repo \- repo smartsync - manual page for repo smartsync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -68,6 +68,9 @@ fetch all branches from server
|
|||||||
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
||||||
temporary manifest to use for this sync
|
temporary manifest to use for this sync
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
|
||||||
|
sync projects matching the specific groups. Not persistent unlike when used on init
|
||||||
|
.TP
|
||||||
\fB\-\-clone\-bundle\fR
|
\fB\-\-clone\-bundle\fR
|
||||||
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
|
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
|
||||||
.TP
|
.TP
|
||||||
@@ -80,8 +83,11 @@ username to authenticate with the manifest server
|
|||||||
\fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
|
\fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
|
||||||
password to authenticate with the manifest server
|
password to authenticate with the manifest server
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-fetch\-submodules\fR
|
\fB\-\-recurse\-submodules\fR
|
||||||
fetch submodules from server
|
sync submodules from server
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-recurse\-submodules\fR
|
||||||
|
don't sync submodules from server
|
||||||
.TP
|
.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
|
||||||
|
|||||||
+15
-5
@@ -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
|
||||||
@@ -68,6 +68,9 @@ fetch all branches from server
|
|||||||
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
|
||||||
temporary manifest to use for this sync
|
temporary manifest to use for this sync
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
|
||||||
|
sync projects matching the specific groups. Not persistent unlike when used on init
|
||||||
|
.TP
|
||||||
\fB\-\-clone\-bundle\fR
|
\fB\-\-clone\-bundle\fR
|
||||||
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
|
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
|
||||||
.TP
|
.TP
|
||||||
@@ -80,8 +83,11 @@ username to authenticate with the manifest server
|
|||||||
\fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
|
\fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
|
||||||
password to authenticate with the manifest server
|
password to authenticate with the manifest server
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-fetch\-submodules\fR
|
\fB\-\-recurse\-submodules\fR
|
||||||
fetch submodules from server
|
sync submodules from server
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-recurse\-submodules\fR
|
||||||
|
don't sync submodules from server
|
||||||
.TP
|
.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
|
||||||
@@ -212,8 +218,12 @@ 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\-\-recurse\-submodules\fR option enables syncing Git submodules of all projects
|
||||||
server.
|
from the server. The \fB\-\-no\-recurse\-submodules\fR option disables syncing Git
|
||||||
|
submodules, even when a project has sync\-s="true" in the manifest.
|
||||||
|
.PP
|
||||||
|
The \fB\-\-fetch\-submodules\fR and \fB\-\-no\-fetch\-submodules\fR options are deprecated aliases
|
||||||
|
for \fB\-\-recurse\-submodules\fR and \fB\-\-no\-recurse\-submodules\fR, respectively.
|
||||||
.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.
|
||||||
|
|||||||
+24
-6
@@ -2674,11 +2674,12 @@ class Project:
|
|||||||
# throws an error.
|
# throws an error.
|
||||||
revs = [f"{self.revisionExpr}^0"]
|
revs = [f"{self.revisionExpr}^0"]
|
||||||
upstream_rev = None
|
upstream_rev = None
|
||||||
|
use_superproject_for_upstream = self.upstream and (
|
||||||
|
self._UseSuperprojectForUpstream(use_superproject)
|
||||||
|
)
|
||||||
|
|
||||||
# Only check upstream when using superproject.
|
# Only check upstream when using superproject.
|
||||||
if self.upstream and git_superproject.UseSuperproject(
|
if use_superproject_for_upstream:
|
||||||
use_superproject, self.manifest
|
|
||||||
):
|
|
||||||
upstream_rev = self.GetRemote().ToLocal(self.upstream)
|
upstream_rev = self.GetRemote().ToLocal(self.upstream)
|
||||||
revs.append(upstream_rev)
|
revs.append(upstream_rev)
|
||||||
|
|
||||||
@@ -2692,9 +2693,7 @@ class Project:
|
|||||||
|
|
||||||
# Only verify upstream relationship for superproject scenarios
|
# Only verify upstream relationship for superproject scenarios
|
||||||
# without affecting plain usage.
|
# without affecting plain usage.
|
||||||
if self.upstream and git_superproject.UseSuperproject(
|
if use_superproject_for_upstream:
|
||||||
use_superproject, self.manifest
|
|
||||||
):
|
|
||||||
self.bare_git.merge_base(
|
self.bare_git.merge_base(
|
||||||
"--is-ancestor",
|
"--is-ancestor",
|
||||||
self.revisionExpr,
|
self.revisionExpr,
|
||||||
@@ -2723,6 +2722,16 @@ class Project:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _UseSuperprojectForUpstream(
|
||||||
|
self, use_superproject: Optional[bool] = None
|
||||||
|
) -> bool:
|
||||||
|
"""Whether to include upstream in the immutability check.
|
||||||
|
|
||||||
|
The upstream ancestry check is only meaningful for projects
|
||||||
|
that participate in a superproject relationship.
|
||||||
|
"""
|
||||||
|
return git_superproject.UseSuperproject(use_superproject, self.manifest)
|
||||||
|
|
||||||
def _FetchArchive(self, tarpath, cwd=None):
|
def _FetchArchive(self, tarpath, cwd=None):
|
||||||
cmd = ["archive", "-v", "-o", tarpath]
|
cmd = ["archive", "-v", "-o", tarpath]
|
||||||
cmd.append("--remote=%s" % self.remote.url)
|
cmd.append("--remote=%s" % self.remote.url)
|
||||||
@@ -4677,6 +4686,15 @@ class MetaProject(Project):
|
|||||||
self.revisionExpr = base
|
self.revisionExpr = base
|
||||||
self.revisionId = None
|
self.revisionId = None
|
||||||
|
|
||||||
|
def _UseSuperprojectForUpstream(
|
||||||
|
self, use_superproject: Optional[bool] = None
|
||||||
|
) -> bool:
|
||||||
|
# MetaProjects (the manifest repo and repo itself) never
|
||||||
|
# participate in a superproject relationship. Returning False
|
||||||
|
# here also avoids loading the manifest during `repo init`,
|
||||||
|
# before manifest.xml has been linked into .repo/.
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def HasChanges(self):
|
def HasChanges(self):
|
||||||
"""Has the remote received new commits not yet checked out?"""
|
"""Has the remote received new commits not yet checked out?"""
|
||||||
|
|||||||
+14
-1
@@ -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)
|
||||||
|
|
||||||
|
|||||||
+52
-8
@@ -378,8 +378,12 @@ resumeable bundle file on a content delivery network. This
|
|||||||
may be necessary if there are problems with the local Python
|
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 --recurse-submodules option enables syncing Git submodules of all projects
|
||||||
of a project from server.
|
from the server. The --no-recurse-submodules option disables syncing Git
|
||||||
|
submodules, even when a project has sync-s="true" in the manifest.
|
||||||
|
|
||||||
|
The --fetch-submodules and --no-fetch-submodules options are deprecated aliases
|
||||||
|
for --recurse-submodules and --no-recurse-submodules, respectively.
|
||||||
|
|
||||||
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.
|
||||||
@@ -427,6 +431,15 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
_JOBS_WARN_THRESHOLD = 100
|
_JOBS_WARN_THRESHOLD = 100
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _deprecated_submodules_option(option, opt_str, _value, parser):
|
||||||
|
enabled = opt_str == "--fetch-submodules"
|
||||||
|
replacement = (
|
||||||
|
"--recurse-submodules" if enabled else "--no-recurse-submodules"
|
||||||
|
)
|
||||||
|
logger.warning("%s is deprecated; use %s instead", opt_str, replacement)
|
||||||
|
setattr(parser.values, option.dest, enabled)
|
||||||
|
|
||||||
def _Options(self, p, show_smart=True):
|
def _Options(self, p, show_smart=True):
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"--jobs-network",
|
"--jobs-network",
|
||||||
@@ -545,6 +558,13 @@ later is required to fix a server side protocol bug.
|
|||||||
help="temporary manifest to use for this sync",
|
help="temporary manifest to use for this sync",
|
||||||
metavar="NAME.xml",
|
metavar="NAME.xml",
|
||||||
)
|
)
|
||||||
|
p.add_option(
|
||||||
|
"-g",
|
||||||
|
"--groups",
|
||||||
|
help="sync projects matching the specific groups. Not persistent "
|
||||||
|
"unlike when used on init",
|
||||||
|
metavar="GROUP",
|
||||||
|
)
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"--clone-bundle",
|
"--clone-bundle",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -569,9 +589,29 @@ later is required to fix a server side protocol bug.
|
|||||||
help="password to authenticate with the manifest server",
|
help="password to authenticate with the manifest server",
|
||||||
)
|
)
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"--fetch-submodules",
|
"--recurse-submodules",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="fetch submodules from server",
|
help="sync submodules from server",
|
||||||
|
)
|
||||||
|
p.add_option(
|
||||||
|
"--no-recurse-submodules",
|
||||||
|
dest="recurse_submodules",
|
||||||
|
action="store_false",
|
||||||
|
help="don't sync submodules from server",
|
||||||
|
)
|
||||||
|
p.add_option(
|
||||||
|
"--fetch-submodules",
|
||||||
|
dest="recurse_submodules",
|
||||||
|
action="callback",
|
||||||
|
callback=self._deprecated_submodules_option,
|
||||||
|
help=optparse.SUPPRESS_HELP,
|
||||||
|
)
|
||||||
|
p.add_option(
|
||||||
|
"--no-fetch-submodules",
|
||||||
|
dest="recurse_submodules",
|
||||||
|
action="callback",
|
||||||
|
callback=self._deprecated_submodules_option,
|
||||||
|
help=optparse.SUPPRESS_HELP,
|
||||||
)
|
)
|
||||||
p.add_option(
|
p.add_option(
|
||||||
"--use-superproject",
|
"--use-superproject",
|
||||||
@@ -741,8 +781,9 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
all_projects = self.GetProjects(
|
all_projects = self.GetProjects(
|
||||||
args,
|
args,
|
||||||
|
groups=opt.groups,
|
||||||
missing_ok=True,
|
missing_ok=True,
|
||||||
submodules_ok=opt.fetch_submodules,
|
submodules_ok=opt.recurse_submodules,
|
||||||
manifest=manifest,
|
manifest=manifest,
|
||||||
all_manifests=not opt.this_manifest_only,
|
all_manifests=not opt.this_manifest_only,
|
||||||
)
|
)
|
||||||
@@ -1070,8 +1111,9 @@ later is required to fix a server side protocol bug.
|
|||||||
self._ReloadManifest(None, manifest)
|
self._ReloadManifest(None, manifest)
|
||||||
all_projects = self.GetProjects(
|
all_projects = self.GetProjects(
|
||||||
args,
|
args,
|
||||||
|
groups=opt.groups,
|
||||||
missing_ok=True,
|
missing_ok=True,
|
||||||
submodules_ok=opt.fetch_submodules,
|
submodules_ok=opt.recurse_submodules,
|
||||||
manifest=manifest,
|
manifest=manifest,
|
||||||
all_manifests=not opt.this_manifest_only,
|
all_manifests=not opt.this_manifest_only,
|
||||||
)
|
)
|
||||||
@@ -2317,8 +2359,9 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
all_projects = self.GetProjects(
|
all_projects = self.GetProjects(
|
||||||
args,
|
args,
|
||||||
|
groups=opt.groups,
|
||||||
missing_ok=True,
|
missing_ok=True,
|
||||||
submodules_ok=opt.fetch_submodules,
|
submodules_ok=opt.recurse_submodules,
|
||||||
manifest=manifest,
|
manifest=manifest,
|
||||||
all_manifests=not opt.this_manifest_only,
|
all_manifests=not opt.this_manifest_only,
|
||||||
)
|
)
|
||||||
@@ -2980,8 +3023,9 @@ later is required to fix a server side protocol bug.
|
|||||||
self._ReloadManifest(None, manifest)
|
self._ReloadManifest(None, manifest)
|
||||||
project_list = self.GetProjects(
|
project_list = self.GetProjects(
|
||||||
args,
|
args,
|
||||||
|
groups=opt.groups,
|
||||||
missing_ok=True,
|
missing_ok=True,
|
||||||
submodules_ok=opt.fetch_submodules,
|
submodules_ok=opt.recurse_submodules,
|
||||||
manifest=manifest,
|
manifest=manifest,
|
||||||
all_manifests=not opt.this_manifest_only,
|
all_manifests=not opt.this_manifest_only,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -105,3 +105,37 @@ def test_post_sync_argument_validation() -> None:
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
sys.stderr = old_stderr
|
sys.stderr = old_stderr
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("yes_val", (True, False))
|
||||||
|
def test_repo_upload_yes_arg(tmp_path, yes_val: bool) -> None:
|
||||||
|
"""Test that yes is passed in kwargs during hook execution."""
|
||||||
|
|
||||||
|
class FakeProject:
|
||||||
|
def __init__(self, worktree):
|
||||||
|
self.worktree = worktree
|
||||||
|
self.enabled_repo_hooks = ["pre-upload"]
|
||||||
|
self.config = None
|
||||||
|
|
||||||
|
hook_file = tmp_path / "pre-upload.py"
|
||||||
|
|
||||||
|
hook_content = """
|
||||||
|
def main(project_list, **kwargs):
|
||||||
|
project_list.append(kwargs.get("yes"))
|
||||||
|
"""
|
||||||
|
hook_file.write_text(hook_content)
|
||||||
|
|
||||||
|
hook = hooks.RepoHook(
|
||||||
|
hook_type="pre-upload",
|
||||||
|
hooks_project=FakeProject(str(tmp_path)),
|
||||||
|
repo_topdir=str(tmp_path),
|
||||||
|
manifest_url="https://gerrit",
|
||||||
|
allow_all_hooks=True,
|
||||||
|
yes=yes_val,
|
||||||
|
)
|
||||||
|
|
||||||
|
project_list = []
|
||||||
|
res = hook.Run(project_list=project_list, worktree_list=[])
|
||||||
|
|
||||||
|
assert res is True
|
||||||
|
assert project_list == [yes_val]
|
||||||
|
|||||||
@@ -716,6 +716,31 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
|
|||||||
fakeproj.config.SetString("manifest.platform", "auto")
|
fakeproj.config.SetString("manifest.platform", "auto")
|
||||||
self.assertEqual(fakeproj.manifest_platform, "auto")
|
self.assertEqual(fakeproj.manifest_platform, "auto")
|
||||||
|
|
||||||
|
def test_check_immutable_revision_metaproject_skips_manifest_load(self):
|
||||||
|
"""MetaProjects must not parse manifest.xml during immutable check.
|
||||||
|
|
||||||
|
During `repo init` the manifestProject's own Sync_NetworkHalf runs
|
||||||
|
before manifest.xml has been linked into .repo/, so
|
||||||
|
_CheckForImmutableRevision must not touch it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with utils_for_test.TempGitTree() as tempdir:
|
||||||
|
fakeproj = self.setUpManifest(tempdir)
|
||||||
|
manifest_path = os.path.join(
|
||||||
|
tempdir, ".repo", manifest_xml.MANIFEST_FILE_NAME
|
||||||
|
)
|
||||||
|
self.assertFalse(os.path.exists(manifest_path))
|
||||||
|
|
||||||
|
fakeproj.revisionExpr = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||||
|
fakeproj.upstream = "refs/heads/main"
|
||||||
|
|
||||||
|
# Must return False without raising ManifestParseError, and
|
||||||
|
# must leave the absent manifest.xml untouched.
|
||||||
|
self.assertFalse(
|
||||||
|
fakeproj._CheckForImmutableRevision(use_superproject=None)
|
||||||
|
)
|
||||||
|
self.assertFalse(os.path.exists(manifest_path))
|
||||||
|
|
||||||
def test_sync_use_local_gitdirs_worktree_conflict(self):
|
def test_sync_use_local_gitdirs_worktree_conflict(self):
|
||||||
"""Test that --use-local-gitdirs conflicts with --worktree."""
|
"""Test that --use-local-gitdirs conflicts with --worktree."""
|
||||||
with utils_for_test.TempGitTree() as tempdir:
|
with utils_for_test.TempGitTree() as tempdir:
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -26,10 +27,51 @@ import pytest
|
|||||||
import command
|
import command
|
||||||
from error import GitError
|
from error import GitError
|
||||||
from error import RepoExitError
|
from error import RepoExitError
|
||||||
|
import manifest_xml
|
||||||
from project import SyncNetworkHalfResult
|
from project import SyncNetworkHalfResult
|
||||||
from subcmds import sync
|
from subcmds import sync
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"cli_args, expected",
|
||||||
|
[
|
||||||
|
([], None),
|
||||||
|
(["--recurse-submodules"], True),
|
||||||
|
(["--no-recurse-submodules"], False),
|
||||||
|
(["--fetch-submodules"], True),
|
||||||
|
(["--no-fetch-submodules"], False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_recurse_submodules_option(cli_args, expected):
|
||||||
|
"""The submodule flags preserve an unset manifest-driven state."""
|
||||||
|
cmd = sync.Sync()
|
||||||
|
|
||||||
|
opts, _ = cmd.OptionParser.parse_args(cli_args)
|
||||||
|
|
||||||
|
assert opts.recurse_submodules is expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"old_flag, new_flag",
|
||||||
|
[
|
||||||
|
("--fetch-submodules", "--recurse-submodules"),
|
||||||
|
("--no-fetch-submodules", "--no-recurse-submodules"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_recurse_submodules_option_deprecation(old_flag, new_flag):
|
||||||
|
"""The old submodule flags warn and direct users to their replacements."""
|
||||||
|
cmd = sync.Sync()
|
||||||
|
|
||||||
|
with mock.patch.object(sync.logger, "warning") as warning:
|
||||||
|
cmd.OptionParser.parse_args([old_flag])
|
||||||
|
|
||||||
|
warning.assert_called_once_with(
|
||||||
|
"%s is deprecated; use %s instead", old_flag, new_flag
|
||||||
|
)
|
||||||
|
|
||||||
|
assert old_flag not in cmd.OptionParser.format_help()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"use_superproject, cli_args, result",
|
"use_superproject, cli_args, result",
|
||||||
[
|
[
|
||||||
@@ -56,6 +98,120 @@ def test_get_current_branch_only(use_superproject, cli_args, result):
|
|||||||
assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result
|
assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"cli_args, expected_groups",
|
||||||
|
[
|
||||||
|
([], None),
|
||||||
|
(["-g", "groupA"], "groupA"),
|
||||||
|
(["--groups=groupB,groupC"], "groupB,groupC"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_groups_option_parsing(cli_args, expected_groups):
|
||||||
|
"""Test --groups / -g option parsing."""
|
||||||
|
cmd = sync.Sync()
|
||||||
|
opts, _ = cmd.OptionParser.parse_args(cli_args)
|
||||||
|
assert opts.groups == expected_groups
|
||||||
|
|
||||||
|
|
||||||
|
def _create_manifest_with_groups(topdir: Path) -> manifest_xml.XmlManifest:
|
||||||
|
"""Create a test XmlManifest with projects assigned to various groups."""
|
||||||
|
repodir = topdir / ".repo"
|
||||||
|
manifest_dir = repodir / "manifests"
|
||||||
|
manifest_file = repodir / manifest_xml.MANIFEST_FILE_NAME
|
||||||
|
|
||||||
|
repodir.mkdir(exist_ok=True)
|
||||||
|
manifest_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
gitdir = repodir / "manifests.git"
|
||||||
|
gitdir.mkdir(exist_ok=True)
|
||||||
|
(gitdir / "config").write_text(
|
||||||
|
"""[remote "origin"]
|
||||||
|
url = https://localhost:0/manifest
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
manifest_file.write_text(
|
||||||
|
"""
|
||||||
|
<manifest>
|
||||||
|
<remote name="origin" fetch="http://localhost" />
|
||||||
|
<default remote="origin" revision="refs/heads/main" />
|
||||||
|
<project name="proj_g1" path="path_g1" groups="group1" />
|
||||||
|
<project name="proj_g2" path="path_g2" groups="group2" />
|
||||||
|
<project name="proj_g1_g2" path="path_g1_g2"
|
||||||
|
groups="group1,group2" />
|
||||||
|
<project name="proj_default" path="path_default" />
|
||||||
|
<project name="proj_notdefault" path="path_notdefault"
|
||||||
|
groups="notdefault" />
|
||||||
|
</manifest>
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
for p in [
|
||||||
|
"proj_g1",
|
||||||
|
"proj_g2",
|
||||||
|
"proj_g1_g2",
|
||||||
|
"proj_default",
|
||||||
|
"proj_notdefault",
|
||||||
|
]:
|
||||||
|
(repodir / "projects" / f"{p}.git").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
return manifest_xml.XmlManifest(str(repodir), str(manifest_file))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"cli_args, expected_projects",
|
||||||
|
[
|
||||||
|
(["-g", "group1"], ["proj_g1", "proj_g1_g2"]),
|
||||||
|
(["-g", "group2"], ["proj_g2", "proj_g1_g2"]),
|
||||||
|
(["-g", "group1,group2"], ["proj_g1", "proj_g1_g2", "proj_g2"]),
|
||||||
|
(["-g", "default,-group1"], ["proj_default", "proj_g2"]),
|
||||||
|
([], ["proj_default", "proj_g1", "proj_g1_g2", "proj_g2"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_sync_groups_manifest_filtering(
|
||||||
|
tmp_path: Path, cli_args, expected_projects
|
||||||
|
):
|
||||||
|
"""Test that repo sync -g selects only matching projects."""
|
||||||
|
manifest = _create_manifest_with_groups(tmp_path)
|
||||||
|
cmd = sync.Sync()
|
||||||
|
cmd.manifest = manifest
|
||||||
|
|
||||||
|
opts, args = cmd.OptionParser.parse_args(cli_args)
|
||||||
|
projects = cmd.GetProjects(args, groups=opts.groups, missing_ok=True)
|
||||||
|
project_names = sorted([p.name for p in projects])
|
||||||
|
assert project_names == sorted(expected_projects)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_update_projects_revision_id_respects_groups(tmp_path: Path):
|
||||||
|
"""Test that _UpdateProjectsRevisionId filters projects using opt.groups."""
|
||||||
|
manifest = _create_manifest_with_groups(tmp_path)
|
||||||
|
cmd = sync.Sync()
|
||||||
|
cmd.manifest = manifest
|
||||||
|
|
||||||
|
superproject = mock.MagicMock()
|
||||||
|
superproject.UpdateProjectsRevisionId.return_value = mock.MagicMock(
|
||||||
|
manifest_path=None
|
||||||
|
)
|
||||||
|
manifest._superproject = superproject
|
||||||
|
|
||||||
|
opts, args = cmd.OptionParser.parse_args(["-g", "group1"])
|
||||||
|
opts.verbose = False
|
||||||
|
opts.fetch_submodules = False
|
||||||
|
opts.this_manifest_only = True
|
||||||
|
opts.local_only = False
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
cmd, "GetProjects", wraps=cmd.GetProjects
|
||||||
|
) as spy_get_projects:
|
||||||
|
with mock.patch.object(cmd, "ManifestList", return_value=[manifest]):
|
||||||
|
cmd._UpdateProjectsRevisionId(opts, args, {}, manifest)
|
||||||
|
spy_get_projects.assert_called_once()
|
||||||
|
_, kwargs = spy_get_projects.call_args
|
||||||
|
assert kwargs.get("groups") == "group1"
|
||||||
|
|
||||||
|
|
||||||
# Used to patch os.cpu_count() for reliable results.
|
# Used to patch os.cpu_count() for reliable results.
|
||||||
OS_CPU_COUNT = 24
|
OS_CPU_COUNT = 24
|
||||||
|
|
||||||
@@ -792,6 +948,19 @@ class SyncCommand(unittest.TestCase):
|
|||||||
self.assertIn(self.sync_local_half_error, e.aggregate_errors)
|
self.assertIn(self.sync_local_half_error, e.aggregate_errors)
|
||||||
self.assertIn(self.sync_network_half_error, e.aggregate_errors)
|
self.assertIn(self.sync_network_half_error, e.aggregate_errors)
|
||||||
|
|
||||||
|
def test_groups_passed_to_get_projects(self):
|
||||||
|
"""Ensure Execute passes opt.groups to GetProjects."""
|
||||||
|
self.opt.groups = "my_group"
|
||||||
|
self.opt.mp_update = False
|
||||||
|
with mock.patch.object(self.cmd, "_UpdateRepoProject"):
|
||||||
|
with mock.patch.object(self.cmd, "_ValidateOptionsWithManifest"):
|
||||||
|
with mock.patch.object(self.cmd, "_SyncInterleaved"):
|
||||||
|
with mock.patch.object(self.cmd, "_RunPostSyncHook"):
|
||||||
|
self.cmd.Execute(self.opt, [])
|
||||||
|
self.cmd.GetProjects.assert_called()
|
||||||
|
_, kwargs = self.cmd.GetProjects.call_args
|
||||||
|
self.assertEqual(kwargs.get("groups"), "my_group")
|
||||||
|
|
||||||
|
|
||||||
class SyncUpdateRepoProject(unittest.TestCase):
|
class SyncUpdateRepoProject(unittest.TestCase):
|
||||||
"""Tests for Sync._UpdateRepoProject."""
|
"""Tests for Sync._UpdateRepoProject."""
|
||||||
|
|||||||
Reference in New Issue
Block a user