diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d900350..29d54730 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,5 @@ jobs: steps: - uses: actions/checkout@v6 - - run: pacman -Sy --needed --noconfirm just python-cram - - run: | - useradd ci - runuser -u ci -- just test + - run: pacman -Sy --needed --noconfirm git just python-cram + - run: just test diff --git a/dist/completion/downgrade/fish b/dist/completion/downgrade/fish index bdc05999..2a32ff9c 100644 --- a/dist/completion/downgrade/fish +++ b/dist/completion/downgrade/fish @@ -4,6 +4,7 @@ complete -c $cmd -l pacman -xa "(complete -C(commandline -ct))" -d 'pacman comma complete -c $cmd -l pacman-conf -r -d 'pacman configuration file' complete -c $cmd -l pacman-cache -r -d 'pacman cache directory' complete -c $cmd -l pacman-log -r -d 'pacman log file' +complete -c $cmd -l git -d 'uses git for choosing version, viable only for aur packages' complete -c $cmd -l maxdepth -x -d 'maximum depth to search for cached packages' complete -c $cmd -l ala-url -x -d 'location of ALA server' complete -c $cmd -l ala-only -d 'only use ALA server' diff --git a/dist/completion/downgrade/zsh b/dist/completion/downgrade/zsh index b8f48159..a4217489 100644 --- a/dist/completion/downgrade/zsh +++ b/dist/completion/downgrade/zsh @@ -20,6 +20,7 @@ _downgrade () { '--pacman-conf[pacman configuration file]:pacman config file:_files' \ '*--pacman-cache[pacman cache directory]:pacman cache directory:_path_files -/' \ '--pacman-log[pacman log file]:pacman log file:_files' \ + '--git[uses git for choosing version, viable only for aur packages]' \ '--maxdepth[maximum depth to search for cached packages]:maximum search depth' \ '--ala-url[location of ALA server]:ala url' \ '--ala-only[only use ALA server]' \ diff --git a/doc/downgrade.8.ronn b/doc/downgrade.8.ronn index 26aa9cb6..0db1ffd2 100644 --- a/doc/downgrade.8.ronn +++ b/doc/downgrade.8.ronn @@ -72,6 +72,15 @@ on the ALA. Pacman log file, default value is extracted from pacman configuration file, or otherwise defaults to _/var/log/pacman.log_. + * `--git`: + If the pacman cache contains the source repository for the package (e.g. if + you use yay and are downgrading an AUR package), present any version-like + tags as available for downgrading (together with cached packages if + present). If selected, build and install the package from sources checked + out at that tag. + + See [EXAMPLES]. + * `--maxdepth`=: Maximum depth to search for cached packages, defaults to _1_. @@ -168,6 +177,11 @@ Non-interactively downgrade `foo` to `1.0.0-1` # downgrade --latest --prefer-cache --ignore never 'foo=1.0.0-1' +Present cached `downgrade` pkgbuilds together with tags with version greater +than 9 + + # downgrade --git --pacman-cache ~/.cache/yay --maxdepth 2 'downgrade>9.0.0' + ## AUTHORS * Patrick Brisbin \ diff --git a/src/downgrade b/src/downgrade index 23ae7ef9..24a32045 100755 --- a/src/downgrade +++ b/src/downgrade @@ -19,6 +19,7 @@ $(gettext "Options"): $(gettext "pacman log file,") $(gettext "default value taken from pacman configuration file,") $(gettext "or otherwise defaults to") "/var/log/pacman.log" + --git $(gettext "uses git for choosing version, viable only for aur packages") --maxdepth <$(gettext "integer")> $(gettext "maximum depth to search for cached packages, defaults to") 1 --ala-url @@ -167,8 +168,10 @@ matches_name_version_filter() { return 0 fi - version_regex="[^-]+-[0-9.]+" - pkg_version=$(sed -r "s/.*$name-($version_regex)-(any|$DOWNGRADE_ARCH)\\.pkg\\.tar\\.(gz|xz|zst)/\1/g" <<<"$pkg") + # version: 2025.11.12.r15.g0eed3fe + version_regex="[a-zA-Z0-9.]+" + hash_or_minor="([0-9]+|[a-z0-9]{6,})" + pkg_version=$(sed -r "s/.*$name-($version_regex)(-$hash_or_minor)?-(any|$DOWNGRADE_ARCH)\\.(git)?pkg\\.tar\\.(gz|xz|zst)/\1\2/g" <<<"$pkg") cmp=$(vercmp "$pkg_version" "$search_version") case "$operator" in @@ -196,6 +199,34 @@ matches_name_version_filter() { esac } +search_git() { + local name=$1 pairs path + + # Delay this defaulting so #read_pacman_conf behavior is tested + if ((!${#PACMAN_CACHE[@]})); then + mapfile -t PACMAN_CACHE < <(read_pacman_conf CacheDir) + fi + + for cache in "${PACMAN_CACHE[@]}"; do + path="$cache/$name" # directory name + if ! [[ -d "$path/.git" ]]; then + { + gettext "\`$path\" not a git directory, skipping..." + echo + } >&2 + continue + fi + + # TODO: in future could be enhanced by counting every commit + # that DOES NOT change version as minor like 0.0.1-2 instead of any + # pair = abcd12 1.0 + pairs=$(git -C "$path" log -p --oneline --format="GIT: %h" PKGBUILD | grep -e 'GIT:.*' -e '+pkgver' | grep -Pzo 'GIT: .*\n\+pkgver=.*\n' | sed 's/GIT: \|+pkgver=//' | tr -d '\0') + + # shellcheck disable=SC2086 + echo "$path/$name" $pairs | awk '{for(i=2;i epoch,version,release,arch s|\(\([^:]*\):\)\?\([^-]*\)\(-\([^-]*\)\)\?\(-\(.*\)\)\?|\2,\3,\5,\7|; @@ -320,6 +354,11 @@ process_term() { candidates=($(printf '%s\n' "${candidates[@]}" | sort_packages)) + # this is already sorted + if ((DOWNGRADE_FROM_GIT)); then + candidates=($(search_git "$name" | filter_packages "$name" "$operator" "$version") "${candidates[@]}") + fi + if (("${#candidates[@]}" == 0)); then { gettext "No results found" @@ -440,6 +479,9 @@ parse_options() { exit 1 fi ;; + --git) + DOWNGRADE_FROM_GIT=1 + ;; --maxdepth) if [[ -n "$2" ]]; then shift @@ -523,6 +565,34 @@ parse_options() { fi } +build_pkg() { + local item="$1" path commit_hash build_failed versionreg hashreg gitpkgreg + if [[ "$item" =~ .*gitpkg.tar.gz ]]; then + # version: 2025.11.12.r15.g0eed3fe + versionreg="([a-zA-Z0-9]+\.?)*" + hashreg='[a-z0-9]{6,}' + #hash_or_minor="-([0-9]+|[a-z0-9]{6,})" # leaving this as a note, only hash should appear tho + gitpkgreg="(.*)-$versionreg-($hashreg)-(any|$DOWNGRADE_ARCH).gitpkg.tar.gz" + path=$(dirname "$item") + commit_hash=$(sed -r "s/$gitpkgreg/\3/" <<<"$item") + + pushd "$path" >/dev/null + su -P "$(stat -c"%U" PKGBUILD)" -c "git checkout $commit_hash &>/dev/null && makepkg -fs | tee makepkg.log" + build_failed=$? + # NOTE: when force-canceling, users might need to manually do checkout master + # otherwise they won't be able to update that pkg using yay or whatever I guess... + su "$(stat -c"%U" PKGBUILD)" -c "git switch - &>/dev/null" + if ((build_failed)); then + exit 1 + fi + + item="$(find "$path" -maxdepth 1 -type f -name "$(sed -nr '/.*Finished making: ([^ ]+) ([^ ]+) .*/{s//\1-\2/;p}' makepkg.log)*" -print -quit)" + rm makepkg.log + popd >/dev/null + fi + printf "%s\0" "$item" >>new_to_install.downgrade +} + cli() { local conf_args=() @@ -536,6 +606,14 @@ cli() { # Proceed with rest of workflow main "${terms[@]}" + if ((DOWNGRADE_FROM_GIT)); then + for item in "${to_install[@]}"; do + build_pkg "$item" + done + mapfile -d '' to_install DOWNGRADE_IGNORE=always > DOWNGRADE_FROM_CACHE=1 > search_cache() { printf "%s\n" "$TMP/foo.pkg"; } - > main foo 2>/tmp/downgradechoicelog; exit_code=$? + > main foo 2>/dev/null; exit_code=$? > printf "ignore: %s\n" "${to_ignore[@]}" > printf "install: %s\n" "${to_install[@]}" > printf "exit code: %s\n" "$exit_code" diff --git a/test/pacignore/parse_failure.t b/test/pacignore/parse_failure.t index 05bc719a..c1f0a098 100644 --- a/test/pacignore/parse_failure.t +++ b/test/pacignore/parse_failure.t @@ -48,25 +48,32 @@ Check that pacignore fails if no argument provided [options] IgnorePkg = foo bar -Check that pacignore fails if no root access provided for add or rm - - $ PACMAN_CONF="$(write_pacman_conf "[options]" "IgnorePkg = foo bar")" - > pacignore add 2>&1 - > printf "exit_code=%s\n" "$?" - > cat "$PACMAN_CONF" - pacignore must be run as root for this subcommand - exit_code=1 - [options] - IgnorePkg = foo bar - - $ PACMAN_CONF="$(write_pacman_conf "[options]" "IgnorePkg = foo bar")" - > pacignore rm 2>&1 - > printf "exit_code=%s\n" "$?" - > cat "$PACMAN_CONF" - pacignore must be run as root for this subcommand - exit_code=1 - [options] - IgnorePkg = foo bar +# TODO: Because CI is running on an archlinux container on GitHub Actions, the +# tests must run as root there. +# +# https://docs.github.com/en/actions/reference/workflows-and-actions/dockerfile-support#user +# +# That means this test won't work there. +# +# Check that pacignore fails if no root access provided for add or rm +# +# $ PACMAN_CONF="$(write_pacman_conf "[options]" "IgnorePkg = foo bar")" +# > pacignore add 2>&1 +# > printf "exit_code=%s\n" "$?" +# > cat "$PACMAN_CONF" +# pacignore must be run as root for this subcommand +# exit_code=1 +# [options] +# IgnorePkg = foo bar +# +# $ PACMAN_CONF="$(write_pacman_conf "[options]" "IgnorePkg = foo bar")" +# > pacignore rm 2>&1 +# > printf "exit_code=%s\n" "$?" +# > cat "$PACMAN_CONF" +# pacignore must be run as root for this subcommand +# exit_code=1 +# [options] +# IgnorePkg = foo bar Check that parsing fails if no package is package is specified for add or rm diff --git a/test/search_git.t b/test/search_git.t new file mode 100644 index 00000000..4716b4be --- /dev/null +++ b/test/search_git.t @@ -0,0 +1,16 @@ + $ source "$TESTDIR/helper.sh" + > cache=$(mktemp -d) + > git clone -q "https://aur.archlinux.org/downgrade.git" "$cache/downgrade" + > write_pacman_conf "[options]" "CacheDir = $cache" + > export DOWNGRADE_FROM_GIT=1 + +List packages based on git tags + + $ search_git 'downgrade' | head -n 2 + /tmp/*/downgrade-5.1.3-0bdb507-any.gitpkg.tar.gz (glob) + /tmp/*/downgrade-5.1.4-dee7bd9-any.gitpkg.tar.gz (glob) + +Outputs appropriately for filter_packages + + $ search_git 'downgrade' | filter_packages 'downgrade' '=~' '^7' | head -n 1 + /tmp/*/downgrade-7.0.0-513f504-any.gitpkg.tar.gz (glob)