system-manager-wakasu
  1#!/usr/bin/env bash
  2
  3set -euo pipefail
  4
  5# Colors for output
  6RED='\033[0;31m'
  7GREEN='\033[0;32m'
  8YELLOW='\033[1;33m'
  9BLUE='\033[0;34m'
 10NC='\033[0m' # No Color
 11
 12# Help message
 13usage() {
 14    cat <<EOF
 15Usage: gh-restart-failed [OPTIONS] [REPOSITORY]
 16
 17List pull requests with failed checks and restart selected workflows.
 18
 19Options:
 20    -i, --ignore PATTERN    Ignore workflows matching PATTERN (can be used multiple times)
 21    -h, --help             Show this help message
 22
 23Arguments:
 24    REPOSITORY    Optional repository in OWNER/REPO format or path to local repo.
 25                  If not provided, uses the current directory's repository.
 26
 27Dependencies:
 28    - gh (GitHub CLI)
 29    - fzf (fuzzy finder)
 30    - jq (JSON processor)
 31
 32Note:
 33    By default, "Label Checker" workflows are ignored. Use -i to add more patterns.
 34
 35Examples:
 36    gh-restart-failed                                    # Use current repository
 37    gh-restart-failed owner/repo                         # Use specific GitHub repository
 38    gh-restart-failed -i "build" -i "test"              # Ignore build and test workflows
 39    gh-restart-failed /path/to/repo                     # Use repository at path
 40
 41EOF
 42    exit 0
 43}
 44
 45# Check dependencies
 46check_dependencies() {
 47    local missing=()
 48
 49    for cmd in gh fzf jq; do
 50        if ! command -v "$cmd" &> /dev/null; then
 51            missing+=("$cmd")
 52        fi
 53    done
 54
 55    if [ ${#missing[@]} -gt 0 ]; then
 56        echo -e "${RED}Error: Missing required dependencies: ${missing[*]}${NC}" >&2
 57        echo "Please install them and try again." >&2
 58        exit 1
 59    fi
 60}
 61
 62# Default ignore patterns
 63IGNORE_PATTERNS=("Label Checker")
 64
 65# Parse arguments
 66REPO_ARG=""
 67while [[ $# -gt 0 ]]; do
 68    case $1 in
 69        -h|--help)
 70            usage
 71            ;;
 72        -i|--ignore)
 73            if [ -n "${2:-}" ]; then
 74                IGNORE_PATTERNS+=("$2")
 75                shift 2
 76            else
 77                echo -e "${RED}Error: --ignore requires a pattern argument${NC}" >&2
 78                exit 1
 79            fi
 80            ;;
 81        -*)
 82            echo -e "${RED}Error: Unknown option: $1${NC}" >&2
 83            usage
 84            ;;
 85        *)
 86            REPO_ARG="$1"
 87            shift
 88            ;;
 89    esac
 90done
 91
 92check_dependencies
 93
 94# Determine repository context
 95REPO_FLAG=()
 96if [ -n "$REPO_ARG" ]; then
 97    if [ -d "$REPO_ARG" ]; then
 98        # It's a directory path
 99        REPO_FLAG=(-R "$(cd "$REPO_ARG" && gh repo view --json nameWithOwner -q .nameWithOwner)")
100    else
101        # Assume it's OWNER/REPO format
102        REPO_FLAG=(-R "$REPO_ARG")
103    fi
104fi
105
106# Show ignored patterns
107if [ ${#IGNORE_PATTERNS[@]} -gt 0 ]; then
108    echo -e "${YELLOW}Ignoring workflows matching: ${IGNORE_PATTERNS[*]}${NC}" >&2
109fi
110
111# Get all open PRs with their check status
112echo -e "${BLUE}Fetching pull requests...${NC}" >&2
113
114# Fetch PRs with detailed check information
115prs_json=$(gh pr list "${REPO_FLAG[@]}" \
116    --json number,title,headRefName,author,statusCheckRollup \
117    --limit 100)
118
119# Filter PRs with failed checks and format for display
120failed_prs=$(echo "$prs_json" | jq -r '
121    .[] |
122    select(.statusCheckRollup // [] | any(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "STARTUP_FAILURE" or .conclusion == "ACTION_REQUIRED")) |
123    {
124        number: .number,
125        title: .title,
126        branch: .headRefName,
127        author: .author.login,
128        failed_checks: [.statusCheckRollup[] | select(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "STARTUP_FAILURE" or .conclusion == "ACTION_REQUIRED")]
129    } |
130    "#\(.number) | \(.title) | @\(.author) | \(.branch) | \(.failed_checks | length) failed"
131')
132
133if [ -z "$failed_prs" ]; then
134    echo -e "${GREEN}No pull requests with failed checks found!${NC}"
135    exit 0
136fi
137
138echo -e "${YELLOW}Found pull requests with failed checks:${NC}" >&2
139echo ""
140
141# Use fzf to select PRs
142selected_prs=$(echo "$failed_prs" | fzf \
143    --multi \
144    --ansi \
145    --header="Select pull requests to restart failed workflows (TAB to select multiple, ENTER to confirm)" \
146    --preview="pr_number=\$(echo {} | cut -d'|' -f1 | tr -d '# '); gh pr checks ${REPO_FLAG[*]} \"\$pr_number\" 2>/dev/null | grep -E '(fail|FAILURE|×)' || echo 'Loading...'" \
147    --preview-window=right:60%:wrap \
148    --bind='ctrl-/:toggle-preview' \
149    --height=80%)
150
151if [ -z "$selected_prs" ]; then
152    echo -e "${YELLOW}No pull requests selected.${NC}"
153    exit 0
154fi
155
156echo ""
157echo -e "${BLUE}Processing selected pull requests...${NC}"
158echo ""
159
160# Process each selected PR
161while IFS= read -r pr_line; do
162    pr_number=$(echo "$pr_line" | cut -d'|' -f1 | tr -d '# ' | xargs)
163    pr_title=$(echo "$pr_line" | cut -d'|' -f2 | xargs)
164    pr_branch=$(echo "$pr_line" | cut -d'|' -f4 | xargs)
165
166    echo -e "${BLUE}PR #$pr_number: $pr_title${NC}"
167
168    # Build jq ignore filter
169    ignore_filter=""
170    for pattern in "${IGNORE_PATTERNS[@]}"; do
171        if [ -n "$ignore_filter" ]; then
172            ignore_filter="$ignore_filter and "
173        fi
174        ignore_filter="${ignore_filter}(.name | contains(\"$pattern\") | not)"
175    done
176
177    # Get failed workflow runs for this PR using the branch
178    failed_runs=$(gh run list "${REPO_FLAG[@]}" \
179        --branch "$pr_branch" \
180        --json databaseId,name,conclusion,status,event \
181        --limit 50 \
182        | jq -r "
183        .[] |
184        select(.event == \"pull_request\" and (.conclusion == \"failure\" or .conclusion == \"timed_out\" or .conclusion == \"startup_failure\" or .conclusion == \"action_required\") and ($ignore_filter)) |
185        \"\(.databaseId)|\(.name)|\(.conclusion)\"")
186
187    if [ -z "$failed_runs" ]; then
188        echo -e "${YELLOW}  No failed workflow runs found (may have been restarted already)${NC}"
189        continue
190    fi
191
192    # Restart all failed workflow runs
193    echo -e "${YELLOW}  Restarting failed workflows:${NC}"
194
195    echo "$failed_runs" | while IFS='|' read -r run_id workflow_name status; do
196        echo -e "  ${GREEN}${NC} Restarting: $workflow_name ($status)"
197
198        if gh run rerun "${REPO_FLAG[@]}" "$run_id" --failed 2>&1 | grep -v "^$"; then
199            echo -e "    ${GREEN}${NC} Restarted successfully"
200        else
201            echo -e "    ${GREEN}${NC} Restarted successfully"
202        fi
203    done
204
205    echo ""
206done <<< "$selected_prs"
207
208echo -e "${GREEN}Done!${NC}"