diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index c24c68e2bc36ccceefc30336711cfe1ce0730ceb..bae4fe79b8de067fc2fe6630861d06b6b42f35e4 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -46,7 +46,7 @@ pmaports_min_version = "7" # Version of the work folder (as asked during 'pmbootstrap init'). Increase # this number, whenever migration is required and provide the migration code, # see migrate_work_folder()). -work_version = 7 +work_version = 8 # Minimum required version of postmarketos-ondev (pmbootstrap install --ondev). # Try to support the current versions of all channels (edge, v21.03). When diff --git a/pmb/helpers/git.py b/pmb/helpers/git.py index bef4e70923c099ffb8abbeb0da58bf07b07ae4f5..c478a6f5a1bc819ee4db2c72258b7418182e8b8c 100644 --- a/pmb/helpers/git.py +++ b/pmb/helpers/git.py @@ -1,9 +1,11 @@ # Copyright 2024 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later import configparser +from enum import Enum from pathlib import Path +from typing import Final from pmb.core.context import get_context -from pmb.core.pkgrepo import pkgrepo_path +from pmb.core.pkgrepo import pkgrepo_default_path, pkgrepo_path from pmb.helpers import logging import os import re @@ -15,6 +17,7 @@ import pmb.helpers.pmaports import pmb.helpers.run from pmb.helpers.exceptions import NonBugError from pmb.meta import Cache +from pmb.types import PathString re_branch_aports = re.compile(r"^\d+\.\d\d+-stable$") re_branch_pmaports = re.compile(r"^v\d\d\.\d\d$") @@ -94,6 +97,12 @@ def clean_worktree(path: Path): return pmb.helpers.run.user_output(command, path) == "" +def list_remotes(aports: Path) -> list[str]: + command = ["git", "remote", "-v"] + output = pmb.helpers.run.user_output(command, aports, output="null") + return output.splitlines() + + def get_upstream_remote(aports: Path): """Find the remote, which matches the git URL from the config. @@ -101,17 +110,83 @@ def get_upstream_remote(aports: Path): """ name_repo = aports.parts[-1] urls = pmb.config.git_repos[name_repo] - command = ["git", "remote", "-v"] - output = pmb.helpers.run.user_output(command, aports, output="null") - for line in output.split("\n"): + lines = list_remotes(aports) + for line in lines: if any(u in line for u in urls): return line.split("\t", 1)[0] + raise NonBugError( f"{name_repo}: could not find remote name for any URL '{urls}' in git" f" repository: {aports}" ) +class RemoteType(Enum): + FETCH = "fetch" + PUSH = "push" + + @staticmethod + def from_git_output(git_remote_type: str) -> "RemoteType": + match git_remote_type: + case "(fetch)": + return RemoteType.FETCH + case "(push)": + return RemoteType.PUSH + case _: + raise ValueError(f'Unknown remote type "{git_remote_type}"') + + +def set_remote_url(repo: Path, remote_name: str, remote_url: str, remote_type: RemoteType) -> None: + command: list[PathString] = [ + "git", + "-C", + repo, + "remote", + "set-url", + remote_name, + remote_url, + "--push" if remote_type == RemoteType.PUSH else "--no-push", + ] + + pmb.helpers.run.user(command, output="stdout") + + +# Intentionally lower case for case-insensitive comparison +OUTDATED_GIT_REMOTES_HTTP: Final[list[str]] = ["https://gitlab.com/postmarketos/pmaports.git"] +OUTDATED_GIT_REMOTES_SSH: Final[list[str]] = ["git@gitlab.com:postmarketos/pmaports.git"] + + +def migrate_upstream_remote() -> None: + """Migrate pmaports git remote URL from gitlab.com to gitlab.postmarketos.org.""" + + repo = pkgrepo_default_path() + repo_name = repo.parts[-1] + lines = list_remotes(repo) + + current_git_remote_http: Final[str] = pmb.config.git_repos[repo_name][0] + current_git_remote_ssh: Final[str] = pmb.config.git_repos[repo_name][1] + + for line in lines: + if not line: + continue # Skip empty line at the end. + + remote_name, remote_url, remote_type_raw = line.split() + remote_type = RemoteType.from_git_output(remote_type_raw) + + if remote_url.lower() in OUTDATED_GIT_REMOTES_HTTP: + new_remote = current_git_remote_http + elif remote_url.lower() in OUTDATED_GIT_REMOTES_SSH: + new_remote = current_git_remote_ssh + else: + new_remote = None + + if new_remote: + logging.info( + f"Migrating to new {remote_type.value} URL (from {remote_url} to {new_remote})" + ) + set_remote_url(repo, remote_name, current_git_remote_http, remote_type) + + @Cache("aports") def parse_channels_cfg(aports: Path): """Parse channels.cfg from pmaports.git, origin/master branch. diff --git a/pmb/helpers/other.py b/pmb/helpers/other.py index 70e332e4afbe5b44aeec3964dcf3a028ea686754..1c2402dfa5b246e7e77027aacd07596768c9ec31 100644 --- a/pmb/helpers/other.py +++ b/pmb/helpers/other.py @@ -1,6 +1,7 @@ # Copyright 2023 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later from pmb.core.context import get_context +from pmb.core.pkgrepo import pkgrepo_default_path from pmb.helpers import logging import os from pathlib import Path @@ -111,6 +112,30 @@ def migrate_work_folder(): migrate_success(context.config.work, 7) current = 7 + if current == 7: + # Ask for confirmation + logging.info("Changelog:") + logging.info("* Moved from GitLab.com to gitlab.postmarketOS.org") + logging.info("Migration will do the following:") + logging.info("* Update your pmaports remote URL") + if not pmb.helpers.cli.confirm(): + raise RuntimeError("Aborted.") + + pmb.helpers.git.migrate_upstream_remote() + try: + pmb.helpers.git.get_upstream_remote(pkgrepo_default_path()) + except NonBugError: + logging.error( + "Couldn't find new upstream remote, migration probably failed." + " Please try updating the remote manually with:\n" + f" $ git -C '{pkgrepo_default_path()}' remote set-url origin 'https://gitlab.postmarketos.org/postmarketOS/pmaports.git'" + ) + raise NonBugError("Migration failed.") + + # Update version file + migrate_success(context.config.work, 8) + current = 8 + # Can't migrate, user must delete it if current != required: raise NonBugError(