diff --git a/tools/README.md b/tools/README.md index 769b64c..f5bcd03 100644 --- a/tools/README.md +++ b/tools/README.md @@ -4,7 +4,7 @@ Collection of shell scripts to manage GitHub issues for the Typee project using ## Overview -These tools provide command-line access to GitHub Issues management functionality: +These tools provide command-line access to GitHub Issues management and pull request workflows: - **Create Issue** - Create new GitHub issues with priority prefixes and labels - **Get Issue by ID** - Retrieve specific issue information @@ -12,6 +12,7 @@ These tools provide command-line access to GitHub Issues management functionalit - **Update Issue by ID** - Modify issue properties (title, body, labels, state, assignees) - **Close Issue by ID** - Close issues - **Create PR from Issue** - Create pull requests automatically from GitHub issues +- **Merge Pull Request** - Merge pull requests with automatic branch cleanup ## Prerequisites @@ -613,6 +614,117 @@ PR Information: --- +### 7. merge-pull-request.sh + +Merge a pull request with automatic source branch cleanup. Uses squash merge strategy by default for clean commit history. + +**Usage:** +```bash +./merge-pull-request.sh [options] +``` + +**Options:** +- `-h, --help`: Show help message +- `-p, --pr `: Pull request number (required) +- `-s, --strategy `: Merge strategy: `squash`, `merge`, `rebase` (default: `squash`) +- `-d, --delete-branch`: Delete source branch after merge (default behavior) +- `--keep-branch`: Keep source branch after merge (don't delete) +- `-f, --force`: Skip confirmation prompt +- `--dry-run`: Show what would be done without applying +- `-m, --message `: Custom commit message (for squash merge) + +**Examples:** +```bash +# Merge PR #35 using squash strategy and delete branch +./merge-pull-request.sh --pr 35 + +# Merge with rebase strategy +./merge-pull-request.sh -p 35 --strategy rebase + +# Merge without deleting source branch +./merge-pull-request.sh --pr 33 --keep-branch + +# Force merge without confirmation +./merge-pull-request.sh -p 34 --force + +# Preview merge without applying +./merge-pull-request.sh --pr 36 --dry-run + +# Merge using standard merge strategy +./merge-pull-request.sh --pr 37 --strategy merge --force +``` + +**Merge Strategies:** +- **squash** (default) - Squash all commits into single commit for clean history +- **merge** - Create merge commit preserving all commit history +- **rebase** - Rebase commits on top of target branch + +**How It Works:** + +1. Fetches PR details from GitHub +2. Validates PR state and status checks +3. Merges PR using specified strategy +4. Automatically deletes source branch (unless `--keep-branch` is used) +5. Displays merge summary and next steps + +**Features:** +- ✅ Multiple merge strategies supported +- ✅ Automatic branch cleanup after merge +- ✅ Status check verification +- ✅ Dry-run mode for safe preview +- ✅ Force mode for automation +- ✅ Confirmation prompt (optional) +- ✅ Clear error handling + +**Sample Output:** +``` +════════════════════════════════════════════════════════════ +Merge Pull Request with Branch Cleanup +════════════════════════════════════════════════════════════ + +ℹ️ Fetching PR #35 details... + +Pull Request Information: + Number: #35 + Title: ci/cd: simplify lint workflow and fix status check recognition + State: OPEN + Source Branch: ci/cd-fix-workflow + Target Branch: master + Merge Strategy: squash + Delete Branch: Yes + +This will: + 1. Merge PR #35 using squash strategy + 2. Delete source branch: ci/cd-fix-workflow + +Continue? (y/N): y + +ℹ️ Merging PR #35 using squash strategy... +✅ PR #35 merged successfully + +ℹ️ Deleting source branch: ci/cd-fix-workflow +✅ Source branch deleted: ci/cd-fix-workflow + +════════════════════════════════════════════════════════════ +Pull Request Merged Successfully! +════════════════════════════════════════════════════════════ +PR #35 has been merged into master +Source branch has been cleaned up + +Next Steps: + 1. Pull latest changes: git pull origin master + 2. Delete local branch: git branch -d ci/cd-fix-workflow + 3. Continue with next task + +Merge Details: + PR: #35 - ci/cd: simplify lint workflow + Strategy: squash + Source → Target: ci/cd-fix-workflow → master +════════════════════════════════════════════════════════════ +``` + +--- + ## Common Workflows ### Workflow 1: Check Specific Issue Status @@ -703,9 +815,32 @@ done # Create PR with custom base branch ./create-pr-from-issue.sh --issue 28 --base develop --auto-format -# Create PR from current branch (don't create new branch) -git checkout -b my-branch -./create-pr-from-issue.sh --issue 29 --no-branch --title "Custom PR title" +### Workflow 7: Merge Pull Request with Branch Cleanup +```bash +# Merge PR #35 using squash strategy and delete branch +./merge-pull-request.sh --pr 35 + +# Merge PR #36 with rebase strategy without deleting branch +./merge-pull-request.sh -p 36 --strategy rebase --keep-branch + +# Preview merge before applying +./merge-pull-request.sh --pr 37 --dry-run + +# Force merge without confirmation prompt +./merge-pull-request.sh --pr 38 --force + +# Merge multiple PRs in sequence +for pr in 35 36 37; do + ./merge-pull-request.sh --pr "$pr" --force +done + +# Merge with custom merge message +./merge-pull-request.sh --pr 39 --message "chore: merge PR #39 with improvements" + +# Automated PR merge workflow with branch cleanup +gh pr list --state open --json number,headRefName | jq -r '.[] | "\(.number) \(.headRefName)"' | while read pr branch; do + ./merge-pull-request.sh --pr "$pr" --force +done ``` --- @@ -886,6 +1021,7 @@ For issues or suggestions: | `update-issue-by-id.sh` | Modify issue | `./update-issue-by-id.sh [options]` | | `close-issue-by-id.sh` | Close issue | `./close-issue-by-id.sh [options]` | | `create-pr-from-issue.sh` | Create PR from issue | `./create-pr-from-issue.sh --issue [options]` | +| `merge-pull-request.sh` | Merge PR and cleanup branch | `./merge-pull-request.sh --pr [options]` | --- diff --git a/tools/merge-pull-request.sh b/tools/merge-pull-request.sh new file mode 100755 index 0000000..e47f9a2 --- /dev/null +++ b/tools/merge-pull-request.sh @@ -0,0 +1,301 @@ +#!/bin/bash + +############################################################################## +# Merge Pull Request with Branch Cleanup +# +# Merges a GitHub pull request and automatically deletes the source branch. +# Uses squash merge strategy by default for clean commit history. +# +# Usage: +# ./merge-pull-request.sh [options] +# +# Options: +# -h, --help Show help message +# -p, --pr Pull request number (required) +# -s, --strategy Merge strategy: squash, merge, rebase (default: squash) +# -d, --delete-branch Delete source branch after merge (default: true) +# --keep-branch Keep source branch after merge +# -f, --force Skip confirmation prompt +# --dry-run Show what would be done without applying +# -m, --message Custom commit message (for squash merge) +# +# Examples: +# ./merge-pull-request.sh --pr 35 +# ./merge-pull-request.sh -p 35 --strategy squash --force +# ./merge-pull-request.sh --pr 33 --keep-branch +# ./merge-pull-request.sh -p 34 --strategy rebase +# ./merge-pull-request.sh --pr 36 --dry-run +# +# Requirements: +# - GitHub CLI (gh): https://cli.github.com +# - Authenticated with GitHub: gh auth login +# +############################################################################## + +set -e + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color + +# Default values +PR_NUMBER="" +MERGE_STRATEGY="squash" +DELETE_BRANCH=true +FORCE=false +DRY_RUN=false +CUSTOM_MESSAGE="" + +# Helper functions +error() { + echo -e "${RED}Error: $1${NC}" >&2 + exit 1 +} + +info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +success() { + echo -e "${GREEN}✅ $1${NC}" +} + +warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +show_help() { + sed -n '/^##############################################################################/,/^##############################################################################/p' "$0" | sed '1d;$d' + exit 0 +} + +# Check if gh is installed +if ! command -v gh &> /dev/null; then + error "GitHub CLI (gh) is not installed. Install it from: https://cli.github.com" +fi + +# Check if authenticated +if ! gh auth status &> /dev/null; then + error "Not authenticated with GitHub. Run: gh auth login" +fi + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + ;; + -p|--pr) + PR_NUMBER="$2" + shift 2 + ;; + -s|--strategy) + MERGE_STRATEGY="$2" + shift 2 + ;; + -d|--delete-branch) + DELETE_BRANCH=true + shift + ;; + --keep-branch) + DELETE_BRANCH=false + shift + ;; + -f|--force) + FORCE=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + -m|--message) + CUSTOM_MESSAGE="$2" + shift 2 + ;; + *) + error "Unknown option: $1" + ;; + esac +done + +# Validate arguments +if [ -z "$PR_NUMBER" ]; then + error "Pull request number is required\nUsage: ./merge-pull-request.sh --pr [options]" +fi + +# Validate PR number is numeric +if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then + error "Invalid PR number: $PR_NUMBER (must be numeric)" +fi + +# Validate merge strategy +if [[ ! "$MERGE_STRATEGY" =~ ^(squash|merge|rebase)$ ]]; then + error "Invalid merge strategy: $MERGE_STRATEGY (must be: squash, merge, or rebase)" +fi + +# Display header +echo "════════════════════════════════════════════════════════════" +echo "Merge Pull Request with Branch Cleanup" +echo "════════════════════════════════════════════════════════════" +echo "" + +# Fetch PR details +info "Fetching PR #$PR_NUMBER details..." +PR_DATA=$(gh pr view "$PR_NUMBER" --json number,title,state,headRefName,baseRefName,statusCheckRollup --repo isaaceliape/typee 2>/dev/null || error "PR #$PR_NUMBER not found") + +# Extract PR information +PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') +PR_STATE=$(echo "$PR_DATA" | jq -r '.state') +SOURCE_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName') +TARGET_BRANCH=$(echo "$PR_DATA" | jq -r '.baseRefName') +STATUS_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[] | select(.status == "COMPLETED") | .conclusion' | sort | uniq) + +# Check if PR is already merged +if [ "$PR_STATE" = "MERGED" ]; then + warning "PR #$PR_NUMBER is already merged" + info "Source branch: $SOURCE_BRANCH" + info "Target branch: $TARGET_BRANCH" + + # Still offer to delete branch if requested + if [ "$DELETE_BRANCH" = true ]; then + info "Attempting to delete source branch: $SOURCE_BRANCH" + if [ "$DRY_RUN" = false ] && [ "$FORCE" = true ]; then + if git -C "$(git rev-parse --show-toplevel)" push origin --delete "$SOURCE_BRANCH" 2>/dev/null; then + success "Source branch deleted: $SOURCE_BRANCH" + else + warning "Could not delete source branch (may already be deleted)" + fi + fi + fi + exit 0 +fi + +# Check if PR is closed without merge +if [ "$PR_STATE" = "CLOSED" ]; then + error "PR #$PR_NUMBER is closed without being merged" +fi + +# Display PR information +echo "Pull Request Information:" +echo " Number: #$PR_NUMBER" +echo " Title: $PR_TITLE" +echo " State: $PR_STATE" +echo " Source Branch: $SOURCE_BRANCH" +echo " Target Branch: $TARGET_BRANCH" +echo " Merge Strategy: $MERGE_STRATEGY" +echo " Delete Branch: $([ "$DELETE_BRANCH" = true ] && echo 'Yes' || echo 'No')" +echo "" + +# Check if status checks are passing +if echo "$STATUS_CHECKS" | grep -q "FAILURE"; then + warning "Some status checks are failing!" + info "Failed checks detected" + echo "" +fi + +# Dry run mode +if [ "$DRY_RUN" = true ]; then + warning "DRY RUN MODE - PR will not be merged" + info "Would merge PR #$PR_NUMBER" + info "Strategy: $MERGE_STRATEGY" + info "Source: $SOURCE_BRANCH → Target: $TARGET_BRANCH" + if [ "$DELETE_BRANCH" = true ]; then + info "Would delete branch: $SOURCE_BRANCH" + fi + exit 0 +fi + +# Ask for confirmation if not forced +if [ "$FORCE" = false ]; then + echo "" + echo "This will:" + echo " 1. Merge PR #$PR_NUMBER using $MERGE_STRATEGY strategy" + if [ "$DELETE_BRANCH" = true ]; then + echo " 2. Delete source branch: $SOURCE_BRANCH" + fi + echo "" + read -p "Continue? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + warning "Cancelled" + exit 0 + fi +fi + +echo "" + +# Merge the PR +info "Merging PR #$PR_NUMBER using $MERGE_STRATEGY strategy..." + +# Build merge command based on strategy +case "$MERGE_STRATEGY" in + squash) + GH_MERGE_CMD="gh pr merge $PR_NUMBER --squash" + ;; + rebase) + GH_MERGE_CMD="gh pr merge $PR_NUMBER --rebase" + ;; + merge) + GH_MERGE_CMD="gh pr merge $PR_NUMBER --merge" + ;; +esac + +# Execute merge +if eval "$GH_MERGE_CMD" --repo isaaceliape/typee --auto 2>&1 | tee /tmp/merge_output.txt; then + success "PR #$PR_NUMBER merged successfully" + echo "" +else + # Check if it was already merged + if grep -q "already merged" /tmp/merge_output.txt; then + warning "PR #$PR_NUMBER was already merged" + else + error "Failed to merge PR #$PR_NUMBER" + fi +fi + +# Delete source branch if requested +if [ "$DELETE_BRANCH" = true ]; then + echo "" + info "Deleting source branch: $SOURCE_BRANCH" + + # Attempt to delete via gh CLI first + if gh api repos/isaaceliape/typee/git/refs/heads/"$SOURCE_BRANCH" -X DELETE 2>/dev/null; then + success "Source branch deleted: $SOURCE_BRANCH" + else + warning "Could not delete source branch via API (may have been auto-deleted)" + + # Try via git push + if git push origin --delete "$SOURCE_BRANCH" 2>/dev/null; then + success "Source branch deleted via git: $SOURCE_BRANCH" + else + warning "Could not delete source branch (it may already be deleted or protected)" + fi + fi +else + info "Keeping source branch: $SOURCE_BRANCH" +fi + +echo "" +echo "════════════════════════════════════════════════════════════" +echo "Pull Request Merged Successfully!" +echo "════════════════════════════════════════════════════════════" +echo "PR #$PR_NUMBER has been merged into $TARGET_BRANCH" +if [ "$DELETE_BRANCH" = true ]; then + echo "Source branch has been cleaned up" +fi +echo "" +echo "Next Steps:" +echo " 1. Pull latest changes: git pull origin $TARGET_BRANCH" +echo " 2. Delete local branch: git branch -d $SOURCE_BRANCH" +echo " 3. Continue with next task" +echo "" +echo "Merge Details:" +echo " PR: #$PR_NUMBER - $PR_TITLE" +echo " Strategy: $MERGE_STRATEGY" +echo " Source → Target: $SOURCE_BRANCH → $TARGET_BRANCH" +echo "════════════════════════════════════════════════════════════"