main
sh 242 lines 6.15 KB
Raw
1 #!/bin/bash
2 #
3 # cherry-pick-from-milestone.sh
4 # Find commits from a GitHub milestone that are missing from the current branch
5 # and output them in chronological order for cherry-picking.
6 #
7 # Usage: ./cherry-pick-from-milestone.sh <milestone> [--dry-run] [--execute]
8 #
9
10 set -euo pipefail
11
12 # Colors for output
13 RED='\033[0;31m'
14 GREEN='\033[0;32m'
15 YELLOW='\033[1;33m'
16 BLUE='\033[0;34m'
17 NC='\033[0m' # No Color
18
19 usage() {
20 cat <<EOF
21 Usage: $(basename "$0") <milestone> [options]
22
23 Find commits from a GitHub milestone that need to be cherry-picked into the current branch.
24
25 Arguments:
26 milestone The GitHub milestone name (e.g., v0.14.0)
27
28 Options:
29 --dry-run Show the cherry-pick commands without executing (default)
30 --execute Actually execute the cherry-picks
31 --main-branch Specify the main branch name (default: main)
32 --help Show this help message
33
34 Examples:
35 $(basename "$0") v0.14.0
36 $(basename "$0") v0.14.0 --dry-run
37 $(basename "$0") v0.14.0 --execute
38 $(basename "$0") v0.14.0 --main-branch master
39 EOF
40 exit 1
41 }
42
43 log_info() {
44 echo -e "${BLUE}[INFO]${NC} $1"
45 }
46
47 log_success() {
48 echo -e "${GREEN}[OK]${NC} $1"
49 }
50
51 log_warn() {
52 echo -e "${YELLOW}[WARN]${NC} $1"
53 }
54
55 log_error() {
56 echo -e "${RED}[ERROR]${NC} $1" >&2
57 }
58
59 # Default values
60 MILESTONE=""
61 DRY_RUN=true
62 MAIN_BRANCH="main"
63
64 # Parse arguments
65 while [[ $# -gt 0 ]]; do
66 case $1 in
67 --dry-run)
68 DRY_RUN=true
69 shift
70 ;;
71 --execute)
72 DRY_RUN=false
73 shift
74 ;;
75 --main-branch)
76 MAIN_BRANCH="$2"
77 shift 2
78 ;;
79 --help|-h)
80 usage
81 ;;
82 -*)
83 log_error "Unknown option: $1"
84 usage
85 ;;
86 *)
87 if [[ -z "$MILESTONE" ]]; then
88 MILESTONE="$1"
89 else
90 log_error "Unexpected argument: $1"
91 usage
92 fi
93 shift
94 ;;
95 esac
96 done
97
98 # Validate milestone argument
99 if [[ -z "$MILESTONE" ]]; then
100 log_error "Milestone is required"
101 usage
102 fi
103
104 # Check if we're in a git repository
105 if ! git rev-parse --is-inside-work-tree &>/dev/null; then
106 log_error "Not in a git repository"
107 exit 1
108 fi
109
110 # Check if gh CLI is available
111 if ! command -v gh &>/dev/null; then
112 log_error "GitHub CLI (gh) is not installed"
113 exit 1
114 fi
115
116 # Check if authenticated with gh
117 if ! gh auth status &>/dev/null; then
118 log_error "Not authenticated with GitHub CLI. Run 'gh auth login' first."
119 exit 1
120 fi
121
122 CURRENT_BRANCH=$(git branch --show-current)
123 log_info "Current branch: ${CURRENT_BRANCH}"
124 log_info "Main branch: ${MAIN_BRANCH}"
125 log_info "Milestone: ${MILESTONE}"
126 echo ""
127
128 # Fetch latest from remote
129 log_info "Fetching latest from remote..."
130 git fetch origin "$MAIN_BRANCH" --quiet
131
132 # Get merged PRs from the milestone, sorted by merge date
133 log_info "Fetching merged PRs from milestone '${MILESTONE}'..."
134
135 # Store PR data in a temp file
136 PR_DATA=$(mktemp)
137 trap 'rm -f "$PR_DATA"' EXIT
138
139 if ! gh pr list --state merged --search "milestone:${MILESTONE}" \
140 --limit 1000 \
141 --json number,title,mergeCommit,mergedAt \
142 --jq 'sort_by(.mergedAt) | .[] | "\(.mergeCommit.oid)\t\(.number)\t\(.title)"' > "$PR_DATA" 2>/dev/null; then
143 log_error "Failed to fetch PRs from milestone '${MILESTONE}'"
144 log_error "This could be due to:"
145 log_error " - Milestone does not exist"
146 log_error " - Network/authentication issues"
147 log_error " - Invalid milestone name format"
148 exit 1
149 fi
150
151 if [[ ! -s "$PR_DATA" ]]; then
152 log_warn "No merged PRs found for milestone '${MILESTONE}'"
153 exit 0
154 fi
155
156 TOTAL_PRS=$(wc -l < "$PR_DATA")
157 log_info "Found ${TOTAL_PRS} merged PR(s) in milestone"
158 echo ""
159
160 # Find commits that are missing from current branch
161 MISSING_COMMITS=()
162 MISSING_INFO=()
163
164 while IFS=$'\t' read -r sha pr_number title; do
165 # Skip if SHA is empty or null
166 if [[ -z "$sha" || "$sha" == "null" ]]; then
167 log_warn "PR #${pr_number} has no merge commit SHA, skipping"
168 continue
169 fi
170
171 # Check if this commit is already in the current branch
172 if git merge-base --is-ancestor "$sha" HEAD 2>/dev/null; then
173 log_success "PR #${pr_number} already in branch: ${title:0:60}"
174 else
175 log_warn "PR #${pr_number} MISSING: ${title:0:60}"
176 MISSING_COMMITS+=("$sha")
177 MISSING_INFO+=("$sha PR #${pr_number}: ${title}")
178 fi
179 done < "$PR_DATA"
180
181 echo ""
182
183 if [[ ${#MISSING_COMMITS[@]} -eq 0 ]]; then
184 log_success "All PRs from milestone '${MILESTONE}' are already in the current branch!"
185 exit 0
186 fi
187
188 log_info "Found ${#MISSING_COMMITS[@]} missing commit(s) to cherry-pick"
189 echo ""
190
191 # Output the cherry-pick commands
192 echo "=========================================="
193 echo "Cherry-pick commands (in chronological order):"
194 echo "=========================================="
195 echo ""
196
197 for info in "${MISSING_INFO[@]}"; do
198 echo "# $info"
199 done
200 echo ""
201
202 echo "# Run these commands to cherry-pick all missing commits:"
203 echo "git cherry-pick ${MISSING_COMMITS[*]}"
204 echo ""
205
206 # Or one by one
207 echo "# Or cherry-pick one at a time:"
208 for sha in "${MISSING_COMMITS[@]}"; do
209 echo "git cherry-pick $sha"
210 done
211 echo ""
212
213 # Execute if requested
214 if [[ "$DRY_RUN" == false ]]; then
215 echo "=========================================="
216 log_info "Executing cherry-picks..."
217 echo "=========================================="
218
219 for i in "${!MISSING_COMMITS[@]}"; do
220 sha="${MISSING_COMMITS[$i]}"
221 info="${MISSING_INFO[$i]}"
222
223 echo ""
224 log_info "Cherry-picking: $info"
225
226 if git cherry-pick "$sha"; then
227 log_success "Successfully cherry-picked $sha"
228 else
229 log_error "Failed to cherry-pick $sha"
230 log_error "Resolve conflicts and run 'git cherry-pick --continue', or 'git cherry-pick --abort' to cancel"
231 exit 1
232 fi
233 done
234
235 echo ""
236 log_success "All cherry-picks completed successfully!"
237 else
238 echo "=========================================="
239 echo -e "${YELLOW}Dry run mode - no changes made${NC}"
240 echo "Run with --execute to perform the cherry-picks"
241 echo "=========================================="
242 fi