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!
This commit is contained in:
parent
56dfdd4ad3
commit
0d320d0613
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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. """
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue