ランブック

マージ済みの Bitbucket ブランチを自動でクリーンアップ

Bitbucket ワークスペース内のすべてのリポジトリから、放置されたマージ済みブランチを削除します。重要なブランチは除外パターンで保護し、ドライランモードで削除内容を事前に確認できます。

この処理の内容

このランブックは、Bitbucket ワークスペース内のリポジトリを走査し、すでにマージされたブランチを特定して削除します。mainmasterdevelop のような保護対象のブランチは、設定可能なパターンによって自動的に除外されます。ドライランモードが既定で有効なため、削除に踏み切る前に、何が削除されるのかを正確に確認できます。

前提条件

クイックスタート

# Preview branches that would be deleted (dry-run)
./branch-cleanup.sh --workspace myworkspace --dry-run

# Target a specific repo with custom exclusions
./branch-cleanup.sh --workspace myworkspace --repo api-service \
  --exclude "main,master,develop,release/*"

# Execute the cleanup
./branch-cleanup.sh --workspace myworkspace --execute

ランブックスクリプト全文

branch-cleanup.sh

#!/bin/bash
# Bitbucket Branch Cleanup Script
#
# Deletes merged branches across repositories in a workspace.
# Supports dry-run mode and exclusion patterns.
#
# Usage:
#   # Dry run to preview branches to delete
#   ./branch-cleanup.sh --workspace myworkspace --dry-run
#
#   # Delete merged branches (excluding protected branches)
#   ./branch-cleanup.sh --workspace myworkspace \
#                       --exclude "main,master,develop,release/*"
#
#   # Clean up a specific repository
#   ./branch-cleanup.sh --workspace myworkspace --repo myrepo
#
# Requirements:
#   - atlassian-cli installed and configured

set -euo pipefail

# Configuration
WORKSPACE=""
REPO=""
PROFILE="default"
DRY_RUN=true
EXCLUDE_PATTERNS="main,master,develop"
CONCURRENCY=4

# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

log() {
    echo -e "${GREEN}[INFO]${NC} $*"
}

warn() {
    echo -e "${YELLOW}[WARN]${NC} $*"
}

error() {
    echo -e "${RED}[ERROR]${NC} $*" >&2
}

# Parse arguments
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --workspace)
                WORKSPACE="$2"
                shift 2
                ;;
            --repo)
                REPO="$2"
                shift 2
                ;;
            --profile)
                PROFILE="$2"
                shift 2
                ;;
            --exclude)
                EXCLUDE_PATTERNS="$2"
                shift 2
                ;;
            --execute)
                DRY_RUN=false
                shift
                ;;
            --dry-run)
                DRY_RUN=true
                shift
                ;;
            *)
                error "Unknown option: $1"
                exit 1
                ;;
        esac
    done

    if [ -z "$WORKSPACE" ]; then
        error "Workspace slug required (--workspace WORKSPACE)"
        exit 1
    fi
}

# Check if branch should be excluded
is_excluded() {
    local branch="$1"

    IFS=',' read -ra patterns <<< "$EXCLUDE_PATTERNS"
    for pattern in "${patterns[@]}"; do
        # Simple wildcard matching
        if [[ "$branch" == $pattern ]]; then
            return 0
        fi
    done

    return 1
}

# Get all repositories or specific repo
get_repos() {
    if [ -n "$REPO" ]; then
        echo "[\"$REPO\"]"
    else
        log "Fetching all repositories from workspace: $WORKSPACE"
        atlassian-cli bitbucket repo list \
            --profile "$PROFILE" \
            "$WORKSPACE" \
            --output json | jq -r '[.[].slug]'
    fi
}

# Get merged branches for a repository
get_merged_branches() {
    local repo="$1"

    log "Finding merged branches in $repo..."

    # Get all branches
    local branches
    branches=$(atlassian-cli bitbucket branch list \
        --profile "$PROFILE" \
        "$WORKSPACE" \
        "$repo" \
        --output json)

    # Filter for merged branches
    echo "$branches" | jq -r '.[] | select(.merge_strategies != null) | .name'
}

# Delete branch
delete_branch() {
    local repo="$1"
    local branch="$2"

    if [ "$DRY_RUN" = "true" ]; then
        warn "[DRY-RUN] Would delete branch: $repo/$branch"
        return
    fi

    log "Deleting branch: $repo/$branch"

    atlassian-cli bitbucket branch delete \
        --profile "$PROFILE" \
        "$WORKSPACE" \
        "$repo" \
        "$branch" || warn "Failed to delete: $branch"
}

# Process repository branches
process_repo() {
    local repo="$1"

    log "Processing repository: $repo"

    local branches
    branches=$(get_merged_branches "$repo")

    if [ -z "$branches" ]; then
        log "No merged branches found in $repo"
        return
    fi

    local deleted=0
    local skipped=0

    while IFS= read -r branch; do
        if is_excluded "$branch"; then
            log "Skipping protected branch: $branch"
            skipped=$((skipped + 1))
            continue
        fi

        delete_branch "$repo" "$branch"
        deleted=$((deleted + 1))

    done <<< "$branches"

    log "Repository $repo: $deleted deleted, $skipped skipped"
}

# Main execution
main() {
    parse_args "$@"

    log "Bitbucket Branch Cleanup"
    log "Workspace: $WORKSPACE | Profile: $PROFILE"

    if [ "$DRY_RUN" = "true" ]; then
        warn "DRY-RUN MODE: No branches will be deleted"
    fi

    log "Exclude patterns: $EXCLUDE_PATTERNS"

    local repos
    repos=$(get_repos)

    if [ "$(echo "$repos" | jq '. | length')" -eq 0 ]; then
        error "No repositories found"
        exit 1
    fi

    local total_repos
    total_repos=$(echo "$repos" | jq '. | length')
    log "Found $total_repos repositories to process"

    # Process each repository
    echo "$repos" | jq -r '.[]' | while IFS= read -r repo_name; do
        process_repo "$repo_name"
    done

    log "Branch cleanup complete"

    if [ "$DRY_RUN" = "true" ]; then
        warn "This was a dry run. Use --execute to actually delete branches."
    fi
}

main "$@"

仕組み

1

ワークスペースと除外設定。スクリプトは必須の --workspace、単一のリポジトリを対象にする任意の --repo、保護するブランチパターンをカンマ区切りで指定する --exclude を受け付けます。既定では mainmasterdevelop が保護されます。

2

リポジトリの検出。特定のリポジトリを指定しない場合、スクリプトは bitbucket repo list を呼び出してワークスペース内のすべてのリポジトリを取得します。結果は JSON として解析し、1 つずつ処理します。

3

マージ済みブランチの抽出。各リポジトリで bitbucket branch list がすべてのブランチを取得します。スクリプトはマージ済みのブランチだけを絞り込み、bash のワイルドカードマッチで保護パターンに一致するものを除外します。

4

ドライランでのプレビュー。既定ではドライランモードで動作し、変更を加えずに削除対象のブランチを 1 つずつ表示します。これにより、実行前にクリーンアップの内容を確認できます。

5

削除の実行。実際にブランチを削除するには --execute を指定します。スクリプトはリポジトリごとに、削除したブランチ数と除外ルールでスキップしたブランチ数をまとめて報告します。

関連ランブック

コピーしました