From 0d320d061379f3d7f121ae25ad9319ada94aaa9f Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Fri, 12 Apr 2024 00:09:55 +0200 Subject: [PATCH] pmbootstrap status: rework (MR 2294) Reimplement "pmbootstrap status" to be just a simple and useful status overview. The previous version ran a bunch of checks every time, and would fail on these even if pmaports was used for normal development: * "non-official" branch checked out in pmaports * pmaports.git is not clean The information about aports.git was also considered not so useful upon revisiting this command, since it is only used for "pmbootstrap aportgen". Most users don't need this, and if the user runs this command, it will tell if aports.git is outdated. All of the above made the previous version unpleasant to use and I suspect most people stopped using the command after trying it out a few times and seeing the irrelevant but loud NOK complaints. New version: $ pmbootstrap status Channel: edge (pmaports: master_staging_systemd) Device: qemu-amd64 (x86_64, kernel: virt) UI: console systemd: no (default for selected UI) Old version (without --details it only shows NOK checks): $ pmbootstrap status --details [00:55:20] *** CONFIG *** [00:55:20] Device: qemu-amd64 (x86_64, "QEMU amd64") [00:55:20] Kernel: virt [00:55:20] User Interface: console [00:55:20] [00:55:20] *** GIT REPOS *** [00:55:20] Path: /home/user/.local/var/pmbootstrap/cache_git [00:55:20] - aports_upstream (master) [00:55:20] - pmaports (master) [00:55:20] [00:55:20] *** CHECKS *** [00:55:20] [OK ] Chroots zapped recently (or non-existing) [00:55:20] [OK ] aports_upstream: on official channel branch [00:55:20] [OK ] aports_upstream: workdir is clean [00:55:20] [OK ] aports_upstream: tracking proper remote branch 'origin/master' [00:55:20] [OK ] aports_upstream: up to date with remote branch [00:55:20] [OK ] aports_upstream: remote information updated recently (via git fetch/pull) [00:55:20] [OK ] pmaports: on official channel branch [00:55:20] [OK ] pmaports: workdir is clean [00:55:20] [OK ] pmaports: tracking proper remote branch 'origin/master' [00:55:20] [OK ] pmaports: up to date with remote branch [00:55:20] [OK ] pmaports: remote information updated recently (via git fetch/pull) [00:55:20] [00:55:20] NOTE: chroot is still active (use 'pmbootstrap shutdown' as necessary) [00:55:20] DONE! --- pmb/config/__init__.py | 4 - pmb/config/other.py | 14 +++ pmb/helpers/frontend.py | 6 +- pmb/helpers/git.py | 20 ---- pmb/helpers/status.py | 177 +++++++----------------------------- pmb/parse/arguments.py | 4 +- test/test_helpers_git.py | 27 ------ test/test_helpers_status.py | 92 ------------------- 8 files changed, 54 insertions(+), 290 deletions(-) delete mode 100644 test/test_helpers_status.py diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 7cd45729..61b15bdc 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -1109,10 +1109,6 @@ git_repos = { "pmaports": "https://gitlab.com/postmarketOS/pmaports.git", } -# When a git repository is considered outdated (in seconds) -# (Measuring timestamp of FETCH_HEAD: https://stackoverflow.com/a/9229377) -git_repo_outdated = 3600 * 24 * 2 - # # APORTGEN # diff --git a/pmb/config/other.py b/pmb/config/other.py index f8d122bd..a10a2eb6 100644 --- a/pmb/config/other.py +++ b/pmb/config/other.py @@ -14,3 +14,17 @@ def is_systemd_selected(args): if args.systemd == "never": return False return pmb.helpers.ui.check_option(args, args.ui, "pmb:systemd") + + +def systemd_selected_str(args): + if "systemd" not in pmb.config.pmaports.read_config_repos(args): + return "no", "not supported by pmaports branch" + if pmb.helpers.ui.check_option(args, args.ui, "pmb:systemd-never"): + return "no", "not supported by selected UI" + if args.systemd == "always": + return "yes", "'always' selected in 'pmbootstrap init'" + if args.systemd == "never": + return "no", "'never' selected in 'pmbootstrap init'" + if pmb.helpers.ui.check_option(args, args.ui, "pmb:systemd"): + return "yes", "default for selected UI" + return "no", "default for selected UI" diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index bb2d164a..87b6ba9f 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -624,8 +624,10 @@ def lint(args): def status(args: Namespace) -> None: - if not pmb.helpers.status.print_status(args, args.details): - sys.exit(1) + pmb.helpers.status.print_status(args) + + # Do not print the DONE! line + sys.exit(0) def ci(args): diff --git a/pmb/helpers/git.py b/pmb/helpers/git.py index f04901b7..dab1b1a9 100644 --- a/pmb/helpers/git.py +++ b/pmb/helpers/git.py @@ -3,7 +3,6 @@ import configparser import logging import os -import time import pmb.build import pmb.chroot.apk @@ -229,25 +228,6 @@ def pull(args, name_repo): return 0 -def is_outdated(path): - # FETCH_HEAD always exists in repositories cloned by pmbootstrap. - # Usually it does not (before first git fetch/pull), but there is no good - # fallback. For exampe, getting the _creation_ date of .git/HEAD is non- - # trivial with python on linux (https://stackoverflow.com/a/39501288). - # Note that we have to assume here that the user had fetched the "origin" - # repository. If the user fetched another repository, FETCH_HEAD would also - # get updated, even though "origin" may be outdated. For pmbootstrap status - # it is good enough, because it should help the users that are not doing - # much with pmaports.git to know when it is outdated. People who manually - # fetch other repos should usually know that and how to handle that - # situation. - path_head = path + "/.git/FETCH_HEAD" - date_head = os.path.getmtime(path_head) - - date_outdated = time.time() - pmb.config.git_repo_outdated - return date_head <= date_outdated - - def get_topdir(args, path): """ :returns: a string with the top dir of the git repository, or an empty string if it's not a git repository. """ diff --git a/pmb/helpers/status.py b/pmb/helpers/status.py index c2f1b922..87107dca 100644 --- a/pmb/helpers/status.py +++ b/pmb/helpers/status.py @@ -1,165 +1,58 @@ -# Copyright 2023 Oliver Smith +# Copyright 2024 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later -import os -import logging - import pmb.config import pmb.config.workdir import pmb.helpers.git from argparse import Namespace -from typing import List, Tuple -def print_config(args: Namespace) -> None: - """ Print an overview of what was set in "pmbootstrap init". """ - logging.info("*** CONFIG ***") - info = args.deviceinfo - logging.info("Device: {} ({}, \"{}\")" - .format(args.device, info["arch"], info["name"])) +def print_status_line(key: str, value: str): + styles = pmb.config.styles + key = f"{styles['GREEN']}{key}{styles['END']}:" + padding = 17 - if pmb.parse._apkbuild.kernels(args, args.device): - logging.info("Kernel: " + args.kernel) - - if args.extra_packages != "none": - logging.info("Extra packages: {}".format(args.extra_packages)) - - logging.info("User Interface: {}".format(args.ui)) + print(f"{key.ljust(padding)} {value}") -def print_git_repos(args: Namespace) -> None: - logging.info("*** GIT REPOS ***") - logging.info("Path: {}/cache_git".format(args.work)) - for repo in pmb.config.git_repos.keys(): - path = pmb.helpers.git.get_path(args, repo) - if not os.path.exists(path): - continue +def print_channel(args: Namespace) -> None: + pmaports_cfg = pmb.config.pmaports.read_config(args) + channel = pmaports_cfg["channel"] - # Get branch name (if on branch) or current commit - ref = pmb.helpers.git.rev_parse(args, path, - extra_args=["--abbrev-ref"]) - if ref == "HEAD": - ref = pmb.helpers.git.rev_parse(args, path)[0:8] - - logging.info("- {} ({})".format(repo, ref)) - - -def print_checks_git_repo(args: Namespace, repo: str, details: bool=True) -> Tuple[int, str]: - """ Perform various checks on one checked out git repo. - :param details: if True, print each passing check (this is True by - default for the testsuite) - :returns: status, todo_msg - - status: integer, 0 if all passed, < 0 on failure - - msg_todo: message to help the user resolve the failure """ - def log_ok(msg_ok): - if details: - logging.info("[OK ] {}: {}".format(repo, msg_ok)) - - def log_nok_ret(status, msg_nok, msg_todo): - logging.warning("[NOK] {}: {}".format(repo, msg_nok)) - return (status, msg_todo) - - # On official branch - path = pmb.helpers.git.get_path(args, repo) - branches = pmb.helpers.git.get_branches_official(args, repo) + # Get branch name (if on branch) or current commit + path = pmb.helpers.git.get_path(args, "pmaports") ref = pmb.helpers.git.rev_parse(args, path, extra_args=["--abbrev-ref"]) - if ref not in branches: - return log_nok_ret(-1, "not on official channel branch", - "consider checking out: " + ", ".join(branches)) - log_ok("on official channel branch") + if ref == "HEAD": + ref = pmb.helpers.git.rev_parse(args, path)[0:8] - # Workdir clean if not pmb.helpers.git.clean_worktree(args, path): - return log_nok_ret(-2, "workdir is not clean", - "consider cleaning your workdir") - log_ok("workdir is clean") + ref += ", dirty" - # Tracking proper remote - remote_upstream = pmb.helpers.git.get_upstream_remote(args, repo) - branch_upstream = remote_upstream + "/" + ref - remote_ref = pmb.helpers.git.rev_parse(args, path, ref + "@{u}", - ["--abbrev-ref"]) - if remote_ref != branch_upstream: - return log_nok_ret(-3, "tracking unexpected remote branch", - "consider tracking remote branch '{}' instead of" - " '{}'".format(branch_upstream, remote_ref)) - log_ok("tracking proper remote branch '{}'".format(branch_upstream)) - - # Up to date - ref_branch = pmb.helpers.git.rev_parse(args, path, ref) - ref_branch_upstream = pmb.helpers.git.rev_parse(args, path, - branch_upstream) - if ref_branch != ref_branch_upstream: - return log_nok_ret(-4, "not up to date with remote branch", - "update with 'pmbootstrap pull'") - log_ok("up to date with remote branch") - - # Outdated remote information - if pmb.helpers.git.is_outdated(path): - return log_nok_ret(-5, "outdated remote information", - "update with 'pmbootstrap pull'") - log_ok("remote information updated recently (via git fetch/pull)") - - return (0, "") + value = f"{channel} (pmaports: {ref})" + print_status_line("Channel", value) -def print_checks_git_repos(args: Namespace, details: bool) -> List[str]: - """ Perform various checks on the checked out git repos. - :param details: if True, print each passing check - :returns: list of unresolved checklist items """ - ret = [] - for repo in pmb.config.git_repos.keys(): - path = pmb.helpers.git.get_path(args, repo) - if not os.path.exists(path): - continue - status, todo_msg = print_checks_git_repo(args, repo, details) - if status: - ret += ["{}: {}".format(repo, todo_msg)] - return ret +def print_device(args: Namespace) -> None: + kernel = "" + if pmb.parse._apkbuild.kernels(args, args.device): + kernel = f", kernel: {args.kernel}" + + value = f"{args.device} ({args.deviceinfo['arch']}{kernel})" + print_status_line("Device", value) -def print_checks_chroots_outdated(args: Namespace, details: bool) -> List[str]: - """ Check if chroots were zapped recently. - :param details: if True, print each passing check instead of a summary - :returns: list of unresolved checklist items """ - if pmb.config.workdir.chroots_outdated(args): - logging.info("[NOK] Chroots not zapped recently") - return ["Run 'pmbootstrap zap' to delete possibly outdated chroots"] - elif details: - logging.info("[OK ] Chroots zapped recently (or non-existing)") - return [] +def print_ui(args: Namespace) -> None: + print_status_line("UI", args.ui) -def print_checks(args: Namespace, details: bool) -> bool: +def print_systemd(args: Namespace) -> None: + yesno, reason = pmb.config.other.systemd_selected_str(args) + print_status_line("systemd", f"{yesno} ({reason})") + + +def print_status(args: Namespace) -> None: """ :param details: if True, print each passing check instead of a summary :returns: True if all checks passed, False otherwise """ - logging.info("*** CHECKS ***") - checklist = [] - checklist += print_checks_chroots_outdated(args, details) - checklist += print_checks_git_repos(args, details) - - # All OK - if not checklist: - if not details: - logging.info("All checks passed! \\o/") - logging.info("") - return True - - # Some NOK: print checklist - logging.info("") - logging.info("*** CHECKLIST ***") - for item in checklist: - logging.info("- " + item) - logging.info("- Run 'pmbootstrap status' to verify that all is resolved") - return False - - -def print_status(args: Namespace, details: bool=False) -> bool: - """ :param details: if True, print each passing check instead of a summary - :returns: True if all checks passed, False otherwise """ - print_config(args) - logging.info("") - print_git_repos(args) - logging.info("") - ret = print_checks(args, details) - - return ret + print_channel(args) + print_device(args) + print_ui(args) + print_systemd(args) diff --git a/pmb/parse/arguments.py b/pmb/parse/arguments.py index 2c5c7586..01bc0538 100644 --- a/pmb/parse/arguments.py +++ b/pmb/parse/arguments.py @@ -551,9 +551,7 @@ def arguments_lint(subparser): def arguments_status(subparser): ret = subparser.add_parser("status", - help="quick health check for the work dir") - ret.add_argument("--details", action="store_true", - help="list passing checks in detail, not as summary") + help="show a config and pmaports overview") return ret diff --git a/test/test_helpers_git.py b/test/test_helpers_git.py index 3f5488d3..46d62e5f 100644 --- a/test/test_helpers_git.py +++ b/test/test_helpers_git.py @@ -4,7 +4,6 @@ import os import sys import pytest import shutil -import time import pmb_test # noqa import pmb_test.const @@ -166,29 +165,3 @@ def test_pull(args, monkeypatch, tmpdir): run_git(["reset", "--hard", "origin/master"]) run_git(["commit", "--allow-empty", "-m", "new"], "remote") assert func(args, name_repo) == 0 - - -def test_is_outdated(tmpdir, monkeypatch): - func = pmb.helpers.git.is_outdated - - # Override time.time(): now is "100" - def fake_time(): - return 100.0 - monkeypatch.setattr(time, "time", fake_time) - - # Create .git/FETCH_HEAD - path = str(tmpdir) - os.mkdir(path + "/.git") - fetch_head = path + "/.git/FETCH_HEAD" - open(fetch_head, "w").close() - - # Set mtime to 90 - os.utime(fetch_head, times=(0, 90)) - - # Outdated (date_outdated: 90) - monkeypatch.setattr(pmb.config, "git_repo_outdated", 10) - assert func(path) is True - - # Not outdated (date_outdated: 89) - monkeypatch.setattr(pmb.config, "git_repo_outdated", 11) - assert func(path) is False diff --git a/test/test_helpers_status.py b/test/test_helpers_status.py deleted file mode 100644 index 7dbd95f4..00000000 --- a/test/test_helpers_status.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2023 Oliver Smith -# SPDX-License-Identifier: GPL-3.0-or-later -""" Test pmb/helpers/status.py """ -import os -import pytest -import shutil -import sys - -import pmb_test -import pmb_test.git -import pmb.config -import pmb.config.workdir - - -@pytest.fixture -def args(request): - import pmb.parse - sys.argv = ["pmbootstrap", "init"] - args = pmb.parse.arguments() - args.log = args.work + "/log_testsuite.txt" - pmb.helpers.logging.init(args) - request.addfinalizer(pmb.helpers.logging.logfd.close) - return args - - -def test_pmbootstrap_status(args, tmpdir): - """ High level testing of 'pmbootstrap status': run it twice, once with - a fine workdir, and once where one check is failing. """ - # Prepare empty workdir - work = str(tmpdir) - with open(work + "/version", "w") as handle: - handle.write(str(pmb.config.work_version)) - - # "pmbootstrap status" succeeds (pmb.helpers.run.user verifies exit 0) - pmbootstrap = pmb.config.pmb_src + "/pmbootstrap.py" - pmb.helpers.run.user(args, [pmbootstrap, "-w", work, "status", - "--details"]) - - # Mark chroot_native as outdated - with open(work + "/workdir.cfg", "w") as handle: - handle.write("[chroot-init-dates]\nnative = 1234\n") - - # "pmbootstrap status" fails - ret = pmb.helpers.run.user(args, [pmbootstrap, "-w", work, "status"], - check=False) - assert ret == 1 - - -def test_print_checks_git_repo(args, monkeypatch, tmpdir): - """ Test pmb.helpers.status.print_checks_git_repo """ - path, run_git = pmb_test.git.prepare_tmpdir(args, monkeypatch, tmpdir) - - # Not on official branch - func = pmb.helpers.status.print_checks_git_repo - name_repo = "test" - run_git(["checkout", "-b", "inofficial-branch"]) - status, _ = func(args, name_repo) - assert status == -1 - - # Workdir is not clean - run_git(["checkout", "master"]) - shutil.copy(__file__, path + "/test.py") - status, _ = func(args, name_repo) - assert status == -2 - os.unlink(path + "/test.py") - - # Tracking different remote - status, _ = func(args, name_repo) - assert status == -3 - - # Let master track origin/master - run_git(["checkout", "-b", "temp"]) - run_git(["branch", "-D", "master"]) - run_git(["checkout", "-b", "master", "--track", "origin/master"]) - - # Not up to date - run_git(["commit", "--allow-empty", "-m", "new"], "remote") - run_git(["fetch"]) - status, _ = func(args, name_repo) - assert status == -4 - - # Up to date - run_git(["pull"]) - status, _ = func(args, name_repo) - assert status == 0 - - # Outdated remote information - def is_outdated(path): - return True - monkeypatch.setattr(pmb.helpers.git, "is_outdated", is_outdated) - status, _ = func(args, name_repo) - assert status == -5