#!/usr/bin/env bash # # Auto-Merge PR Script # # Automatically merges a PR to develop if all quality gates pass. # This script can be called from Woodpecker CI or manually. # # Usage: # auto-merge-pr.sh # # Environment variables: # GITEA_TOKEN - Gitea API token (required) # GITEA_URL - Gitea instance URL (default: https://git.mosaicstack.dev) # REPO_OWNER - Repository owner (default: mosaic) # REPO_NAME - Repository name (default: stack) # TARGET_BRANCH - Target branch for auto-merge (default: develop) # DRY_RUN - If set, only check eligibility without merging (default: false) set -euo pipefail # Configuration GITEA_URL="${GITEA_URL:-https://git.mosaicstack.dev}" REPO_OWNER="${REPO_OWNER:-mosaic}" REPO_NAME="${REPO_NAME:-stack}" TARGET_BRANCH="${TARGET_BRANCH:-develop}" DRY_RUN="${DRY_RUN:-false}" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Functions log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } log_success() { echo -e "${GREEN}✅ $1${NC}" } log_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } log_error() { echo -e "${RED}❌ $1${NC}" } # Check requirements if [ -z "${GITEA_TOKEN:-}" ]; then log_error "GITEA_TOKEN environment variable is required" exit 1 fi if [ $# -lt 1 ]; then log_error "Usage: $0 " exit 1 fi PR_NUMBER="$1" API_BASE="$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME" log_info "Checking PR #$PR_NUMBER for auto-merge eligibility..." # Fetch PR details PR_DATA=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \ "$API_BASE/pulls/$PR_NUMBER" || { log_error "Failed to fetch PR #$PR_NUMBER" exit 1 }) # Extract PR information PR_STATE=$(echo "$PR_DATA" | jq -r '.state // ""') PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged // false') BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref // ""') HEAD_BRANCH=$(echo "$PR_DATA" | jq -r '.head.ref // ""') IS_MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false') HAS_CONFLICTS=$(echo "$PR_DATA" | jq -r '.mergeable_state // "unknown"') PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // "Unknown"') log_info "PR #$PR_NUMBER: $PR_TITLE" log_info " State: $PR_STATE" log_info " Base: $BASE_BRANCH ← Head: $HEAD_BRANCH" log_info " Mergeable: $IS_MERGEABLE ($HAS_CONFLICTS)" # Check if PR is already merged if [ "$PR_MERGED" = "true" ]; then log_success "PR is already merged" exit 0 fi # Check if PR is open if [ "$PR_STATE" != "open" ]; then log_warning "PR is not open (state: $PR_STATE)" exit 0 fi # Check if PR targets the correct branch if [ "$BASE_BRANCH" != "$TARGET_BRANCH" ]; then log_warning "PR does not target $TARGET_BRANCH (targets: $BASE_BRANCH)" exit 0 fi # Check if PR is mergeable if [ "$IS_MERGEABLE" != "true" ]; then log_error "PR is not mergeable (state: $HAS_CONFLICTS)" exit 1 fi # Fetch CI status checks log_info "Checking CI status..." STATUS_DATA=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \ "$API_BASE/statuses/$(echo "$PR_DATA" | jq -r '.head.sha')" || echo "[]") # Count status check results TOTAL_CHECKS=$(echo "$STATUS_DATA" | jq 'length') SUCCESS_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "success")] | length') PENDING_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "pending")] | length') FAILURE_CHECKS=$(echo "$STATUS_DATA" | jq '[.[] | select(.state == "failure" or .state == "error")] | length') log_info " Total checks: $TOTAL_CHECKS" log_info " Success: $SUCCESS_CHECKS" log_info " Pending: $PENDING_CHECKS" log_info " Failed: $FAILURE_CHECKS" # Check if there are any failures if [ "$FAILURE_CHECKS" -gt 0 ]; then log_error "PR has $FAILURE_CHECKS failed status checks" echo "$STATUS_DATA" | jq -r '.[] | select(.state == "failure" or .state == "error") | " - \(.context): \(.description)"' exit 1 fi # Check if there are pending checks if [ "$PENDING_CHECKS" -gt 0 ]; then log_warning "PR has $PENDING_CHECKS pending status checks - waiting..." exit 0 fi # Check if all required checks passed if [ "$TOTAL_CHECKS" -eq 0 ]; then log_warning "No status checks found - proceeding with caution" elif [ "$SUCCESS_CHECKS" -ne "$TOTAL_CHECKS" ]; then log_warning "Not all status checks are successful ($SUCCESS_CHECKS/$TOTAL_CHECKS)" fi # All checks passed - ready to merge log_success "All quality gates passed!" if [ "$DRY_RUN" = "true" ]; then log_info "DRY RUN: Would merge PR #$PR_NUMBER to $TARGET_BRANCH" exit 0 fi # Perform the merge log_info "Merging PR #$PR_NUMBER to $TARGET_BRANCH..." MERGE_RESULT=$(curl -sf -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "Do": "merge", "MergeMessageField": "", "MergeTitleField": "", "delete_branch_after_merge": true, "force_merge": false, "merge_when_checks_succeed": false }' \ "$API_BASE/pulls/$PR_NUMBER/merge" || { log_error "Failed to merge PR #$PR_NUMBER" exit 1 }) # Check if merge was successful if echo "$MERGE_RESULT" | jq -e '.merged' > /dev/null 2>&1; then MERGE_SHA=$(echo "$MERGE_RESULT" | jq -r '.sha // "unknown"') log_success "Successfully merged PR #$PR_NUMBER to $TARGET_BRANCH" log_success " Merge commit: $MERGE_SHA" log_success " Branch $HEAD_BRANCH deleted" else ERROR_MSG=$(echo "$MERGE_RESULT" | jq -r '.message // "Unknown error"') log_error "Merge failed: $ERROR_MSG" exit 1 fi