Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'sync': introduce '--switch' #433

Merged
merged 1 commit into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tsrc/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def resolve_repos(
all_cloned: bool,
include_regex: str = "",
exclude_regex: str = "",
do_switch: bool = False,
ignore_if_group_not_found: bool = False,
ignore_group_item: bool = False,
) -> List[Repo]:
Expand All @@ -192,6 +193,8 @@ def resolve_repos(
repos = manifest.get_repos(
groups=groups, ignore_if_group_not_found=ignore_if_group_not_found
)
elif do_switch is True:
repos = manifest.get_repos(groups, do_switch)
elif all_cloned:
repos = manifest.get_repos(all_=True)
repos = [repo for repo in repos if (workspace.root_path / repo.dest).exists()]
Expand Down
47 changes: 31 additions & 16 deletions tsrc/cli/sync.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""" Entry point for `tsrc sync` """

import argparse
from typing import List
from typing import List, Union

import cli_ui as ui

Expand Down Expand Up @@ -50,6 +50,12 @@ def configure_parser(subparser: argparse._SubParsersAction) -> None:
dest="ignore_group_item",
help="ignore group element if it is not found among Manifest's Repos. WARNING: If you end up in need of this option, you have to understand that you end up with useles Manifest. Warnings will be printed for each Group element that is missing, so it may be easier to fix that. Using this option is NOT RECOMMENDED for normal use", # noqa: E501
)
parser.add_argument(
"--switch",
action="store_true",
dest="do_switch",
help="change config in accordance to Manifest's switch section. this is pariculary usefull when we wish to change Manifest branch to some version that may have different Groups configured.", # noqa: E501
)
parser.add_argument(
"--clean",
action="store_true",
Expand Down Expand Up @@ -89,42 +95,50 @@ def run(args: argparse.Namespace) -> None:
correct_branch = args.correct_branch
workspace = get_workspace(args)
num_jobs = get_num_jobs(args)
do_switch = args.do_switch
do_clean = args.do_clean
do_hard_clean = args.do_hard_clean

ignore_if_group_not_found: bool = False
report_update_repo_groups: bool = False
report_update_repo_groups: Union[bool, None] = False

if update_manifest:
repo_groups_0 = workspace.config.repo_groups.copy()
ui.info_2("Updating manifest")
workspace.update_manifest()

# check if groups needs to be ignored
# check if groups needs to be updated on config
found_groups: List[str] = []
if groups and args.ignore_if_group_not_found is True:
if groups:
local_manifest = workspace.local_manifest.get_manifest()
if local_manifest.group_list and local_manifest.group_list.groups:
found_groups = list(
set(groups).intersection(local_manifest.group_list.groups)
)
if update_config_repo_groups is True:
workspace.update_config_repo_groups(
groups=found_groups, ignore_group_item=args.ignore_group_item
)
report_update_repo_groups = True

if update_config_repo_groups is True:
if args.ignore_if_group_not_found is True:
ignore_if_group_not_found = True
if do_switch is True:

report_update_repo_groups = workspace.update_config_on_switch(
workspace.local_manifest.get_manifest(), found_groups, groups
)
if not isinstance(report_update_repo_groups, bool):
ui.error("Provided Groups does not match any in the Manifest")
return
elif update_config_repo_groups is True:
workspace.update_config_repo_groups(
groups=found_groups, ignore_group_item=args.ignore_group_item
groups=found_groups,
ignore_group_item=args.ignore_group_item,
want_groups=groups,
)
report_update_repo_groups = True

if report_update_repo_groups is True:
ui.info_2("Updating repo_groups")
if (
report_update_repo_groups is True
and repo_groups_0 != workspace.config.repo_groups # noqa: W503
):
ui.info_2("Updating Workspace Groups configuration")
else:
ui.info_2("Leaving repo_groups intact")
ui.info_2("Leaving Workspace Groups configuration intact")
else:
ui.info_2("Not updating manifest")
if args.ignore_if_group_not_found is True:
Expand All @@ -137,6 +151,7 @@ def run(args: argparse.Namespace) -> None:
all_cloned=all_cloned,
include_regex=include_regex,
exclude_regex=exclude_regex,
do_switch=do_switch,
ignore_if_group_not_found=ignore_if_group_not_found,
ignore_group_item=args.ignore_group_item,
)
Expand Down
6 changes: 6 additions & 0 deletions tsrc/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ def __init__(self, mtod: ManifestsTypeOfData) -> None:
super().__init__(msg)


class LoadManifestSwitchConfigGroupsError(Error):
def __init__(self) -> None:
msg = "Manifest's Switch Config Groups does not match Groups"
super().__init__(msg)


class MissingRepoError(Error):
def __init__(self, dest: str):
super().__init__(f"No repo found in '{dest}'. Please run `tsrc sync`")
56 changes: 55 additions & 1 deletion tsrc/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
import schema

from tsrc.config import parse_config
from tsrc.errors import Error, InvalidConfigError, LoadManifestSchemaError
from tsrc.errors import (
Error,
InvalidConfigError,
LoadManifestSchemaError,
LoadManifestSwitchConfigGroupsError,
)
from tsrc.file_system import Copy, FileSystemOperation, Link
from tsrc.groups import GroupList
from tsrc.manifest_common_data import ManifestsTypeOfData, mtod_can_ignore_remotes
from tsrc.repo import Remote, Repo
from tsrc.switch import Switch


class RepoNotFound(Error):
Expand All @@ -29,6 +35,7 @@ class Manifest:
def __init__(self) -> None:
self._repos: List[Repo] = []
self.group_list: Optional[GroupList[str]] = None
self._switch: Optional[Switch] = None

def apply_config(
self,
Expand All @@ -53,6 +60,9 @@ def apply_config(
ignore_on_mtod=ignore_on_mtod,
)

switch_config = config.get("switch")
self._handle_switch(switch_config)

def _handle_repo(self, repo_config: Any) -> None:
dest = repo_config["dest"]
branch = orig_branch = repo_config.get("branch")
Expand Down Expand Up @@ -122,15 +132,33 @@ def _handle_groups(
ignore_on_mtod=ignore_on_mtod,
)

def _handle_switch(self, switch_config: Any) -> None:
self._switch = Switch(switch_config)

# verify if groups in switch>config>groups are present in 'groups'
if self.group_list and self.group_list.groups and self._switch._groups:
switch_groups = list(self._switch._groups)
groups_groups = self.group_list.groups.keys()
if switch_groups and groups_groups:
if set(switch_groups) != set(groups_groups).intersection(switch_groups):
raise LoadManifestSwitchConfigGroupsError()
elif self._switch._groups:
# you cannot have 'swtich>config>groups' alone (without 'groups')
raise LoadManifestSwitchConfigGroupsError()

def get_repos(
self,
groups: Optional[List[str]] = None,
do_switch: bool = False,
all_: bool = False,
ignore_if_group_not_found: bool = False,
) -> List[Repo]:
if all_:
return self._repos

if do_switch is True:
return self._get_repos_on_switch(groups)

if not groups:
if self._has_default_group():
return self._get_repos_in_groups(["default"])
Expand All @@ -143,6 +171,13 @@ def _has_default_group(self) -> bool:
assert self.group_list
return self.group_list.get_group("default") is not None

def _get_repos_on_switch(self, groups: Optional[List[str]]) -> List[Repo]:
if self._switch:
if self._switch._groups:
matched_groups = list(self._switch._groups)
return self._get_repos_in_groups(matched_groups)
return self._repos # all repos

def _get_repos_in_groups(
self,
groups: List[str],
Expand Down Expand Up @@ -220,6 +255,21 @@ def validate_repo_no_remote_required(data: Any) -> None:
)


def validate_switch_config(data: Any) -> None:
switch_config_schema = schema.Schema(
{
schema.Optional("groups"): [str],
}
)
switch_config_schema.validate(data)


def validate_switch(data: Any) -> None:
on_config_schema = schema.Use(validate_switch_config)
switch_schema = schema.Schema({schema.Optional("config"): on_config_schema})
switch_schema.validate(data)


def load_manifest(manifest_path: Path) -> Manifest:
"""Main entry point: return a manifest instance by parsing
a `manifest.yml` file.
Expand All @@ -230,12 +280,14 @@ def load_manifest(manifest_path: Path) -> Manifest:
group_schema = {"repos": [str], schema.Optional("includes"): [str]}
# Note: gitlab and github_enterprise_url keys are ignored,
# and kept here only for backward compatibility reasons
on_switch_schema = schema.Use(validate_switch)
manifest_schema = schema.Schema(
{
"repos": [repo_schema],
schema.Optional("gitlab"): remote_git_server_schema,
schema.Optional("github_enterprise"): remote_git_server_schema,
schema.Optional("groups"): {str: group_schema},
schema.Optional("switch"): on_switch_schema,
}
)
parsed = parse_config(manifest_path, schema=manifest_schema)
Expand All @@ -262,12 +314,14 @@ def load_manifest_safe_mode(manifest_path: Path, mtod: ManifestsTypeOfData) -> M
group_schema = {"repos": [str], schema.Optional("includes"): [str]}
# Note: gitlab and github_enterprise_url keys are ignored,
# and kept here only for backward compatibility reasons
on_switch_schema = schema.Use(validate_switch)
manifest_schema = schema.Schema(
{
"repos": [repo_schema],
schema.Optional("gitlab"): remote_git_server_schema,
schema.Optional("github_enterprise"): remote_git_server_schema,
schema.Optional("groups"): {str: group_schema},
schema.Optional("switch"): on_switch_schema,
}
)
try:
Expand Down
29 changes: 29 additions & 0 deletions tsrc/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Support for switch in manifest

'switch' can hold part of new default configuration
and such should only be activated by option '--switch'
when activated:
A) and present:
it should completely overwrite current configuration.
B) but not present:
the default (empty) configuration should be used

this is particulary usefull when switching
to new Manifest branch, no need to care about
current configuration anymore.

NOTE: original handling of configuration
should be adhered when no '--switch' is provided
"""

from typing import Any, List, Optional


class Switch:
def __init__(self, switch_config: Any) -> None:
self._config: Optional[Any] = None
self._groups: Optional[List[Any]] = None
if switch_config:
self._config = switch_config.get("config")
if self._config:
self._groups = self._config.get("groups")
1 change: 0 additions & 1 deletion tsrc/test/cli/test_dump_manifest__filter_bo_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ def test_only_manifest__on_workspace(
raise Exception("There should be only Manifest")
if count != 1:
raise Exception("Manifest processing error")
print("DEBUG PATH =", workspace_path)


def test_only_manifest__on_raw(
Expand Down
Loading
Loading