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}"