diff --git a/.buildkite/scripts/cherry-pick-from-milestone.sh b/.buildkite/scripts/cherry-pick-from-milestone.sh new file mode 100755 index 000000000..99eb36acd --- /dev/null +++ b/.buildkite/scripts/cherry-pick-from-milestone.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# +# cherry-pick-from-milestone.sh +# Find commits from a GitHub milestone that are missing from the current branch +# and output them in chronological order for cherry-picking. +# +# Usage: ./cherry-pick-from-milestone.sh [--dry-run] [--execute] +# + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +usage() { + cat < [options] + +Find commits from a GitHub milestone that need to be cherry-picked into the current branch. + +Arguments: + milestone The GitHub milestone name (e.g., v0.14.0) + +Options: + --dry-run Show the cherry-pick commands without executing (default) + --execute Actually execute the cherry-picks + --main-branch Specify the main branch name (default: main) + --help Show this help message + +Examples: + $(basename "$0") v0.14.0 + $(basename "$0") v0.14.0 --dry-run + $(basename "$0") v0.14.0 --execute + $(basename "$0") v0.14.0 --main-branch master +EOF + exit 1 +} + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[OK]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +# Default values +MILESTONE="" +DRY_RUN=true +MAIN_BRANCH="main" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + --execute) + DRY_RUN=false + shift + ;; + --main-branch) + MAIN_BRANCH="$2" + shift 2 + ;; + --help|-h) + usage + ;; + -*) + log_error "Unknown option: $1" + usage + ;; + *) + if [[ -z "$MILESTONE" ]]; then + MILESTONE="$1" + else + log_error "Unexpected argument: $1" + usage + fi + shift + ;; + esac +done + +# Validate milestone argument +if [[ -z "$MILESTONE" ]]; then + log_error "Milestone is required" + usage +fi + +# Check if we're in a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + log_error "Not in a git repository" + exit 1 +fi + +# Check if gh CLI is available +if ! command -v gh &>/dev/null; then + log_error "GitHub CLI (gh) is not installed" + exit 1 +fi + +# Check if authenticated with gh +if ! gh auth status &>/dev/null; then + log_error "Not authenticated with GitHub CLI. Run 'gh auth login' first." + exit 1 +fi + +CURRENT_BRANCH=$(git branch --show-current) +log_info "Current branch: ${CURRENT_BRANCH}" +log_info "Main branch: ${MAIN_BRANCH}" +log_info "Milestone: ${MILESTONE}" +echo "" + +# Fetch latest from remote +log_info "Fetching latest from remote..." +git fetch origin "$MAIN_BRANCH" --quiet + +# Get merged PRs from the milestone, sorted by merge date +log_info "Fetching merged PRs from milestone '${MILESTONE}'..." + +# Store PR data in a temp file +PR_DATA=$(mktemp) +trap "rm -f $PR_DATA" EXIT + +if ! gh pr list --state merged --search "milestone:${MILESTONE}" \ + --limit 1000 \ + --json number,title,mergeCommit,mergedAt \ + --jq 'sort_by(.mergedAt) | .[] | "\(.mergeCommit.oid)\t\(.number)\t\(.title)"' > "$PR_DATA" 2>/dev/null; then + log_error "Failed to fetch PRs from milestone '${MILESTONE}'" + log_error "This could be due to:" + log_error " - Milestone does not exist" + log_error " - Network/authentication issues" + log_error " - Invalid milestone name format" + exit 1 +fi + +if [[ ! -s "$PR_DATA" ]]; then + log_warn "No merged PRs found for milestone '${MILESTONE}'" + exit 0 +fi + +TOTAL_PRS=$(wc -l < "$PR_DATA") +log_info "Found ${TOTAL_PRS} merged PR(s) in milestone" +echo "" + +# Find commits that are missing from current branch +MISSING_COMMITS=() +MISSING_INFO=() + +while IFS=$'\t' read -r sha pr_number title; do + # Skip if SHA is empty or null + if [[ -z "$sha" || "$sha" == "null" ]]; then + log_warn "PR #${pr_number} has no merge commit SHA, skipping" + continue + fi + + # Check if this commit is already in the current branch + if git merge-base --is-ancestor "$sha" HEAD 2>/dev/null; then + log_success "PR #${pr_number} already in branch: ${title:0:60}" + else + log_warn "PR #${pr_number} MISSING: ${title:0:60}" + MISSING_COMMITS+=("$sha") + MISSING_INFO+=("$sha PR #${pr_number}: ${title}") + fi +done < "$PR_DATA" + +echo "" + +if [[ ${#MISSING_COMMITS[@]} -eq 0 ]]; then + log_success "All PRs from milestone '${MILESTONE}' are already in the current branch!" + exit 0 +fi + +log_info "Found ${#MISSING_COMMITS[@]} missing commit(s) to cherry-pick" +echo "" + +# Output the cherry-pick commands +echo "==========================================" +echo "Cherry-pick commands (in chronological order):" +echo "==========================================" +echo "" + +for info in "${MISSING_INFO[@]}"; do + echo "# $info" +done +echo "" + +echo "# Run these commands to cherry-pick all missing commits:" +echo "git cherry-pick ${MISSING_COMMITS[*]}" +echo "" + +# Or one by one +echo "# Or cherry-pick one at a time:" +for sha in "${MISSING_COMMITS[@]}"; do + echo "git cherry-pick $sha" +done +echo "" + +# Execute if requested +if [[ "$DRY_RUN" == false ]]; then + echo "==========================================" + log_info "Executing cherry-picks..." + echo "==========================================" + + for i in "${!MISSING_COMMITS[@]}"; do + sha="${MISSING_COMMITS[$i]}" + info="${MISSING_INFO[$i]}" + + echo "" + log_info "Cherry-picking: $info" + + if git cherry-pick "$sha"; then + log_success "Successfully cherry-picked $sha" + else + log_error "Failed to cherry-pick $sha" + log_error "Resolve conflicts and run 'git cherry-pick --continue', or 'git cherry-pick --abort' to cancel" + exit 1 + fi + done + + echo "" + log_success "All cherry-picks completed successfully!" +else + echo "==========================================" + echo -e "${YELLOW}Dry run mode - no changes made${NC}" + echo "Run with --execute to perform the cherry-picks" + echo "==========================================" +fi diff --git a/.buildkite/scripts/trigger-ci-build.sh b/.buildkite/scripts/trigger-ci-build.sh new file mode 100755 index 000000000..65a761588 --- /dev/null +++ b/.buildkite/scripts/trigger-ci-build.sh @@ -0,0 +1,227 @@ +#!/bin/bash +# +# trigger-ci-build.sh +# Trigger a Buildkite CI build using the bk CLI for the current commit and branch +# with RUN_ALL=1 and NIGHTLY=1 environment variables. +# +# Usage: ./trigger-ci-build.sh [options] +# +# Requires: bk CLI (https://buildkite.com/docs/platform/cli) +# +# SAFETY: Dry-run by default. Use --execute to actually trigger a build. +# + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default configuration +PIPELINE="ci" +DRY_RUN=true + +usage() { + cat <&2 +} + +# Parse arguments +COMMIT="" +BRANCH="" +MESSAGE="" + +while [[ $# -gt 0 ]]; do + case $1 in + --execute) + DRY_RUN=false + shift + ;; + --pipeline) + PIPELINE="$2" + shift 2 + ;; + --commit) + COMMIT="$2" + shift 2 + ;; + --branch) + BRANCH="$2" + shift 2 + ;; + --message) + MESSAGE="$2" + shift 2 + ;; + --help|-h) + usage + ;; + -*) + log_error "Unknown option: $1" + usage + ;; + *) + log_error "Unexpected argument: $1" + usage + ;; + esac +done + +# Check if bk CLI is installed +if ! command -v bk &>/dev/null; then + log_error "Buildkite CLI (bk) is not installed" + echo "" + echo "Install with:" + echo " brew tap buildkite/buildkite && brew install buildkite/buildkite/bk" + echo "" + echo "Then configure:" + echo " bk configure" + exit 1 +fi + +# Check if we're in a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + log_error "Not in a git repository" + exit 1 +fi + +# Get current commit and branch if not overridden +if [[ -z "$COMMIT" ]]; then + COMMIT=$(git rev-parse HEAD) +fi + +if [[ -z "$BRANCH" ]]; then + BRANCH=$(git branch --show-current) + if [[ -z "$BRANCH" ]]; then + # Detached HEAD state - try to get branch from ref + BRANCH=$(git rev-parse --abbrev-ref HEAD) + fi +fi + +# Generate default message if not provided +if [[ -z "$MESSAGE" ]]; then + COMMIT_MSG=$(git log -1 --pretty=format:"%s" "$COMMIT" 2>/dev/null || echo "Manual build") + MESSAGE="[Manual] ${COMMIT_MSG}" +fi + +# Safety check: Verify the commit exists on the remote +log_info "Verifying commit exists on remote..." +git fetch origin --quiet 2>/dev/null || true + +# Check if commit is reachable from any remote branch +REMOTE_BRANCHES=$(git branch -r --contains "$COMMIT" 2>/dev/null || true) +if [[ -z "$REMOTE_BRANCHES" ]]; then + log_error "Commit ${COMMIT} does not exist on any remote branch!" + echo "" + echo "The CI system will fail to checkout this commit." + echo "Please push your changes first:" + echo "" + echo " git push origin ${BRANCH}" + echo "" + exit 1 +fi + +log_success "Commit found on remote branches:" +echo "$REMOTE_BRANCHES" | head -5 | sed 's/^/ /' +if [[ $(echo "$REMOTE_BRANCHES" | wc -l) -gt 5 ]]; then + echo " ... and more" +fi +echo "" + +log_info "Pipeline: ${PIPELINE}" +log_info "Branch: ${BRANCH}" +log_info "Commit: ${COMMIT}" +log_info "Message: ${MESSAGE}" +log_info "Environment: RUN_ALL=1, NIGHTLY=1" +echo "" + +# Build the command +CMD=(bk build create + -y + -w + -i + --pipeline "${PIPELINE}" + --commit "${COMMIT}" + --branch "${BRANCH}" + --message "${MESSAGE}" + --env "RUN_ALL=1" + --env "NIGHTLY=1" +) + +if [[ "$DRY_RUN" == true ]]; then + echo "==========================================" + log_warn "DRY-RUN MODE - No build will be triggered" + echo "==========================================" + echo "" + echo "Command that would be executed:" + echo "" + # Escape single quotes in values for safe shell display + escape_for_shell() { + printf '%s' "$1" | sed "s/'/'\\\\''/g" + } + echo " bk build create \\" + echo " -y \\" + echo " -w \\" + echo " -i \\" + echo " --pipeline '$(escape_for_shell "${PIPELINE}")' \\" + echo " --commit '$(escape_for_shell "${COMMIT}")' \\" + echo " --branch '$(escape_for_shell "${BRANCH}")' \\" + echo " --message '$(escape_for_shell "${MESSAGE}")' \\" + echo " --env 'RUN_ALL=1' \\" + echo " --env 'NIGHTLY=1'" + echo "" + echo "==========================================" + echo -e "${YELLOW}To actually trigger this build, run:${NC}" + echo "" + echo " $0 --execute" + echo "==========================================" + exit 0 +fi + +log_info "Triggering build..." + +# Execute the command - bk will print the URL and open browser +"${CMD[@]}"