|
| 1 | +#!/usr/bin/bash |
| 2 | + |
| 3 | +# SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | + |
| 5 | +AUR_REPO_URL="https://github.com/archlinux/aur" |
| 6 | + |
| 7 | +set -euo pipefail |
| 8 | + |
| 9 | +usage() { |
| 10 | + cat <<EOF |
| 11 | +Usage: $(basename "$0") [-d|--diff] [-f|--fetch] [-n|--days <n>] [-m|--meta <path>] [-p|--pattern <glob>] [-r|--repo <path>] [--clone <path>] |
| 12 | +
|
| 13 | +Find AUR packages modified within a time window whose branches contain changes |
| 14 | +to files matching a given glob pattern. |
| 15 | +
|
| 16 | +Options: |
| 17 | + -d, --diff Print the effective diff for each matched package |
| 18 | + -f, --fetch Sync the local mirror and metadata to the latest state before running |
| 19 | + -n, --days <n> Look-back window in days (default: 7) |
| 20 | + -m, --meta <path> Path to packages-meta-v1.json (default: packages-meta-v1.json in the aur repo root) |
| 21 | + -p, --pattern <glob> File pattern to inspect (default: *) |
| 22 | + -r, --repo <path> Path to the AUR git checkout (default: git repo of current directory) |
| 23 | + --clone <path> Clone the AUR mono repository to <path> and use it as the repo |
| 24 | + -h, --help Show this help text |
| 25 | +
|
| 26 | +Examples: |
| 27 | + $(basename "$0") --repo ~/Documents/shared_projects/aur --fetch --diff --days 1 |
| 28 | +EOF |
| 29 | +} |
| 30 | + |
| 31 | +SHOW_DIFF=0 |
| 32 | +FETCH=0 |
| 33 | +DAYS=7 |
| 34 | +META_JSON="" |
| 35 | +PATTERN="*" |
| 36 | +REPO_ROOT="" |
| 37 | +CLONE_PATH="" |
| 38 | +while [[ "${1:-}" == -* ]]; do |
| 39 | + case "$1" in |
| 40 | + -d|--diff) SHOW_DIFF=1 ;; |
| 41 | + -f|--fetch) FETCH=1 ;; |
| 42 | + -n|--days) shift; DAYS="$1" ;; |
| 43 | + -m|--meta) shift; META_JSON="$1" ;; |
| 44 | + -p|--pattern) shift; PATTERN="$1" ;; |
| 45 | + -r|--repo) shift; REPO_ROOT="$1" ;; |
| 46 | + --clone) shift; CLONE_PATH="$1" ;; |
| 47 | + -h|--help) usage; exit 0 ;; |
| 48 | + *) echo "Unknown flag: $1" >&2; echo >&2; usage >&2; exit 1 ;; |
| 49 | + esac |
| 50 | + shift |
| 51 | +done |
| 52 | + |
| 53 | +if [[ -n "$CLONE_PATH" ]]; then |
| 54 | + echo "Cloning AUR mono repository to $CLONE_PATH ..." >&2 |
| 55 | + git clone "$AUR_REPO_URL" "$CLONE_PATH" |
| 56 | + REPO_ROOT="$CLONE_PATH" |
| 57 | +fi |
| 58 | + |
| 59 | +REPO_ROOT="${REPO_ROOT:-"$(git rev-parse --show-toplevel)"}" |
| 60 | +META_JSON="${META_JSON:-"$REPO_ROOT/packages-meta-v1.json"}" |
| 61 | + |
| 62 | +if [[ ! -f "$META_JSON" ]] && ! (( FETCH )); then |
| 63 | + echo "error: metadata file not found: $META_JSON" >&2 |
| 64 | + echo "hint: run with --fetch to download it" >&2 |
| 65 | + exit 1 |
| 66 | +fi |
| 67 | + |
| 68 | +if (( FETCH )); then |
| 69 | + echo "Updating git mirror ..." >&2 |
| 70 | + git -C "$REPO_ROOT" fetch --all --prune |
| 71 | + echo "Fetching latest metadata from aur.archlinux.org ..." >&2 |
| 72 | + curl -fsSL "https://aur.archlinux.org/packages-meta-v1.json.gz" \ |
| 73 | + | gzip -d > "$META_JSON" |
| 74 | + echo "Saved to $META_JSON" >&2 |
| 75 | +fi |
| 76 | + |
| 77 | +# Compute the cutoff as unix timestamp |
| 78 | +CUTOFF=$(date -d "-${DAYS} days" +%s) |
| 79 | + |
| 80 | +# Collect the unique PackageBases modified since CUTOFF |
| 81 | +mapfile -t PKGS < <( |
| 82 | + jq -r --argjson cutoff "$CUTOFF" \ |
| 83 | + '[.[] | select(.LastModified >= $cutoff)] | map(.PackageBase) | unique | .[]' \ |
| 84 | + "$META_JSON" |
| 85 | +) |
| 86 | + |
| 87 | +echo "Packages modified in the last ${DAYS} day(s): ${#PKGS[@]}" >&2 |
| 88 | +echo "File pattern: $PATTERN" >&2 |
| 89 | + |
| 90 | +# For each package, check if its branch has matching file changes since $CUTOFF |
| 91 | +SINCE_DATE=$(date -d "-${DAYS} days" --iso-8601=seconds) |
| 92 | + |
| 93 | +for pkg in "${PKGS[@]}"; do |
| 94 | + # Check if the remote branch exists |
| 95 | + if ! git -C "$REPO_ROOT" rev-parse --verify "origin/$pkg" &>/dev/null; then |
| 96 | + echo "error: no remote branch for package '$pkg'" >&2 |
| 97 | + exit 1 |
| 98 | + fi |
| 99 | + |
| 100 | + # Check if there are any commits matching $PATTERN for this package since $CUTOFF |
| 101 | + if git -C "$REPO_ROOT" log "origin/$pkg" --since="$SINCE_DATE" --name-only --format="" -- "$PATTERN" | grep -q .; then |
| 102 | + echo "$pkg" |
| 103 | + |
| 104 | + if (( SHOW_DIFF )); then |
| 105 | + # Find the boundary commits for the range touching $PATTERN |
| 106 | + mapfile -t commits < <( |
| 107 | + git -C "$REPO_ROOT" log "origin/$pkg" --since="$SINCE_DATE" --format="%H" -- "$PATTERN" |
| 108 | + ) |
| 109 | + newest="${commits[0]}" |
| 110 | + oldest="${commits[-1]}" |
| 111 | + # Diff from the parent of the oldest in-range commit (empty tree if root) |
| 112 | + if git -C "$REPO_ROOT" rev-parse "${oldest}^" &>/dev/null; then |
| 113 | + base="${oldest}^" |
| 114 | + else |
| 115 | + # So far this always outputs the magic value |
| 116 | + # '4b825dc642cb6eb9a060e54bf8d69288fbee4904', but instead of hardcoding |
| 117 | + # it we regenerate it each time to not rely on it |
| 118 | + # |
| 119 | + # https://stackoverflow.com/a/9766506 |
| 120 | + base="$(git mktree < /dev/null)" |
| 121 | + fi |
| 122 | + git -C "$REPO_ROOT" diff "$base" "$newest" -- "$PATTERN" |
| 123 | + fi |
| 124 | + fi |
| 125 | +done |
0 commit comments