diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-25 11:56:21 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-25 11:56:21 +0200 |
| commit | 20d3c8e798085493f6f08a41f3793e29c56e5b54 (patch) | |
| tree | 6ecf20fa3514716feee763c5b0ca6f8412e789f2 | |
| parent | 9262725eca4ef63be9c7086fad1c59d27026caab (diff) | |
| download | mkhintfile-20d3c8e798085493f6f08a41f3793e29c56e5b54.tar.gz mkhintfile-20d3c8e798085493f6f08a41f3793e29c56e5b54.zip | |
feat: add --strip-version/-S and --versions
-S removes VERSION, DOWNLOAD and MD5SUM (plus arch-suffixed variants)
from hint files so slackrepo falls back to the repository's current
version, keeping every other modification. --check now skips hints
with no VERSION line instead of hitting the empty-pattern sed path.
--versions compares the SBo .info, newest built package, hint and
nvchecker upstream versions for one or more packages, coloured like
--list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | CHANGELOG.md | 23 | ||||
| -rw-r--r-- | CLAUDE.md | 26 | ||||
| -rw-r--r-- | README.md | 53 | ||||
| -rwxr-xr-x | mkhint | 199 | ||||
| -rw-r--r-- | mkhint.1.gz | bin | 4256 -> 4994 bytes | |||
| -rw-r--r-- | mkhint.1.md | 36 | ||||
| -rw-r--r-- | mkhint.bash-completion | 6 | ||||
| -rwxr-xr-x | tests/mkhint_test.sh | 308 |
8 files changed, 642 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a17d47..5f7f803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] + +### Added +- `--strip-version`/`-S`: remove the version pin *and* the version-dependent + download/checksum variables (`VERSION`, `DOWNLOAD`, `MD5SUM`, and their + `_x86_64` variants) from hint files so slackrepo falls back to the + repository's current version and download data, keeping every other + modification (`DELREQUIRES`, `NODOWNLOAD`, `ARCH`, bundled deps). Multiline + (backslash-continued) values are removed whole. No args = all hints; one or + more names = just those. Backs up a modified hint to `.bak`; a hint with none + of those variables is left untouched. Edits the hint only (no `.info` + cross-check). +- `--check`/`-C` now skips hints with no `VERSION` line instead of offering an + update, so stripped or `DELREQUIRES`-only hints don't trip the empty-version + replacement path. +- `--versions`: show and compare every known version for one or more packages — + the SBo `.info` `VERSION`, the newest built `*.txz` in `PACKAGES_DIR`, the + hint's `VERSION`, and (when an nvchecker `[pkg]` section exists) the latest + upstream version. Absent sources are omitted but the rest are still compared; + colours match `--list` (green = newest, magenta = built behind, yellow = all + match). With no nvchecker section the upstream check is skipped and a hint to + run `mkhint -C`/`-n <pkg>` is printed. + ## [1.4.0] - 2026-07-10 ### Added @@ -142,6 +142,23 @@ Test coverage: | T65 | `mkhint -v` prints `mkhint <version>`, exit 0 | | T66 | `mkhint --version` prints `mkhint <version>`, exit 0 | | T67 | `mkhint -V <ver> -n <pkg>` sets hint VERSION (rename works) | +| T76 | `-S <pkg>` — `VERSION` + `DOWNLOAD`/`MD5SUM` (and `_x86_64`) removed, other vars + `.bak` preserved | +| T84 | `-S` multiline `DOWNLOAD`/`MD5SUM` — whole blocks removed, no orphan continuation line | +| T77 | `-S` on a hint with no version-pinned vars — no-op, no `.bak` churn | +| T78 | `-S` no args — strips every hint that has a `VERSION` | +| T79 | `-S pkg1 pkg2` — only the named hints stripped | +| T80 | `-S <missing>` — exit 2 | +| T81 | `-S` re-run — idempotent, no new `.bak` | +| T82 | `-S` with `-V` — mutually-exclusive error, exit 1 | +| T83 | `-C` skips a VERSION-less hint (no empty-pattern sed crash) | +| T85 | `--versions` SBo/Repo/Hint equal — all three yellow | +| T86 | `--versions` hint newest — hint green, built behind magenta, SBo plain | +| T87 | `--versions` only SBo known — SBo line only | +| T88 | `--versions` only hint known — Hint line only | +| T89 | `--versions` unknown package — exit 2 | +| T90 | `--versions` multiple packages — both shown; no args exit 1 | +| T91 | `--versions` with nvchecker section — Upstream shown, green when newest | +| T92 | `--versions` no nvchecker section — note printed, exit 0 | When adding new features, add a corresponding test case to `tests/mkhint_test.sh`. @@ -158,6 +175,8 @@ When adding new features, add a corresponding test case to `tests/mkhint_test.sh - `--no-dl` / `-N`: downloads and recalculates checksums as normal, then appends `NODOWNLOAD=yes` after `MD5SUM_x86_64=`. Works with `--hintfile` or `--new`. Error if used alone. - `--fix-current` / `-F`: bulk sweep. Loads `PHANTOM_DEPS_FILE`, scans every `.info` in `REPO_DIR`, and for each package whose REQUIRES contains a phantom dep, ensures its hint carries the matching `DELREQUIRES`. No existing hint → create a minimal `DELREQUIRES="..."` file. Existing hint → back up to `.bak` and union the phantom deps into its `DELREQUIRES` line (dedup), leaving all other content untouched. Idempotent: if the deps are already present, the file is left alone and no `.bak` is written. No per-package prompts, safe under `set -e`. Mutually exclusive with `-V`/`-f`/`-n` (exit 1). Empty/missing list → "Nothing to do", exit 0. Helpers: `load_phantom_deps`, `phantom_deps_in_info`, `merge_delrequires`, `fix_current`. - `--new` phantom-dep hook: after commenting out REQUIRES, `create_new_hint_file` appends `DELREQUIRES="..."` for any phantom dep found in the `.info` REQUIRES. Same list as `--fix-current`. +- `--strip-version` / `-S`: removes the version pin *and* the version-dependent variables — `VERSION`, `DOWNLOAD`, `MD5SUM`, and their `_x86_64` (or any arch-suffix) variants — from hint files (bulk over `HINT_DIR` with no args, or one-or-more named packages), so slackrepo falls back to the repository's current version and download/checksum data while every other modification (`DELREQUIRES`, `NODOWNLOAD`, `ARCH`, bundled-dep notes) is kept. Multiline backslash-continued `DOWNLOAD`/`MD5SUM` values are removed whole (no orphan continuation lines) via `_strip_version_vars` (a `perl -i -0pe` slurp substitution `s#^(VERSION|DOWNLOAD|MD5SUM)(_[A-Za-z0-9_-]*)?="[^"]*"\n##mg`). A modified hint is backed up to `.bak` first; a hint with none of those variables is left untouched (idempotent, no `.bak` churn — the "no version pin to strip (skipped)" notice only prints for named targets, so a batch re-run stays quiet). Missing named hint → exit 2. Mutually exclusive with `-V`/`-f`/`-n` (exit 1). Helpers: `strip_version`, `_strip_version_vars`. `-S` edits the hint only — no `.info` cross-check, because unpinning is unconditional (there is nothing to compare). Because a stripped hint has no pinned version, `--check`/`-C` now skips hints with no `VERSION` line (reporting `skip <pkg>: no VERSION in hint`) instead of offering an update — this also fixes the empty-pattern `sed` abort that `update_hint_file`'s global version swap (`s/${old_version}/.../g`) would otherwise hit when `old_version` is empty. +- `--versions` (long-only): show and compare every known version for one or more package names — the SBo `.info` `VERSION`, the newest built `*.txz` in `PACKAGES_DIR` (via `repo_version`), the hint's `VERSION`, and the latest upstream version when an nvchecker `[pkg]` section exists. One line per present source (`SBo:`/`Repo:`/`Hint:`/`Upstream:`); absent sources are omitted but the remaining ones are still compared. Colours match `--list`: green = newest (via `sort -V` over `_normalize_version`-normalized values), magenta = built `Repo:` behind the newest, yellow = all present match, plain when only one source is present. Upstream is refreshed first (single pkg → `nvchecker -e`, two+ → one full scan) only when at least one package has a section; with no section the check is skipped and a non-interactive note suggests `mkhint -C` / `mkhint -n <pkg>`; a section with no keyfile result shows `(no nvchecker result)`. No information at all → exit 2; no args → exit 1; mutually exclusive with `-V`/`-f`/`-n`. Handler `show_versions` (sibling of `show_info`). A legend prints once when any package had ≥2 sources. - `--check` bundled-dep reconcile: `BUNDLE_MANIFEST_FILE` entries are 3-field, `<pkg> <mode> <rest>` (field 2 = `url` or `sha`; `bundle_mode <pkg>` reads it, the dispatcher `reconcile_bundle_deps` routes on it). `pkg_has_manifest` is true @@ -262,8 +281,11 @@ retagged, so everything before the tag must be verified first): -9 -n -f mkhint.1` (commit the regenerated `mkhint.1.gz`). 3. **Test.** `bash tests/mkhint_test.sh` must be all-pass. Every feature ships its own test case. -4. **Deploy to the VM and smoke-test there BEFORE tagging.** `scp` the three - files to `buildsystem` (`/usr/local/bin/mkhint`, +4. **Deploy to the VM and smoke-test there BEFORE tagging.** `./deploy.sh` does + this automatically (scp + chmod + md5 verification + `mkhint --version` + smoke check; `./deploy.sh <host>` or `DEPLOY_HOST=<host>` to override the + `buildsystem` alias). Manual equivalent: `scp` the three files to + `buildsystem` (`/usr/local/bin/mkhint`, `/etc/bash_completion.d/mkhint`, `/usr/local/man/man1/mkhint.1.gz`), confirm `mkhint --version` and md5s match local, and exercise the new feature on the VM's real data (it has real hints and built packages the mock suite can't @@ -260,6 +260,59 @@ contains a phantom dep, it adds the matching `DELREQUIRES` for you. If the list file is missing or empty, both features are no-ops. +### Strip the pinned version + +A hint's `VERSION="x.y.z"` line pins that package to a specific version. When the +repository moves on, that pin goes stale even though the rest of the hint (a +`DELREQUIRES`, a `NODOWNLOAD=yes`, a custom `ARCH`, …) is still wanted. +`--strip-version` (`-S`) removes the version pin *and* the version-dependent +download/checksum variables — `VERSION`, `DOWNLOAD`, `MD5SUM`, and their +`_x86_64` variants — so slackrepo falls back to the repository's current version +and its own download/checksum data, while every other edit is kept. Multiline +(backslash-continued) `DOWNLOAD`/`MD5SUM` values are removed whole. With no +arguments it sweeps every hint in the directory; with one or more package names +it strips just those. A modified hint is backed up to `.bak` first, and a hint +that already has none of those variables is left alone (no `.bak` churn): + +```bash +mkhint --strip-version # strip all hints +mkhint -S # short form +mkhint -S pkg1 pkg2 # strip just the named hints +``` + +`--strip-version` edits the hint file only; it does not consult the repository +`.info` (there is no decision to make — unpinning always means dropping the +pinned version and download data, regardless of what the `.info` currently +holds). Because a stripped hint has no pinned version to compare, `--check` +skips it (reporting `no VERSION in hint`) rather than offering an update. + +### Show all versions for a package + +`--versions` (long form only) reports every version mkhint can find for one or +more package names and compares them with the same colour rules as `--list`: + +```bash +mkhint --versions llama.cpp-vulkan +mkhint --versions pkg1 pkg2 +``` + +For each package it prints one line per known source, omitting any that are +absent but still comparing the rest: + +- `SBo:` — the `VERSION` from the package's `.info` in the repository. +- `Repo:` — the newest built `*.txz` version in `PACKAGES_DIR`. +- `Hint:` — the hint file's `VERSION` (if a hint exists). +- `Upstream:` — the latest version from nvchecker, when the package has a + `[pkg]` section in the nvchecker config. A targeted `nvchecker -e` refresh + runs first for a single package (a full scan for several). With no section, + the upstream check is skipped and a hint to run `mkhint -C` or + `mkhint -n <pkg>` is shown instead. + +Colour coding matches `--list`: the newest version is **green**, the built +`Repo:` version is **magenta** when it is behind the newest, and every source is +**yellow** when all known versions match. A single known source is shown plain. +A package with no information at all exits 2. + ### Help ```bash @@ -12,6 +12,8 @@ # ./mkhint --clean Remove .bak files from HINT_DIR # ./mkhint --no-dl --hintfile FILE Update hint, skip downloads, add NODOWNLOAD=yes # ./mkhint --no-dl --new FILE Create hint with NODOWNLOAD=yes +# ./mkhint --strip-version [FILE...] Remove VERSION + DOWNLOAD/MD5SUM pin +# ./mkhint --versions PKG... Show and compare SBo/built/hint/upstream versions # ./mkhint --help Show this help set -e @@ -79,6 +81,8 @@ Options: --clean, -c Remove all .bak files from HINT_DIR --check, -C [FILE...] Check hints for upstream updates via nvchecker, update interactively --fix-current, -F Add/merge DELREQUIRES for -current phantom deps across the whole repo + --strip-version, -S [FILE...] Remove VERSION + DOWNLOAD/MD5SUM pin; no args = all hints, FILE... = named + --versions PKG... Show and compare SBo/built/hint/upstream versions for a package --delete, -d FILE Delete a hint file (and .bak if present) --no-dl, -N Skip downloads; add NODOWNLOAD=yes to hint file (use with -f or -n) --info, -i FILE Show category/program path and README for a package @@ -472,6 +476,56 @@ fix_current() { echo "Done. $count package(s) with phantom deps." } +# Remove the version-pinned variables (VERSION, DOWNLOAD, MD5SUM and their +# arch-suffix variants like DOWNLOAD_x86_64) from a hint file. Multiline +# backslash-continued values are removed whole, not just their first line. +_strip_version_vars() { + local file="$1" + perl -i -0pe 's#^(VERSION|DOWNLOAD|MD5SUM)(_[A-Za-z0-9_-]*)?="[^"]*"\n##mg' "$file" +} + +# Strip the version pin from hint files so slackrepo falls back to the +# repository's current version. Removes VERSION plus the version-dependent +# DOWNLOAD*/MD5SUM* variables; everything else (DELREQUIRES, NODOWNLOAD, ARCH, +# bundled-dep notes, etc.) is kept. A modified hint is backed up to .bak first +# (mkhint convention); a hint with none of those variables is left untouched. +# Usage: strip_version [pkg...] (no args = all *.hint in HINT_DIR) +strip_version() { + local targets=() + if [[ $# -gt 0 ]]; then + targets=("$@") + else + local f b + for f in "$HINT_DIR"/*.hint; do + [[ -f "$f" ]] || continue + b=$(basename "$f"); targets+=("${b%.hint}") + done + fi + + local pkg hint stripped=0 named=0 + [[ $# -gt 0 ]] && named=1 + for pkg in "${targets[@]}"; do + if [[ "$pkg" == *.hint ]]; then + hint="${HINT_DIR%/}/${pkg}" + else + hint="${HINT_DIR%/}/${pkg}.hint" + fi + if [[ ! -f "$hint" ]]; then + echo "Error: Hint file does not exist: $hint" >&2 + exit 2 + fi + if ! grep -qE '^(VERSION|DOWNLOAD|MD5SUM)' "$hint"; then + [[ $named -eq 1 ]] && echo " ${pkg%.hint}: no version pin to strip (skipped)" + continue + fi + cp "$hint" "${hint}.bak" + _strip_version_vars "$hint" + echo " ${pkg%.hint}: stripped VERSION + DOWNLOAD/MD5SUM" + (( stripped++ )) || true + done + echo "Done. $stripped hint(s) stripped." +} + # ── bundled-dep manifest handling ───────────────────────────────────────────── # Reconcile a bundle package's extra DOWNLOAD lines against an upstream deps # manifest (opt-in via BUNDLE_MANIFEST_FILE). See docs spec 2026-07-07. @@ -1146,6 +1200,122 @@ show_info() { exit 0 } +# --versions: show every known version for one or more packages and compare them. +# Sources: the SBo .info VERSION, the newest built package in PACKAGES_DIR, the +# hint's VERSION, and (when an nvchecker [pkg] section exists) the latest +# upstream version. A source with no version is omitted from the version list, +# but the remaining ones are still compared. Colors match --list: green = +# newest, magenta = built package behind, yellow = all match. +show_versions() { + if [[ $# -eq 0 ]]; then + echo "Error: --versions requires at least one package name" >&2 + exit 1 + fi + local -a pkgs=("$@") + + local y_on="" g_on="" m_on="" off="" + if [[ -n "$MKHINT_FORCE_COLOR" || -t 1 ]]; then + if command -v tput &>/dev/null && tput setaf 2 &>/dev/null; then + g_on=$(tput setaf 2); y_on=$(tput setaf 3); m_on=$(tput setaf 5); off=$(tput sgr0) + else + g_on=$'\033[32m'; y_on=$'\033[33m'; m_on=$'\033[35m'; off=$'\033[0m' + fi + fi + + # Refresh upstream data once for the whole invocation: a single package gets + # a targeted `nvchecker -e`, two or more get one full scan. Skip entirely + # when no package has a section (or nvchecker/the config is unavailable). + local any_section=0 p + for p in "${pkgs[@]}"; do + _has_nvchecker_section "$p" && { any_section=1; break; } + done + if [[ $any_section -eq 1 ]] && command -v nvchecker &>/dev/null && [[ -f "$NVCHECKER_CONFIG" ]]; then + if [[ ${#pkgs[@]} -eq 1 ]]; then + nvchecker -c "$NVCHECKER_CONFIG" -e "${pkgs[0]}" >&2 || true + else + nvchecker -c "$NVCHECKER_CONFIG" >&2 || true + fi + fi + + local first=1 pkg saw_multi=0 + for pkg in "${pkgs[@]}"; do + [[ $first -eq 1 ]] || echo "" + first=0 + + local info_file category sbo="" + info_file=$(find "$REPO_DIR" -mindepth 2 -name "${pkg}.info" 2>/dev/null | head -1) + if [[ -n "$info_file" && -f "$info_file" ]]; then + sbo=$(grep '^VERSION' "$info_file" | cut -d '"' -f2) + category=$(basename "$(dirname "$(dirname "$info_file")")") + fi + + local repo="" + repo=$(repo_version "$pkg") + + local hint="" + if [[ -f "${HINT_DIR%/}/${pkg}.hint" ]]; then + hint=$(grep '^VERSION' "${HINT_DIR%/}/${pkg}.hint" | cut -d '"' -f2) + fi + + # Upstream (already refreshed above): only when a section exists. + local upstream="" upstream_note="" + if _has_nvchecker_section "$pkg"; then + upstream=$(nvchecker_latest "$pkg") || upstream_note="(no nvchecker result)" + else + upstream_note="(no nvchecker section — run 'mkhint -C' or 'mkhint -n $pkg')" + fi + + if [[ -z "$sbo" && -z "$repo" && -z "$hint" && -z "$upstream" ]]; then + echo "Error: no version information found for $pkg" >&2 + exit 2 + fi + + local header="$pkg" + [[ -n "$category" ]] && header="${category}/${pkg}" + printf '%s%s%s\n' "$g_on" "$header" "$off" + + # Parallel arrays of the present sources, in display order. + local -a labels=() raws=() nrms=() isrepo=() + [[ -n "$sbo" ]] && { labels+=(SBo:); raws+=("$sbo"); nrms+=("$(_normalize_version "$sbo")"); isrepo+=(0); } + [[ -n "$repo" ]] && { labels+=(Repo:); raws+=("$repo"); nrms+=("$(_normalize_version "$repo")"); isrepo+=(1); } + [[ -n "$hint" ]] && { labels+=(Hint:); raws+=("$hint"); nrms+=("$(_normalize_version "$hint")"); isrepo+=(0); } + [[ -n "$upstream" ]] && { labels+=(Upstream:); raws+=("$upstream"); nrms+=("$(_normalize_version "$upstream")"); isrepo+=(0); } + + # Comparison over the present versions. + local multi=0 all_equal=0 newest="" + if [[ ${#nrms[@]} -gt 1 ]]; then + multi=1 + saw_multi=1 + newest=$(printf '%s\n' "${nrms[@]}" | sort -V | tail -1) + all_equal=1 + local n + for n in "${nrms[@]}"; do + [[ "$n" == "$newest" ]] || all_equal=0 + done + fi + + local i + for (( i=0; i<${#labels[@]}; i++ )); do + local col="" + if [[ $multi -eq 1 ]]; then + if [[ $all_equal -eq 1 ]]; then + col="$y_on" + elif [[ "${nrms[$i]}" == "$newest" ]]; then + col="$g_on" + elif [[ ${isrepo[$i]} -eq 1 ]]; then + col="$m_on" + fi + fi + printf ' %-9s %s%s%s\n' "${labels[$i]}" "$col" "${raws[$i]}" "${col:+$off}" + done + [[ -n "$upstream_note" ]] && printf ' %-9s %s\n' "Upstream:" "$upstream_note" + done + + if [[ $saw_multi -eq 1 && -n "$g_on" ]]; then + echo "(green = newest; magenta = built package behind; yellow = all match)" + fi +} + # Emit the TOML section label for a package: bare if the name is a valid # bare key ([A-Za-z0-9_] only), otherwise double-quoted. nvchecker (and TOML) # require quoting for names containing '.', '-', etc. @@ -1723,6 +1893,7 @@ check_updates() { for pkg in "${targets[@]}"; do local hintpath="${HINT_DIR%/}/${pkg}.hint" [[ -f "$hintpath" ]] || { echo "skip ${pkg}: no hint file"; continue; } + grep -q '^VERSION=' "$hintpath" || { echo "skip ${pkg}: no VERSION in hint"; continue; } local current; current=$(grep '^VERSION=' "$hintpath" | sed 's/VERSION="//;s/"$//') local latest if ! latest=$(nvchecker_latest "$pkg"); then @@ -1852,8 +2023,8 @@ check_updates() { # Main function main() { local parsed - parsed=$(getopt -o vV:f:n:i:lcCdNhRF \ - --long version,set-version:,hintfile:,new:,info:,list,clean,check,delete,no-dl,help,review,fix-current,force \ + parsed=$(getopt -o vV:f:n:i:lcCdNhRFS \ + --long version,set-version:,hintfile:,new:,info:,list,clean,check,delete,no-dl,help,review,fix-current,force,strip-version,versions \ -n 'mkhint' -- "$@") || { show_help; exit 1; } eval set -- "$parsed" @@ -1900,6 +2071,14 @@ main() { COMMAND="fix-current" shift ;; + --strip-version|-S) + COMMAND="strip-version" + shift + ;; + --versions) + COMMAND="versions" + shift + ;; --delete|-d) COMMAND="delete" shift @@ -1968,11 +2147,21 @@ main() { exit 1 fi + if [[ "$COMMAND" == "strip-version" && ( -n "$VERSION" || -n "$HINT_FILE" || -n "$NEW_HINT_FILE" ) ]]; then + echo "Error: --strip-version cannot be combined with --set-version/--hintfile/--new" >&2 + exit 1 + fi + if [[ "$COMMAND" == "info" && ( -n "$VERSION" || -n "$HINT_FILE" || -n "$NEW_HINT_FILE" ) ]]; then echo "Error: --info cannot be combined with --set-version/--hintfile/--new" >&2 exit 1 fi + if [[ "$COMMAND" == "versions" && ( -n "$VERSION" || -n "$HINT_FILE" || -n "$NEW_HINT_FILE" ) ]]; then + echo "Error: --versions cannot be combined with --set-version/--hintfile/--new" >&2 + exit 1 + fi + if [[ $FORCE -eq 1 && "$COMMAND" != "check" ]]; then echo "Error: --force is only valid with --check" >&2 exit 1 @@ -2019,6 +2208,12 @@ main() { fix-current) fix_current ;; + strip-version) + strip_version "${DELETE_HINT_FILES[@]}" + ;; + versions) + show_versions "${DELETE_HINT_FILES[@]}" + ;; info) show_info "$INFO_PKG" ;; diff --git a/mkhint.1.gz b/mkhint.1.gz Binary files differindex ce2c222..3828d31 100644 --- a/mkhint.1.gz +++ b/mkhint.1.gz diff --git a/mkhint.1.md b/mkhint.1.md index 4be6ff4..fdc98b9 100644 --- a/mkhint.1.md +++ b/mkhint.1.md @@ -16,6 +16,10 @@ mkhint - manage hint files for slackrepo scripts **mkhint** **\--fix-current** +**mkhint** **\--strip-version** \[*FILE*...] + +**mkhint** **\--versions** *PKG*... + **mkhint** **\--list** \[*FILE*...] **mkhint** **\--review** \[*FILE*...] @@ -74,13 +78,38 @@ present. **\--check**, **-C** \[*FILE*...] : Check all (or the named) hints for upstream updates via **nvchecker** and apply them interactively. With one package, uses `nvchecker -e`; with two or -more, one full scan. Mutually exclusive with **-V**. +more, one full scan. Hints with no `VERSION` line are skipped. Mutually +exclusive with **-V**. **\--fix-current**, **-F** : Sweep the whole repository. For every package whose `REQUIRES` contains a phantom dependency, ensure its hint carries the matching `DELREQUIRES`. Idempotent. Mutually exclusive with **-V**, **-f**, **-n**. +**\--strip-version**, **-S** \[*FILE*...] +: Remove the version pin and its version-dependent variables — `VERSION`, +`DOWNLOAD`, `MD5SUM`, and their `_x86_64` variants — from hint files so +slackrepo falls back to the repository's current version and download data, +keeping every other modification (`DELREQUIRES`, `NODOWNLOAD`, `ARCH`, +bundled-dep notes). Multiline (backslash-continued) values are removed whole. +With no arguments, all hints in `HINT_DIR`; with names, only those. A modified +hint is backed up to `.bak` first; a hint with none of those variables is left +untouched (no `.bak` churn). Idempotent; edits the hint only (no `.info` +cross-check). Mutually exclusive with **-V**, **-f**, **-n**. + +**\--versions** *PKG*... +: Show every known version for one or more packages and compare them. One line +per known source, absent sources omitted: `SBo:` (the `.info` `VERSION`), +`Repo:` (the newest built `*.txz` in `PACKAGES_DIR`), `Hint:` (the hint's +`VERSION`), and `Upstream:` (the latest from **nvchecker**, shown only when the +package has a section in the nvchecker config; a targeted `nvchecker -e` runs +first for a single package, a full scan for several). With no section, the +upstream check is skipped and a hint to run **\--check** or **\--new** is +printed. Colour coding matches **\--list**: newest is green, the built `Repo:` +version is magenta when behind, all sources are yellow when every known version +matches, and a single known source is plain. A package with no information at +all exits 2. Mutually exclusive with **-V**, **-f**, **-n**. + **\--list**, **-l** \[*FILE*...] : List all hint files with their hint version, `.info` version, a `DelReq` marker (populated `DELREQUIRES`), and a `NoDL` marker @@ -160,6 +189,9 @@ List, review, sweep, delete, clean: mkhint --list mypackage mkhint --review mkhint --fix-current + mkhint --strip-version # all hints + mkhint --strip-version pkg1 pkg2 # named hints + mkhint --versions mypackage # SBo + built + hint + upstream mkhint --delete mypackage mkhint --info mypackage mkhint --clean @@ -237,7 +269,7 @@ manifest during **\--check**. See BUNDLED DEPENDENCIES. A bash completion script ships with **mkhint** (install to */etc/bash-completion.d/mkhint*). It completes long and short options and, for -**-f**, **-n**, **-d**, **-C**, **-R**, and **-l**, the package names from their +**-f**, **-n**, **-d**, **-C**, **-S**, **-R**, **--versions**, and **-l**, the package names from their respective directories. With **-f** *package* already on the command line, **-V** *TAB* suggests the current `VERSION` from that package's hint. The completion script sources the same *~/.config/mkhint/config*, so its paths stay diff --git a/mkhint.bash-completion b/mkhint.bash-completion index c7c4a14..22c8df9 100644 --- a/mkhint.bash-completion +++ b/mkhint.bash-completion @@ -14,7 +14,7 @@ _mkhintfile_completions() { hint_dir="${HINT_DIR:-$hint_dir}" repo_dir="${repo_dir%/}"; hint_dir="${hint_dir%/}" - local all_flags="--version -v --set-version -V --hintfile -f --new -n --list -l --review -R --clean -c --check -C --fix-current -F --delete -d --no-dl -N --info -i --force --help -h" + local all_flags="--version -v --set-version -V --hintfile -f --new -n --list -l --review -R --clean -c --check -C --fix-current -F --strip-version -S --versions --delete -d --no-dl -N --info -i --force --help -h" # -R/--review and -l/--list take any number of hint names; complete them repeatedly. local w in_review="" @@ -31,7 +31,7 @@ _mkhintfile_completions() { fi case "$prev" in - --new|-n|--info|-i) + --new|-n|--info|-i|--versions) # Packages live at REPO_DIR/<category>/<pkg>/; complete the pkg dir # names. Strip the trailing slash and category prefix with param # expansion (no per-dir basename fork — the repo has thousands). @@ -41,7 +41,7 @@ _mkhintfile_completions() { done COMPREPLY=($(compgen -W "${words[*]}" -- "$cur")) ;; - --hintfile|-f|--delete|-d|--check|-C) + --hintfile|-f|--delete|-d|--check|-C|--strip-version|-S) local -a words=() for f in "$hint_dir"/*.hint; do [[ -f "$f" ]] && words+=("$(basename "${f%.hint}")") diff --git a/tests/mkhint_test.sh b/tests/mkhint_test.sh index a21ff86..2e885dd 100755 --- a/tests/mkhint_test.sh +++ b/tests/mkhint_test.sh @@ -1297,6 +1297,314 @@ code=$? set -e assert_exit_code "mutually exclusive" 1 "$code" +# ── T76: -S strips VERSION + DOWNLOAD/MD5SUM, keeps other edits, backs up ────── +echo "" +echo "T76: --strip-version removes VERSION + DOWNLOAD/MD5SUM, preserves other content" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.9.0" +ARCH="x86_64" +DELREQUIRES="rust-opt" +DOWNLOAD="https://curl.se/download/curl-8.9.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="https://curl.se/download/curl-8.9.0.tar.gz" +MD5SUM_x86_64="abc123def456abc123def456abc123de" +NODOWNLOAD=yes +EOF +run_mkhint -S curl +assert_not_contains "VERSION line removed" "$MOCK_HINT/curl.hint" '^VERSION=' +assert_not_contains "DOWNLOAD removed" "$MOCK_HINT/curl.hint" '^DOWNLOAD' +assert_not_contains "MD5SUM removed" "$MOCK_HINT/curl.hint" '^MD5SUM' +assert_contains "ARCH preserved" "$MOCK_HINT/curl.hint" 'ARCH="x86_64"' +assert_contains "DELREQUIRES preserved" "$MOCK_HINT/curl.hint" 'DELREQUIRES="rust-opt"' +assert_contains "NODOWNLOAD preserved" "$MOCK_HINT/curl.hint" 'NODOWNLOAD=yes' +assert_file_exists "backup made" "$MOCK_HINT/curl.hint.bak" +assert_contains "backup has old VERSION" "$MOCK_HINT/curl.hint.bak" 'VERSION="8.9.0"' +assert_contains "backup has old DOWNLOAD" "$MOCK_HINT/curl.hint.bak" 'curl-8.9.0.tar.gz' + +# ── T84: -S removes whole multiline DOWNLOAD/MD5SUM blocks ──────────────────── +echo "" +echo "T84: --strip-version removes multiline DOWNLOAD/MD5SUM blocks whole" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/nvim.hint" << 'EOF' +VERSION="0.13.0" +DOWNLOAD="https://github.com/neovim/neovim/archive/v0.13.0/neovim-0.13.0.tar.gz \ + https://github.com/tree-sitter/tree-sitter/archive/v0.26.7/tree-sitter-0.26.7.tar.gz" +MD5SUM="aaa \ + bbb" +ARCH="x86_64" +DELREQUIRES="rust-opt" +EOF +run_mkhint -S nvim +assert_not_contains "VERSION removed" "$MOCK_HINT/nvim.hint" '^VERSION=' +assert_not_contains "DOWNLOAD block removed" "$MOCK_HINT/nvim.hint" '^DOWNLOAD' +assert_not_contains "MD5SUM block removed" "$MOCK_HINT/nvim.hint" '^MD5SUM' +assert_not_contains "no orphan continuation URL" "$MOCK_HINT/nvim.hint" 'tree-sitter' +assert_contains "ARCH preserved" "$MOCK_HINT/nvim.hint" 'ARCH="x86_64"' +assert_contains "DELREQUIRES preserved" "$MOCK_HINT/nvim.hint" 'DELREQUIRES="rust-opt"' + +# ── T77: -S on a VERSION-less hint → no-op, no .bak churn ───────────────────── +echo "" +echo "T77: --strip-version on versionless hint → no-op, no .bak" +cat > "$MOCK_HINT/novers.hint" << 'EOF' +ARCH="x86_64" +DELREQUIRES="rust-opt" +EOF +rm -f "$MOCK_HINT/novers.hint.bak" +out=$(run_mkhint -S novers 2>&1) +assert_file_not_exists "no .bak churn" "$MOCK_HINT/novers.hint.bak" +echo "$out" | grep -q "no version pin to strip" \ + && { echo " PASS: reports no version pin to strip"; (( PASS++ )); } \ + || { echo " FAIL: skip message missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T77 skip msg"); } +assert_contains "hint untouched" "$MOCK_HINT/novers.hint" 'DELREQUIRES="rust-opt"' + +# ── T78: -S batch (no args) strips every VERSION-carrying hint ──────────────── +echo "" +echo "T78: --strip-version with no args strips all hints that have a VERSION" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.9.0" +ARCH="x86_64" +EOF +cat > "$MOCK_HINT/clion.hint" << 'EOF' +VERSION="2025.5" +ARCH="x86_64" +EOF +cat > "$MOCK_HINT/novers.hint" << 'EOF' +ARCH="x86_64" +DELREQUIRES="rust-opt" +EOF +run_mkhint -S +assert_not_contains "curl VERSION stripped" "$MOCK_HINT/curl.hint" '^VERSION=' +assert_not_contains "clion VERSION stripped" "$MOCK_HINT/clion.hint" '^VERSION=' +assert_contains "novers untouched" "$MOCK_HINT/novers.hint" 'DELREQUIRES="rust-opt"' + +# ── T79: -S pkg1 pkg2 strips only the named hints ───────────────────────────── +echo "" +echo "T79: --strip-version two named hints, unrelated hint untouched" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.9.0" +ARCH="x86_64" +EOF +cat > "$MOCK_HINT/clion.hint" << 'EOF' +VERSION="2025.5" +ARCH="x86_64" +EOF +cat > "$MOCK_HINT/wget.hint" << 'EOF' +VERSION="1.25.0" +ARCH="x86_64" +EOF +run_mkhint -S curl clion +assert_not_contains "curl stripped" "$MOCK_HINT/curl.hint" '^VERSION=' +assert_not_contains "clion stripped" "$MOCK_HINT/clion.hint" '^VERSION=' +assert_contains "wget left alone" "$MOCK_HINT/wget.hint" 'VERSION="1.25.0"' + +# ── T80: -S on missing hint → exit 2 ────────────────────────────────────────── +echo "" +echo "T80: --strip-version on nonexistent hint → exit 2" +set +e +run_mkhint -S ghost_package 2>/dev/null +code=$? +set -e +assert_exit_code "strip missing exits 2" 2 "$code" + +# ── T81: -S idempotent → second run creates no new .bak ─────────────────────── +echo "" +echo "T81: --strip-version idempotent on second run" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.9.0" +ARCH="x86_64" +EOF +run_mkhint -S curl +rm -f "$MOCK_HINT/curl.hint.bak" +run_mkhint -S curl +assert_file_not_exists "no .bak churn on re-run" "$MOCK_HINT/curl.hint.bak" + +# ── T82: -S combined with -V → mutually-exclusive error exit 1 ──────────────── +echo "" +echo "T82: --strip-version with -V → exit 1" +set +e +run_mkhint -S -V 1.0.0 2>/dev/null +code=$? +set -e +assert_exit_code "strip + -V exits 1" 1 "$code" + +# ── T83: -C skips a VERSION-less hint instead of crashing ───────────────────── +echo "" +echo "T83: --check skips versionless hints (no empty-pattern sed crash)" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/novers.hint" << 'EOF' +ARCH="x86_64" +DELREQUIRES="rust-opt" +EOF +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "novers": { "version": "9.9.9" } } } +EOF +set +e +out=$(run_mkhint -C novers < <(printf 'Y\n') 2>&1) +code=$? +set -e +assert_exit_code "check versionless exits 0" 0 "$code" +echo "$out" | grep -q "no VERSION in hint" \ + && { echo " PASS: reports skip for versionless hint"; (( PASS++ )); } \ + || { echo " FAIL: skip message missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T83 skip msg"); } +assert_not_contains "hint not re-pinned" "$MOCK_HINT/novers.hint" '^VERSION=' + +# ── T85: --versions three equal sources → all yellow ────────────────────────── +echo "" +echo "T85: --versions SBo/Repo/Hint equal → all yellow" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +EOF +seed_pkg network curl 8.5.0 +out=$(MKHINT_FORCE_COLOR=1 run_mkhint --versions curl 2>&1) +echo "$out" | grep -q 'SBo:.*8\.5\.0' && echo "$out" | grep -q 'Repo:.*8\.5\.0' && echo "$out" | grep -q 'Hint:.*8\.5\.0' \ + && { echo " PASS: SBo/Repo/Hint shown"; (( PASS++ )); } \ + || { echo " FAIL: sources missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T85 sources"); } +[[ $(echo "$out" | grep -c $'\033\[33m') -eq 3 ]] \ + && { echo " PASS: all three yellow"; (( PASS++ )); } \ + || { echo " FAIL: expected 3 yellow lines"; echo "$out" | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T85 yellow"); } + +# ── T86: --versions hint newest → green; built behind → magenta ─────────────── +echo "" +echo "T86: --versions hint newest → green, built behind → magenta, SBo plain" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.6.0" +ARCH="x86_64" +EOF +seed_pkg network curl 8.4.0 +out=$(MKHINT_FORCE_COLOR=1 run_mkhint --versions curl 2>&1) +echo "$out" | grep -q $'\033\[32m8\.6\.0' \ + && { echo " PASS: hint newest green"; (( PASS++ )); } \ + || { echo " FAIL: hint not green"; echo "$out" | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T86 green"); } +echo "$out" | grep -q $'\033\[35m8\.4\.0' \ + && { echo " PASS: built behind magenta"; (( PASS++ )); } \ + || { echo " FAIL: repo not magenta"; echo "$out" | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T86 magenta"); } +echo "$out" | grep 'SBo:' | grep -q $'\033' \ + && { echo " FAIL: SBo should be plain"; (( FAIL++ )); ERRORS+=("T86 sbo plain"); } \ + || { echo " PASS: SBo plain"; (( PASS++ )); } + +# ── T87: --versions only SBo known → single line ────────────────────────────── +echo "" +echo "T87: --versions only SBo known → SBo shown, no Repo/Hint" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +out=$(run_mkhint --versions curl 2>&1) +echo "$out" | grep -q 'SBo:.*8\.5\.0' \ + && ! echo "$out" | grep -q 'Repo:' && ! echo "$out" | grep -q 'Hint:' \ + && { echo " PASS: SBo shown, no Repo/Hint"; (( PASS++ )); } \ + || { echo " FAIL: block wrong"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T87 block"); } + +# ── T88: --versions only hint known → Hint shown ────────────────────────────── +echo "" +echo "T88: --versions only hint known → Hint shown" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +cat > "$MOCK_HINT/orphanpkg.hint" << 'EOF' +VERSION="3.0.0" +ARCH="x86_64" +EOF +out=$(run_mkhint --versions orphanpkg 2>&1) +echo "$out" | grep -q 'Hint:.*3\.0\.0' \ + && ! echo "$out" | grep -q 'SBo:' && ! echo "$out" | grep -q 'Repo:' \ + && { echo " PASS: hint shown, no SBo/Repo"; (( PASS++ )); } \ + || { echo " FAIL: hint block wrong"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T88 hint"); } + +# ── T89: --versions unknown package → exit 2 ────────────────────────────────── +echo "" +echo "T89: --versions unknown package → exit 2" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +set +e +out=$(run_mkhint --versions ghost_package 2>&1) +code=$? +set -e +assert_exit_code "unknown package exits 2" 2 "$code" + +# ── T90: --versions multiple packages + no-args error ───────────────────────── +echo "" +echo "T90: --versions multiple packages shown; no args exits 1" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.6.0" +ARCH="x86_64" +EOF +cat > "$MOCK_HINT/clion.hint" << 'EOF' +VERSION="2025.5" +ARCH="x86_64" +EOF +out=$(run_mkhint --versions curl clion 2>&1) +echo "$out" | grep -q 'network/curl' && echo "$out" | grep -q 'development/clion' \ + && { echo " PASS: both packages shown"; (( PASS++ )); } \ + || { echo " FAIL: multiple blocks missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T90 multiple"); } +set +e +run_mkhint --versions 2>/dev/null +code=$? +set -e +assert_exit_code "no args exits 1" 1 "$code" + +# ── T91: --versions upstream present → Upstream shown, green when newest ────── +echo "" +echo "T91: --versions with nvchecker section → Upstream line, green when newest" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" + +[curl] +source = "github" +github = "curl/curl" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "8.9.0" } } } +EOF +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +EOF +seed_pkg network curl 8.4.0 +out=$(MKHINT_FORCE_COLOR=1 run_mkhint --versions curl 2>&1) +echo "$out" | grep -q 'Upstream:.*8\.9\.0' \ + && { echo " PASS: Upstream shown"; (( PASS++ )); } \ + || { echo " FAIL: Upstream missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T91 upstream"); } +echo "$out" | grep -q $'\033\[32m8\.9\.0' \ + && { echo " PASS: upstream newest green"; (( PASS++ )); } \ + || { echo " FAIL: upstream not green"; echo "$out" | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T91 green"); } + +# ── T92: --versions no nvchecker section → note, exit 0 ─────────────────────── +echo "" +echo "T92: --versions without nvchecker section → note, not an error" +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +rm -rf "$MOCK_PKGS" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +set +e +out=$(run_mkhint --versions curl 2>&1) +code=$? +set -e +assert_exit_code "no-section check exits 0" 0 "$code" +echo "$out" | grep -q "no nvchecker section" \ + && { echo " PASS: no-section note shown"; (( PASS++ )); } \ + || { echo " FAIL: note missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T92 note"); } + # ── T57: -l skips hints with no VERSION ────────────────────────────────────── echo "" echo "T57: -l lists only hints that have a VERSION" |
