|
#!/bin/bash |
|
# BBManager - Git Branch Manager |
|
# An interactive branch management tool that extends branch-manager.sh functionality |
|
# Uses gum for beautiful terminal UI and gum for CLI argument parsing |
|
# |
|
# @meta dotenv |
|
# @option --config-file <FILE> Path to custom configuration file |
|
# @flag --debug Enable debug mode |
|
# @flag --no-color Disable colored output |
|
# @flag --check-gum Check if gum is installed |
|
# @arg action Choice of action [select|create|merge|delete|config|status|help] |
|
|
|
# Integrated brancher.sh functionality - no external dependencies needed |
|
|
|
# Configuration |
|
readonly BB_CONFIG_FILE="${BB_CONFIG_FILE:-$HOME/.bbmanager.conf}" |
|
readonly BB_TEMP_DIR="/tmp/bbmanager-$$" |
|
readonly BB_LOCK_FILE="/tmp/bbmanager.lock" |
|
|
|
# Gum configuration |
|
readonly GIT_COLOR="#f14e32" |
|
readonly BB_HEADER_COLOR="#0080ff" |
|
readonly BB_SUCCESS_COLOR="#00ff80" |
|
readonly BB_WARNING_COLOR="#ffff00" |
|
readonly BB_ERROR_COLOR="#ff4040" |
|
|
|
# Branch type configurations (from brancher.sh) |
|
declare -A BRANCH_TYPES=( |
|
["main"]="protected" |
|
["develop"]="integration" |
|
["feature"]="supporting" |
|
["bugfix"]="supporting" |
|
["hotfix"]="supporting" |
|
["release"]="supporting" |
|
["experiment"]="supporting" |
|
["docs"]="supporting" |
|
["chore"]="maintenance" |
|
["ci"]="maintenance" |
|
["refactor"]="maintenance" |
|
["perf"]="maintenance" |
|
["test"]="supporting" |
|
) |
|
|
|
declare -A BRANCH_DESCRIPTIONS=( |
|
["main"]="Production-ready code; only merge commits from release/hotfix" |
|
["develop"]="Integration branch for completed features awaiting release" |
|
["feature"]="New functionality; 1:1 with Issue or ticket" |
|
["bugfix"]="Non-urgent bug fixes targeting next release" |
|
["hotfix"]="Critical fixes applied immediately to production" |
|
["release"]="Stabilize and prepare a new version" |
|
["experiment"]="Spike or proof-of-concept work" |
|
["docs"]="Documentation changes only" |
|
["chore"]="Routine tasks (dependencies, tooling)" |
|
["ci"]="CI/CD pipeline and config updates" |
|
["refactor"]="Code restructuring without behavior change" |
|
["perf"]="Performance optimizations" |
|
["test"]="Add or improve automated tests" |
|
) |
|
|
|
declare -A BASE_BRANCHES=( |
|
["feature"]="develop" |
|
["bugfix"]="develop" |
|
["hotfix"]="main" |
|
["release"]="develop" |
|
["experiment"]="develop" |
|
["docs"]="develop" |
|
["chore"]="develop" |
|
["ci"]="develop" |
|
["refactor"]="develop" |
|
["perf"]="develop" |
|
["test"]="develop" |
|
) |
|
|
|
# Configuration constants from brancher.sh |
|
readonly MAX_BRANCH_LENGTH=50 |
|
readonly ALLOWED_PATTERN="^[a-z0-9._/-]+$" |
|
readonly SEPARATOR="-" |
|
|
|
# Color codes for output (legacy from brancher.sh) |
|
readonly RED='\033[0;31m' |
|
readonly GREEN='\033[0;32m' |
|
readonly YELLOW='\033[1;33m' |
|
readonly BLUE='\033[0;34m' |
|
readonly CYAN='\033[0;36m' |
|
readonly NC='\033[0m' # No Color |
|
|
|
# Status indicators |
|
declare -A STATUS_ICONS=( |
|
["BUSY"]="⚡" |
|
["WAITING"]="⏳" |
|
["IDLE"]="✅" |
|
["ERROR"]="❌" |
|
["SUCCESS"]="🎉" |
|
["INFO"]="ℹ️" |
|
["WARNING"]="⚠️" |
|
) |
|
|
|
declare -A STATUS_LABELS=( |
|
["BUSY"]="Working" |
|
["WAITING"]="Pending" |
|
["IDLE"]="Ready" |
|
["ERROR"]="Failed" |
|
["SUCCESS"]="Complete" |
|
["INFO"]="Information" |
|
["WARNING"]="Attention" |
|
) |
|
|
|
# Legacy brancher.sh utility functions |
|
log_info() { |
|
echo -e "${BLUE}[INFO]${NC} $1" |
|
} |
|
|
|
log_success() { |
|
echo -e "${GREEN}[SUCCESS]${NC} $1" |
|
} |
|
|
|
log_warning() { |
|
echo -e "${YELLOW}[WARNING]${NC} $1" |
|
} |
|
|
|
log_error() { |
|
echo -e "${RED}[ERROR]${NC} $1" |
|
} |
|
|
|
log_debug() { |
|
[[ "${DEBUG:-}" == "1" ]] && echo -e "${CYAN}[DEBUG]${NC} $1" |
|
} |
|
|
|
# Core validation functions (from brancher.sh) |
|
validate_git_repo() { |
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then |
|
bb_error "Not in a git repository" |
|
return 1 |
|
fi |
|
} |
|
|
|
validate_branch_type() { |
|
local branch_type="$1" |
|
if [[ -z "${BRANCH_TYPES[$branch_type]:-}" ]]; then |
|
bb_error "Invalid branch type: $branch_type" |
|
bb_info "Supported types: ${!BRANCH_TYPES[*]}" |
|
return 1 |
|
fi |
|
} |
|
|
|
validate_branch_name() { |
|
local branch_name="$1" |
|
|
|
# Check length |
|
if [[ ${#branch_name} -gt $MAX_BRANCH_LENGTH ]]; then |
|
bb_error "Branch name too long (${#branch_name} > $MAX_BRANCH_LENGTH): $branch_name" |
|
return 1 |
|
fi |
|
|
|
# Check pattern |
|
if [[ ! "$branch_name" =~ $ALLOWED_PATTERN ]]; then |
|
bb_error "Invalid characters in branch name. Only lowercase letters, numbers, dots, underscores, and hyphens allowed: $branch_name" |
|
return 1 |
|
fi |
|
} |
|
|
|
sanitize_input() { |
|
local input="$1" |
|
# Convert to lowercase, replace spaces and underscores with hyphens |
|
echo "$input" | tr '[:upper:]' '[:lower:]' | tr ' _' '--' | sed 's/--*/-/g' | sed 's/^-\|-$//g' |
|
} |
|
|
|
check_branch_exists() { |
|
local branch_name="$1" |
|
git rev-parse --verify --quiet "$branch_name" > /dev/null 2>&1 |
|
} |
|
|
|
ensure_base_branch() { |
|
local base_branch="$1" |
|
local current_branch |
|
current_branch=$(git branch --show-current) |
|
|
|
# Check if base branch exists, fall back to main if it doesn't |
|
if ! git rev-parse --verify --quiet "$base_branch" > /dev/null 2>&1; then |
|
bb_warning "Base branch '$base_branch' doesn't exist, falling back to 'main'" |
|
base_branch="main" |
|
fi |
|
|
|
if [[ "$current_branch" != "$base_branch" ]]; then |
|
bb_info "Switching to base branch: $base_branch" |
|
if ! git checkout "$base_branch" 2>/dev/null; then |
|
bb_error "Failed to checkout base branch: $base_branch" |
|
return 1 |
|
fi |
|
|
|
bb_info "Pulling latest changes from $base_branch" |
|
if ! git pull origin "$base_branch" 2>/dev/null; then |
|
bb_warning "Failed to pull latest changes. Continuing with local state." |
|
fi |
|
fi |
|
} |
|
|
|
# Core branching functions (from brancher.sh) |
|
create_standard_branch() { |
|
local branch_type="$1" |
|
local issue_number="$2" |
|
local description="$3" |
|
local branch_name |
|
local base_branch="${BASE_BRANCHES[$branch_type]:-develop}" |
|
|
|
# Construct branch name based on type |
|
if [[ "$branch_type" == "release" ]]; then |
|
# For releases, issue_number is actually the version |
|
branch_name="${branch_type}/${issue_number}" |
|
elif [[ "$branch_type" == "experiment" ]] || [[ "$branch_type" == "docs" ]] || [[ "$branch_type" == "chore" ]] || [[ "$branch_type" == "ci" ]] || [[ "$branch_type" == "refactor" ]] || [[ "$branch_type" == "perf" ]] || [[ "$branch_type" == "test" ]]; then |
|
# These types don't require issue numbers |
|
if [[ -n "$issue_number" && -n "$description" ]]; then |
|
branch_name="${branch_type}/$(sanitize_input "$issue_number-$description")" |
|
elif [[ -n "$issue_number" ]]; then |
|
branch_name="${branch_type}/$(sanitize_input "$issue_number")" |
|
else |
|
bb_error "Description required for $branch_type branches" |
|
return 1 |
|
fi |
|
else |
|
# Standard types with issue numbers |
|
if [[ -z "$issue_number" ]]; then |
|
bb_error "Issue number required for $branch_type branches" |
|
return 1 |
|
fi |
|
|
|
if [[ -n "$description" ]]; then |
|
branch_name="${branch_type}/${issue_number}-$(sanitize_input "$description")" |
|
else |
|
branch_name="${branch_type}/${issue_number}" |
|
fi |
|
fi |
|
|
|
# Validate branch name |
|
validate_branch_name "$branch_name" || return 1 |
|
|
|
# Check if branch already exists |
|
if check_branch_exists "$branch_name"; then |
|
bb_error "Branch already exists: $branch_name" |
|
return 1 |
|
fi |
|
|
|
# Ensure we're on the correct base branch |
|
ensure_base_branch "$base_branch" || return 1 |
|
|
|
# Create and checkout the new branch |
|
bb_info "Creating branch: $branch_name (from $base_branch)" |
|
if git checkout -b "$branch_name"; then |
|
bb_success "Successfully created and checked out: $branch_name" |
|
bb_info "Branch type: ${BRANCH_TYPES[$branch_type]}" |
|
bb_info "Description: ${BRANCH_DESCRIPTIONS[$branch_type]}" |
|
return 0 |
|
else |
|
bb_error "Failed to create branch: $branch_name" |
|
return 1 |
|
fi |
|
} |
|
|
|
create_feature_branch_legacy() { |
|
# Legacy feature branch creation with date-indexed naming |
|
local main_branch="main" |
|
local prefix="feature" |
|
local datetime=$(date +%Y-%m-%d) |
|
local username=$(echo "$USER" | tr '[:upper:]' '[:lower:]') |
|
local additional_path=$(echo "$*" | tr ' ' '-') |
|
local index=1 |
|
local new_branch |
|
|
|
# Construct the branch path prefix with optional additional path |
|
local branch_path="${prefix}/${username}" |
|
[[ -n "$additional_path" ]] && branch_path="${branch_path}/${additional_path}" |
|
branch_path="${branch_path}/${datetime}" |
|
|
|
# Switch to main branch and pull latest changes if not on main |
|
if [[ "$(git branch --show-current)" != "$main_branch" ]]; then |
|
bb_info "Switching to ${main_branch} and pulling latest changes..." |
|
git checkout $main_branch |
|
git pull |
|
fi |
|
|
|
# Find the next available branch index for datetime |
|
while git rev-parse --verify --quiet "${branch_path}/${index}"; do |
|
index=$((index + 1)) |
|
done |
|
|
|
# Create and switch to the new branch |
|
new_branch="${branch_path}/${index}" |
|
bb_info "Creating and checking out ${new_branch}..." |
|
git checkout -b $new_branch |
|
} |
|
|
|
# List branches function from brancher.sh |
|
list_branches() { |
|
bb_info "Current branches organized by type:" |
|
echo |
|
|
|
for type in "${!BRANCH_TYPES[@]}"; do |
|
local branches |
|
branches=$(git branch -a | grep -E "^\s*[*]?\s*(remotes/origin/)?${type}/" | sed 's/^\s*[*]?\s*//' | sed 's/remotes\/origin\///' | sort -u) |
|
|
|
if [[ -n "$branches" ]]; then |
|
echo -e "${CYAN}$type branches:${NC}" |
|
echo "$branches" | sed 's/^/ /' |
|
echo |
|
fi |
|
done |
|
} |
|
|
|
# Global state |
|
declare -g CURRENT_BRANCH="" |
|
declare -g BRANCH_STATUS="IDLE" |
|
declare -g ALL_BRANCHES=() |
|
declare -g SHOW_REMOTE=false |
|
|
|
# Utility functions using gum for beautiful output |
|
bb_header() { |
|
gum style \ |
|
--border normal \ |
|
--margin "1" \ |
|
--padding "1 2" \ |
|
--border-foreground "$BB_HEADER_COLOR" \ |
|
--bold \ |
|
--foreground "$BB_HEADER_COLOR" \ |
|
"BBManager - Git Branch Manager" |
|
} |
|
|
|
bb_info() { |
|
local message="$1" |
|
gum style --foreground "$BB_HEADER_COLOR" "ℹ️ $message" |
|
} |
|
|
|
bb_success() { |
|
local message="$1" |
|
gum style --foreground "$BB_SUCCESS_COLOR" "🎉 $message" |
|
} |
|
|
|
bb_warning() { |
|
local message="$1" |
|
gum style --foreground "$BB_WARNING_COLOR" "⚠️ $message" |
|
} |
|
|
|
bb_error() { |
|
local message="$1" |
|
gum style --foreground "$BB_ERROR_COLOR" "❌ $message" |
|
} |
|
|
|
bb_confirm() { |
|
local prompt="$1" |
|
local default="${2:-No}" |
|
gum confirm "$prompt" --default="$default" |
|
} |
|
|
|
bb_input() { |
|
local prompt="$1" |
|
local placeholder="${2:-}" |
|
if [[ -n "$placeholder" ]]; then |
|
gum input --placeholder "$placeholder" --prompt "$prompt " |
|
else |
|
gum input --prompt "$prompt " |
|
fi |
|
} |
|
|
|
bb_choose() { |
|
local prompt="$1" |
|
shift |
|
gum choose --selected.foreground="$GIT_COLOR" "$@" --header "$prompt" |
|
} |
|
|
|
bb_status_line() { |
|
local current_branch |
|
current_branch=$(git branch --show-current 2>/dev/null || echo "No repository") |
|
|
|
local status_display="${STATUS_ICONS[$BRANCH_STATUS]} ${STATUS_LABELS[$BRANCH_STATUS]}" |
|
|
|
gum join --vertical \ |
|
"$(gum style --foreground "$BB_HEADER_COLOR" "Current: $current_branch")" \ |
|
"$(gum style --foreground "#888888" "Status: $status_display")" \ |
|
"$(gum style --foreground "#888888" "Controls: ↑↓ Navigate | Enter Select")" |
|
} |
|
|
|
# Branch management functions using gum interface |
|
get_all_branches() { |
|
local include_remote="${1:-false}" |
|
local branches=() |
|
|
|
# Get local branches |
|
while IFS= read -r branch; do |
|
branch=$(echo "$branch" | sed 's/^[* ] //' | sed 's/^remotes\/origin\///') |
|
[[ "$branch" =~ ^(HEAD|main|master|develop)$ ]] && continue |
|
branches+=("$branch") |
|
done < <(git branch 2>/dev/null) |
|
|
|
# Get remote branches if requested |
|
if [[ "$include_remote" == "true" ]]; then |
|
while IFS= read -r branch; do |
|
branch=$(echo "$branch" | sed 's/^[* ] //' | sed 's/^remotes\/origin\///') |
|
[[ "$branch" =~ ^(HEAD|main|master|develop)$ ]] && continue |
|
# Check if we already have this branch locally |
|
local found=false |
|
for local_branch in "${branches[@]}"; do |
|
if [[ "$local_branch" == "$branch" ]]; then |
|
found=true |
|
break |
|
fi |
|
done |
|
[[ "$found" == "false" ]] && branches+=("🌐 origin/$branch") |
|
done < <(git branch -r 2>/dev/null) |
|
fi |
|
|
|
printf '%s\n' "${branches[@]}" | sort -u |
|
} |
|
|
|
get_branch_display_info() { |
|
local branch="$1" |
|
local clean_branch="$branch" |
|
|
|
# Remove remote prefix for processing |
|
if [[ "$branch" =~ ^🌐\ origin/ ]]; then |
|
clean_branch="${branch#🌐 origin/}" |
|
fi |
|
|
|
# Get last commit date and message |
|
local last_commit_date last_commit_msg |
|
last_commit_date=$(git log -1 --format="%cr" "$clean_branch" 2>/dev/null || echo "unknown") |
|
last_commit_msg=$(git log -1 --format="%s" "$clean_branch" 2>/dev/null | cut -c1-50) |
|
|
|
# Check if branch has upstream |
|
local upstream_status="" |
|
if git rev-parse --verify "$clean_branch@{upstream}" >/dev/null 2>&1; then |
|
local ahead behind |
|
ahead=$(git rev-list --count "$clean_branch@{upstream}..$clean_branch" 2>/dev/null || echo "0") |
|
behind=$(git rev-list --count "$clean_branch..$clean_branch@{upstream}" 2>/dev/null || echo "0") |
|
|
|
if [[ "$ahead" -gt 0 && "$behind" -gt 0 ]]; then |
|
upstream_status="↕${ahead}/${behind}" |
|
elif [[ "$ahead" -gt 0 ]]; then |
|
upstream_status="↑${ahead}" |
|
elif [[ "$behind" -gt 0 ]]; then |
|
upstream_status="↓${behind}" |
|
else |
|
upstream_status="=" |
|
fi |
|
fi |
|
|
|
printf "%s %s %s [%s]" "$branch" "$upstream_status" "$last_commit_date" "$last_commit_msg" |
|
} |
|
|
|
prepare_branch_list() { |
|
local include_remote="${1:-$SHOW_REMOTE}" |
|
ALL_BRANCHES=() |
|
|
|
while IFS= read -r branch; do |
|
[[ -n "$branch" ]] && ALL_BRANCHES+=("$(get_branch_display_info "$branch")") |
|
done < <(get_all_branches "$include_remote") |
|
} |
|
|
|
# Main interactive interface using gum (React-like layout) |
|
show_main_interface() { |
|
clear |
|
|
|
# Create the main interface layout similar to React components |
|
gum style \ |
|
--border normal \ |
|
--margin "1" \ |
|
--padding "1 2" \ |
|
--border-foreground "$BB_HEADER_COLOR" \ |
|
--bold \ |
|
--foreground "$BB_SUCCESS_COLOR" \ |
|
"BBManager - Git Branch Manager" |
|
|
|
echo |
|
|
|
gum style \ |
|
--foreground "#888888" \ |
|
--margin "0 1" \ |
|
"Select a branch to start or resume a git session:" |
|
|
|
echo |
|
|
|
# Get current branch status for display |
|
local current_branch |
|
current_branch=$(git branch --show-current 2>/dev/null || echo "No repository") |
|
|
|
# Create status display with icons and labels |
|
local status_display="${STATUS_ICONS[$BRANCH_STATUS]} ${STATUS_LABELS[$BRANCH_STATUS]}" |
|
local waiting_display="${STATUS_ICONS[WAITING]} ${STATUS_LABELS[WAITING]}" |
|
local idle_display="${STATUS_ICONS[IDLE]} ${STATUS_LABELS[IDLE]}" |
|
|
|
# Status information |
|
gum join --vertical \ |
|
"$(gum style --foreground "#888888" "Status: $status_display $waiting_display $idle_display")" \ |
|
"$(gum style --foreground "#888888" "Controls: ↑↓ Navigate Enter Select")" |
|
|
|
echo |
|
|
|
# Prepare branch options with numbers for quick selection |
|
local menu_options=( |
|
"🔀 Select/Switch Branch" |
|
"🆕 Create New Branch" |
|
"🔄 Merge Branch" |
|
"🗑️ Delete Branch" |
|
"⚙️ Configuration" |
|
"📊 Repository Status" |
|
"❓ Help" |
|
"🚪 Exit" |
|
) |
|
|
|
# Add numbered options for quick selection |
|
local numbered_options=() |
|
local i=0 |
|
for option in "${menu_options[@]}"; do |
|
numbered_options+=("[$i] $option") |
|
((i++)) |
|
done |
|
|
|
# Main menu selection |
|
local action |
|
action=$(gum choose \ |
|
--header "Choose an action:" \ |
|
--selected.foreground="$GIT_COLOR" \ |
|
--height=10 \ |
|
"${numbered_options[@]}") |
|
|
|
# Extract action from numbered format |
|
action=$(echo "$action" | sed 's/^\[[0-9]\] //') |
|
|
|
case "$action" in |
|
"🔀 Select/Switch Branch") |
|
action_select_branch |
|
;; |
|
"🆕 Create New Branch") |
|
action_create_branch |
|
;; |
|
"🔄 Merge Branch") |
|
action_merge_branch |
|
;; |
|
"🗑️ Delete Branch") |
|
action_delete_branch |
|
;; |
|
"⚙️ Configuration") |
|
action_config |
|
;; |
|
"📊 Repository Status") |
|
action_status |
|
;; |
|
"❓ Help") |
|
show_help_screen |
|
;; |
|
"🚪 Exit"|"") |
|
bb_info "Goodbye!" |
|
return 1 |
|
;; |
|
*) |
|
bb_error "Unknown action selected" |
|
;; |
|
esac |
|
} |
|
|
|
# Branch selection interface |
|
action_select_branch() { |
|
prepare_branch_list "$SHOW_REMOTE" |
|
|
|
if [[ ${#ALL_BRANCHES[@]} -eq 0 ]]; then |
|
bb_error "No branches available" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
|
|
# Toggle remote branches option |
|
local toggle_text="Show Remote Branches" |
|
[[ "$SHOW_REMOTE" == "true" ]] && toggle_text="Hide Remote Branches" |
|
|
|
# Add toggle option at the top |
|
local options=("📡 $toggle_text" "${ALL_BRANCHES[@]}") |
|
|
|
local selected_branch |
|
selected_branch=$(gum choose --header "Select a branch:" \ |
|
--selected.foreground="$GIT_COLOR" \ |
|
"${options[@]}") |
|
|
|
if [[ "$selected_branch" =~ ^📡 ]]; then |
|
# Toggle remote branch display |
|
if [[ "$SHOW_REMOTE" == "true" ]]; then |
|
SHOW_REMOTE=false |
|
else |
|
SHOW_REMOTE=true |
|
fi |
|
action_select_branch # Recursive call to refresh |
|
return |
|
fi |
|
|
|
if [[ -z "$selected_branch" ]]; then |
|
return 0 |
|
fi |
|
|
|
# Extract branch name from the display format |
|
local branch_name |
|
branch_name=$(echo "$selected_branch" | awk '{print $1}') |
|
|
|
if [[ "$branch_name" =~ ^🌐 ]]; then |
|
# Remote branch - ask to checkout as new local branch |
|
local clean_name="${branch_name#🌐 }" |
|
local local_name="${clean_name#origin/}" |
|
local new_local_name |
|
new_local_name=$(bb_input "Create local branch from $clean_name as:" "$local_name") |
|
|
|
[[ -z "$new_local_name" ]] && new_local_name="$local_name" |
|
|
|
bb_info "Creating local branch '$new_local_name' from '$clean_name'" |
|
BRANCH_STATUS="BUSY" |
|
|
|
if git checkout -b "$new_local_name" "$clean_name" 2>/dev/null; then |
|
bb_success "Successfully checked out branch: $new_local_name" |
|
CURRENT_BRANCH="$new_local_name" |
|
BRANCH_STATUS="SUCCESS" |
|
else |
|
bb_error "Failed to create branch: $new_local_name" |
|
BRANCH_STATUS="ERROR" |
|
fi |
|
else |
|
# Local branch |
|
bb_info "Switching to branch: $branch_name" |
|
BRANCH_STATUS="BUSY" |
|
|
|
if git checkout "$branch_name" 2>/dev/null; then |
|
bb_success "Successfully switched to: $branch_name" |
|
CURRENT_BRANCH="$branch_name" |
|
BRANCH_STATUS="SUCCESS" |
|
else |
|
bb_error "Failed to switch to branch: $branch_name" |
|
BRANCH_STATUS="ERROR" |
|
fi |
|
fi |
|
|
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
action_create_branch() { |
|
bb_header |
|
echo |
|
gum style --foreground "$BB_HEADER_COLOR" --bold "Create New Branch" |
|
echo |
|
|
|
# Show available branch types using gum |
|
bb_info "Available branch types:" |
|
local types=(feature bugfix hotfix release experiment docs chore ci refactor perf test) |
|
local type_options=() |
|
|
|
for type in "${types[@]}"; do |
|
type_options+=("$type - ${BRANCH_DESCRIPTIONS[$type]}") |
|
done |
|
|
|
local selected_type |
|
selected_type=$(gum choose --header "Select branch type:" \ |
|
--selected.foreground="$GIT_COLOR" \ |
|
"${type_options[@]}") |
|
|
|
if [[ -z "$selected_type" ]]; then |
|
bb_warning "Branch creation cancelled" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 0 |
|
fi |
|
|
|
local branch_type |
|
branch_type=$(echo "$selected_type" | awk '{print $1}') |
|
|
|
validate_branch_type "$branch_type" || { |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
} |
|
|
|
# Get issue number (optional for some types) |
|
local issue_number="" |
|
if [[ "$branch_type" =~ ^(feature|bugfix|hotfix)$ ]]; then |
|
issue_number=$(bb_input "Issue number (required for $branch_type):") |
|
if [[ -z "$issue_number" ]]; then |
|
bb_error "Issue number is required for $branch_type branches" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
elif [[ "$branch_type" == "release" ]]; then |
|
issue_number=$(bb_input "Version number (e.g., 1.2.0):") |
|
if [[ -z "$issue_number" ]]; then |
|
bb_error "Version number is required for release branches" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
else |
|
issue_number=$(bb_input "Issue number or identifier (optional):") |
|
fi |
|
|
|
# Get description |
|
local description |
|
description=$(bb_input "Branch description:") |
|
|
|
if [[ -z "$description" && -z "$issue_number" ]]; then |
|
bb_error "Either issue number or description is required" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
|
|
# Create the branch using brancher.sh function |
|
BRANCH_STATUS="BUSY" |
|
bb_info "Creating branch..." |
|
|
|
if create_standard_branch "$branch_type" "$issue_number" "$description"; then |
|
BRANCH_STATUS="SUCCESS" |
|
CURRENT_BRANCH=$(git branch --show-current) |
|
bb_success "Branch created and checked out successfully: $CURRENT_BRANCH" |
|
else |
|
BRANCH_STATUS="ERROR" |
|
bb_error "Failed to create branch" |
|
fi |
|
|
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
action_merge_branch() { |
|
bb_header |
|
echo |
|
gum style --foreground "$BB_HEADER_COLOR" --bold "Merge Branch" |
|
echo |
|
|
|
local current_branch |
|
current_branch=$(git branch --show-current) |
|
|
|
if [[ -z "$current_branch" ]]; then |
|
bb_error "Not on any branch" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
|
|
# Determine appropriate target branch |
|
local default_target="develop" |
|
if [[ "$current_branch" =~ ^hotfix/ ]]; then |
|
default_target="main" |
|
elif [[ "$current_branch" =~ ^release/ ]]; then |
|
default_target="main" |
|
fi |
|
|
|
local target_branch |
|
target_branch=$(bb_input "Target branch for merge:" "$default_target") |
|
[[ -z "$target_branch" ]] && target_branch="$default_target" |
|
|
|
# Check if target branch exists |
|
if ! git rev-parse --verify --quiet "$target_branch" >/dev/null; then |
|
bb_error "Target branch '$target_branch' does not exist" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
|
|
# Show merge information |
|
gum join --vertical \ |
|
"$(gum style --foreground "$BB_WARNING_COLOR" "About to merge:")" \ |
|
"$(gum style --foreground "$GIT_COLOR" " From: $current_branch")" \ |
|
"$(gum style --foreground "$GIT_COLOR" " To: $target_branch")" \ |
|
"" |
|
|
|
if ! bb_confirm "Continue with merge?"; then |
|
bb_info "Merge cancelled" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 0 |
|
fi |
|
|
|
BRANCH_STATUS="BUSY" |
|
|
|
# Switch to target branch and pull latest |
|
bb_info "Switching to $target_branch and pulling latest changes" |
|
if ! git checkout "$target_branch" 2>/dev/null; then |
|
bb_error "Failed to checkout target branch '$target_branch'" |
|
bb_info "Make sure the branch exists locally. Run 'git branch' to see available branches." |
|
BRANCH_STATUS="ERROR" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
bb_success "Successfully switched to $target_branch" |
|
|
|
# Try to pull latest changes, but don't fail if there's no remote or network issues |
|
bb_info "Checking for remote tracking branch" |
|
if git rev-parse --verify --quiet "origin/$target_branch" >/dev/null 2>&1; then |
|
bb_info "Pulling latest changes from remote" |
|
if git pull origin "$target_branch" 2>/dev/null; then |
|
bb_success "Successfully pulled latest changes" |
|
else |
|
bb_warning "Could not pull from remote (network issue). Continuing with local branch." |
|
fi |
|
else |
|
bb_warning "No remote tracking branch found for '$target_branch'. Continuing with local branch." |
|
fi |
|
|
|
# Perform merge |
|
bb_info "Merging $current_branch into $target_branch" |
|
if git merge --no-ff "$current_branch" 2>&1; then |
|
bb_success "Successfully merged $current_branch into $target_branch" |
|
|
|
# Ask if they want to push |
|
if bb_confirm "Push changes to remote?" true; then |
|
if git push origin "$target_branch" 2>&1; then |
|
bb_success "Changes pushed to remote" |
|
else |
|
bb_warning "Failed to push to remote. You may need to push manually later." |
|
fi |
|
fi |
|
|
|
# Ask if they want to delete the merged branch |
|
if bb_confirm "Delete merged branch '$current_branch'?"; then |
|
if git branch -d "$current_branch" 2>/dev/null; then |
|
bb_success "Deleted branch: $current_branch" |
|
else |
|
bb_warning "Could not delete branch '$current_branch'. You may need to force delete it manually." |
|
fi |
|
fi |
|
|
|
BRANCH_STATUS="SUCCESS" |
|
else |
|
bb_error "Merge failed - resolve conflicts and try again" |
|
bb_info "Run 'git status' to see conflicted files" |
|
BRANCH_STATUS="ERROR" |
|
fi |
|
|
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
action_delete_branch() { |
|
bb_header |
|
echo |
|
gum style --foreground "$BB_ERROR_COLOR" --bold "Delete Branch" |
|
echo |
|
|
|
# Get local branches (excluding protected ones) |
|
local local_branches=() |
|
while IFS= read -r branch; do |
|
branch=$(echo "$branch" | sed 's/^[* ] //') |
|
[[ "$branch" =~ ^(main|master|develop)$ ]] && continue |
|
local_branches+=("$branch") |
|
done < <(git branch 2>/dev/null) |
|
|
|
if [[ ${#local_branches[@]} -eq 0 ]]; then |
|
bb_error "No branches available for deletion" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
|
|
local branch_to_delete |
|
branch_to_delete=$(gum choose --header "Select branch to delete:" \ |
|
--selected.foreground="$GIT_COLOR" \ |
|
"${local_branches[@]}") |
|
|
|
if [[ -z "$branch_to_delete" ]]; then |
|
bb_info "Deletion cancelled" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 0 |
|
fi |
|
|
|
local current_branch |
|
current_branch=$(git branch --show-current) |
|
|
|
# Can't delete current branch |
|
if [[ "$branch_to_delete" == "$current_branch" ]]; then |
|
bb_error "Cannot delete current branch. Switch to another branch first." |
|
gum input --placeholder "Press Enter to continue..." |
|
return 1 |
|
fi |
|
|
|
# Show warning |
|
gum join --vertical \ |
|
"$(gum style --foreground "$BB_ERROR_COLOR" "⚠️ WARNING: This will permanently delete the branch!")" \ |
|
"$(gum style --foreground "$BB_WARNING_COLOR" "Branch to delete: $branch_to_delete")" \ |
|
"" |
|
|
|
# Require typing DELETE for confirmation |
|
local confirm_text |
|
confirm_text=$(bb_input "Type 'DELETE' to confirm:") |
|
|
|
if [[ "$confirm_text" != "DELETE" ]]; then |
|
bb_info "Deletion cancelled" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 0 |
|
fi |
|
|
|
BRANCH_STATUS="BUSY" |
|
|
|
# Check if branch has been merged |
|
if git branch --merged | grep -q "^[* ] $branch_to_delete$"; then |
|
# Safe deletion |
|
if git branch -d "$branch_to_delete" 2>/dev/null; then |
|
bb_success "Successfully deleted branch: $branch_to_delete" |
|
BRANCH_STATUS="SUCCESS" |
|
else |
|
bb_error "Failed to delete branch: $branch_to_delete" |
|
BRANCH_STATUS="ERROR" |
|
fi |
|
else |
|
# Force deletion required |
|
bb_warning "Branch has unmerged changes!" |
|
if bb_confirm "Force delete anyway?"; then |
|
if git branch -D "$branch_to_delete" 2>/dev/null; then |
|
bb_success "Force deleted branch: $branch_to_delete" |
|
BRANCH_STATUS="SUCCESS" |
|
else |
|
bb_error "Failed to force delete branch: $branch_to_delete" |
|
BRANCH_STATUS="ERROR" |
|
fi |
|
else |
|
bb_info "Deletion cancelled" |
|
gum input --placeholder "Press Enter to continue..." |
|
return 0 |
|
fi |
|
fi |
|
|
|
# Ask about remote deletion |
|
if git ls-remote --heads origin "$branch_to_delete" 2>/dev/null | grep -q "$branch_to_delete"; then |
|
if bb_confirm "Also delete remote branch?"; then |
|
git push origin --delete "$branch_to_delete" 2>/dev/null && bb_success "Deleted remote branch: $branch_to_delete" |
|
fi |
|
fi |
|
|
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
action_config() { |
|
bb_header |
|
echo |
|
gum style --foreground "$BB_HEADER_COLOR" --bold "BBManager Configuration" |
|
echo |
|
|
|
# Show current configuration |
|
gum join --vertical \ |
|
"$(gum style --foreground "$BB_WARNING_COLOR" "Current Configuration:")" \ |
|
"$(gum style --foreground "#888888" "Config file: $BB_CONFIG_FILE")" \ |
|
"$(gum style --foreground "#888888" "Debug mode: ${gum_debug:-off}")" \ |
|
"$(gum style --foreground "#888888" "Git repository: $(git rev-parse --show-toplevel 2>/dev/null || echo 'Not in repository')")" \ |
|
"" |
|
|
|
bb_info "Default base branches:" |
|
for type in "${!BASE_BRANCHES[@]}"; do |
|
gum style --foreground "#888888" " $(printf "%-12s -> %s" "$type" "${BASE_BRANCHES[$type]}")" |
|
done |
|
echo |
|
|
|
# Configuration options |
|
local config_action |
|
config_action=$(gum choose --header "Configuration Options:" \ |
|
--selected.foreground="$GIT_COLOR" \ |
|
"Edit base branch mappings" \ |
|
"Set default branch types" \ |
|
"Configure aliases" \ |
|
"Reset to defaults" \ |
|
"Back to main menu") |
|
|
|
case "$config_action" in |
|
"Edit base branch mappings") |
|
bb_info "Edit Base Branch Mappings" |
|
for type in "${!BASE_BRANCHES[@]}"; do |
|
local new_base |
|
new_base=$(bb_input "Base branch for $type:" "${BASE_BRANCHES[$type]}") |
|
[[ -n "$new_base" ]] && BASE_BRANCHES[$type]="$new_base" |
|
done |
|
bb_success "Base branch mappings updated" |
|
;; |
|
"Set default branch types") |
|
bb_info "Default Branch Types" |
|
gum style --foreground "#888888" "Current types: ${!BRANCH_TYPES[*]}" |
|
bb_warning "This feature is planned for future release" |
|
;; |
|
"Configure aliases") |
|
bb_info "Configure Aliases" |
|
bb_warning "This feature is planned for future release" |
|
;; |
|
"Reset to defaults") |
|
bb_info "Reset to Defaults" |
|
if bb_confirm "Reset all configuration to defaults?"; then |
|
[[ -f "$BB_CONFIG_FILE" ]] && rm "$BB_CONFIG_FILE" |
|
bb_success "Configuration reset to defaults" |
|
fi |
|
;; |
|
*) |
|
return 0 |
|
;; |
|
esac |
|
|
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
action_status() { |
|
bb_header |
|
echo |
|
gum style --foreground "$BB_HEADER_COLOR" --bold "Repository Status" |
|
echo |
|
|
|
# Git status |
|
bb_info "Git Status:" |
|
local git_status |
|
git_status=$(git status --short 2>/dev/null || echo "Not in a git repository") |
|
if [[ -n "$git_status" ]]; then |
|
echo "$git_status" |
|
else |
|
gum style --foreground "#888888" "Working tree clean" |
|
fi |
|
echo |
|
|
|
# Branch information |
|
bb_info "Branch Information:" |
|
local current_branch |
|
current_branch=$(git branch --show-current 2>/dev/null) |
|
|
|
if [[ -n "$current_branch" ]]; then |
|
gum style --foreground "#888888" "Current branch: $current_branch" |
|
|
|
# Check for upstream |
|
if git rev-parse --verify "$current_branch@{upstream}" >/dev/null 2>&1; then |
|
local ahead behind |
|
ahead=$(git rev-list --count "$current_branch@{upstream}..$current_branch" 2>/dev/null || echo "0") |
|
behind=$(git rev-list --count "$current_branch..$current_branch@{upstream}" 2>/dev/null || echo "0") |
|
gum style --foreground "#888888" "Upstream: ${current_branch}@{upstream}" |
|
gum style --foreground "#888888" "Ahead: $ahead commits" |
|
gum style --foreground "#888888" "Behind: $behind commits" |
|
else |
|
gum style --foreground "#888888" "No upstream configured" |
|
fi |
|
|
|
# Last commit |
|
local last_commit |
|
last_commit=$(git log -1 --format="%h %s (%cr)" 2>/dev/null || echo "No commits") |
|
gum style --foreground "#888888" "Last commit: $last_commit" |
|
else |
|
gum style --foreground "#888888" "Not on any branch" |
|
fi |
|
echo |
|
|
|
# Recent branches |
|
bb_info "Recent Branches:" |
|
local recent_branches |
|
recent_branches=$(git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short) (%(committerdate:relative))' 2>/dev/null | head -5) |
|
if [[ -n "$recent_branches" ]]; then |
|
echo "$recent_branches" | while read -r line; do |
|
gum style --foreground "#888888" " $line" |
|
done |
|
else |
|
gum style --foreground "#888888" " No branches found" |
|
fi |
|
echo |
|
|
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
# Interactive main loop using gum with keyboard shortcuts |
|
interactive_mode() { |
|
# Initialize |
|
validate_git_repo || { |
|
bb_error "Not in a git repository" |
|
return 1 |
|
} |
|
|
|
CURRENT_BRANCH=$(git branch --show-current) |
|
|
|
# Check if gum is available |
|
if ! command -v gum >/dev/null 2>&1; then |
|
echo "Gum is not installed. Checking installation..." |
|
check_gum_installation || return 1 |
|
fi |
|
|
|
# Main loop with keyboard shortcut handling |
|
while true; do |
|
# Check for keyboard shortcuts using environment or direct input |
|
local shortcut_action="" |
|
|
|
# Show main interface |
|
if ! show_main_interface; then |
|
break |
|
fi |
|
|
|
# Handle quick shortcuts if implemented |
|
case "$shortcut_action" in |
|
"n"|"N") |
|
action_create_branch |
|
;; |
|
"m"|"M") |
|
action_merge_branch |
|
;; |
|
"d"|"D") |
|
action_delete_branch |
|
;; |
|
"c"|"C") |
|
action_config |
|
;; |
|
"s"|"S") |
|
action_status |
|
;; |
|
"h"|"H"|"?") |
|
show_help_screen |
|
;; |
|
"q"|"Q") |
|
bb_info "Goodbye!" |
|
break |
|
;; |
|
[0-9]) |
|
# Quick numeric selection (could be implemented) |
|
;; |
|
esac |
|
done |
|
} |
|
|
|
show_help_screen() { |
|
bb_header |
|
echo |
|
gum style --foreground "$BB_HEADER_COLOR" --bold "BBManager Help" |
|
echo |
|
|
|
gum join --vertical \ |
|
"$(gum style --foreground "$BB_WARNING_COLOR" "Navigation:")" \ |
|
"$(gum style --foreground "#888888" " ↑↓ Arrow Keys Navigate branch list")" \ |
|
"$(gum style --foreground "#888888" " Enter Select highlighted branch")" \ |
|
"$(gum style --foreground "#888888" " ESC Return to main menu")" \ |
|
"" \ |
|
"$(gum style --foreground "$BB_WARNING_COLOR" "Quick Actions:")" \ |
|
"$(gum style --foreground "#888888" " N Create new branch")" \ |
|
"$(gum style --foreground "#888888" " M Merge current branch")" \ |
|
"$(gum style --foreground "#888888" " D Delete branch")" \ |
|
"$(gum style --foreground "#888888" " C Configuration")" \ |
|
"$(gum style --foreground "#888888" " S Show status")" \ |
|
"$(gum style --foreground "#888888" " H or ? Show this help")" \ |
|
"$(gum style --foreground "#888888" " Q Quit")" \ |
|
"" \ |
|
"$(gum style --foreground "$BB_WARNING_COLOR" "Status Indicators:")" |
|
|
|
for status in BUSY WAITING IDLE ERROR SUCCESS INFO WARNING; do |
|
gum style --foreground "#888888" " ${STATUS_ICONS[$status]} ${STATUS_LABELS[$status]}" |
|
done |
|
|
|
echo |
|
gum input --placeholder "Press Enter to continue..." |
|
} |
|
|
|
check_gum_installation() { |
|
if ! command -v gum >/dev/null 2>&1; then |
|
echo -e "\033[1;33m⚠️ gum is not installed\033[0m" |
|
echo |
|
echo -e "\033[0;34mgum is required for this script to work properly.\033[0m" |
|
echo |
|
echo -e "\033[0;34mInstall with one of the following commands:\033[0m" |
|
echo |
|
echo -e "\033[0;37m# macOS or Linux\033[0m" |
|
echo -e "\033[0;32mbrew install gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Arch Linux (btw)\033[0m" |
|
echo -e "\033[0;32mpacman -S gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Nix\033[0m" |
|
echo -e "\033[0;32mnix-env -iA nixpkgs.gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Flox\033[0m" |
|
echo -e "\033[0;32mflox install gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Windows (via WinGet or Scoop)\033[0m" |
|
echo -e "\033[0;32mwinget install charmbracelet.gum\033[0m" |
|
echo -e "\033[0;32mscoop install charm-gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Debian/Ubuntu\033[0m" |
|
echo -e "\033[0;32msudo mkdir -p /etc/apt/keyrings\033[0m" |
|
echo -e "\033[0;32mcurl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg\033[0m" |
|
echo -e "\033[0;32mecho \"deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *\" | sudo tee /etc/apt/sources.list.d/charm.list\033[0m" |
|
echo -e "\033[0;32msudo apt update && sudo apt install gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Fedora/RHEL/OpenSuse\033[0m" |
|
echo -e "\033[0;32mecho '[charm]\nname=Charm\nbaseurl=https://repo.charm.sh/yum/\nenabled=1\ngpgcheck=1\ngpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo\033[0m" |
|
echo -e "\033[0;32msudo rpm --import https://repo.charm.sh/yum/gpg.key\033[0m" |
|
echo -e "\033[0;32msudo yum install gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# FreeBSD\033[0m" |
|
echo -e "\033[0;32msudo pkg install gum\033[0m" |
|
echo |
|
echo -e "\033[0;37m# Go\033[0m" |
|
echo -e "\033[0;32mgo install github.com/charmbracelet/gum@latest\033[0m" |
|
echo |
|
read -p "Install gum now using apt? (y/N): " -n 1 -r |
|
echo |
|
if [[ $REPLY =~ ^[Yy]$ ]]; then |
|
echo -e "\033[0;34mℹ️ Installing gum...\033[0m" |
|
if sudo apt update && sudo apt install gum; then |
|
echo -e "\033[0;32m🎉 gum installed successfully\033[0m" |
|
return 0 |
|
else |
|
echo -e "\033[0;31m❌ Failed to install gum\033[0m" |
|
return 1 |
|
fi |
|
else |
|
echo -e "\033[0;31m❌ gum is required for this script\033[0m" |
|
return 1 |
|
fi |
|
else |
|
local gum_version |
|
gum_version=$(gum --version 2>/dev/null || echo "unknown") |
|
bb_success "gum is already installed (version: $gum_version)" |
|
gum style --foreground "$BB_HEADER_COLOR" "Location: $(which gum)" |
|
return 0 |
|
fi |
|
} |
|
|
|
# Main function for gum integration |
|
main() { |
|
# Handle command line arguments directly |
|
local action="" |
|
local check_gum_flag=false |
|
local debug_flag=false |
|
local no_color_flag=false |
|
|
|
# Parse arguments |
|
while [[ $# -gt 0 ]]; do |
|
case $1 in |
|
--check-gum) |
|
check_gum_flag=true |
|
shift |
|
;; |
|
--debug) |
|
debug_flag=true |
|
shift |
|
;; |
|
--no-color) |
|
no_color_flag=true |
|
shift |
|
;; |
|
--config-file) |
|
BB_CONFIG_FILE="$2" |
|
shift 2 |
|
;; |
|
*) |
|
action="$1" |
|
shift |
|
break # Exit the parsing loop to preserve remaining arguments |
|
;; |
|
esac |
|
done |
|
|
|
# Handle flags first |
|
if [[ "$check_gum_flag" == "true" ]]; then |
|
check_gum_installation |
|
return $? |
|
fi |
|
|
|
if [[ "$debug_flag" == "true" ]]; then |
|
export DEBUG=1 |
|
fi |
|
|
|
if [[ "$no_color_flag" == "true" ]]; then |
|
export NO_COLOR=1 |
|
fi |
|
|
|
# Default action |
|
[[ -z "$action" ]] && action="select" |
|
|
|
# Set up cleanup |
|
trap 'rm -rf "$BB_TEMP_DIR" 2>/dev/null; [[ -f "$BB_LOCK_FILE" ]] && rm -f "$BB_LOCK_FILE"' EXIT |
|
|
|
# Create temp directory |
|
mkdir -p "$BB_TEMP_DIR" |
|
|
|
# Handle actions |
|
case "$action" in |
|
"select"|"interactive"|"") |
|
interactive_mode |
|
;; |
|
"create"|"new") |
|
validate_git_repo || return 1 |
|
action_create_branch |
|
;; |
|
"merge") |
|
validate_git_repo || return 1 |
|
action_merge_branch |
|
;; |
|
"delete"|"del"|"remove") |
|
validate_git_repo || return 1 |
|
action_delete_branch |
|
;; |
|
"config"|"conf") |
|
action_config |
|
;; |
|
"status"|"st") |
|
validate_git_repo || return 1 |
|
action_status |
|
;; |
|
"help"|"h"|"--help") |
|
show_help_screen |
|
;; |
|
"check-gum"|"check_gum") |
|
check_gum_installation |
|
;; |
|
"list"|"l"|"--list") |
|
validate_git_repo || return 1 |
|
list_branches |
|
;; |
|
"legacy") |
|
validate_git_repo || return 1 |
|
create_feature_branch_legacy "$@" |
|
;; |
|
# Handle direct brancher.sh style commands |
|
"feature"|"bugfix"|"hotfix"|"release"|"experiment"|"docs"|"chore"|"ci"|"refactor"|"perf"|"test") |
|
validate_git_repo || return 1 |
|
brancher "$action" "$@" |
|
;; |
|
*) |
|
# Check if it's a brancher-style command |
|
if [[ -n "${BRANCH_TYPES[$action]:-}" ]]; then |
|
validate_git_repo || return 1 |
|
brancher "$action" "$@" |
|
else |
|
bb_error "Unknown action: $action" |
|
echo |
|
show_brancher_usage |
|
return 1 |
|
fi |
|
;; |
|
esac |
|
} |
|
|
|
# Export functions for external use and brancher compatibility |
|
export -f main interactive_mode action_select_branch action_create_branch |
|
export -f action_merge_branch action_delete_branch action_config action_status |
|
export -f validate_git_repo log_info log_success log_warning log_error |
|
export -f create_standard_branch validate_branch_type sanitize_input check_branch_exists |
|
|
|
# Legacy brancher.sh aliases for backward compatibility |
|
alias br="bbmanager" |
|
alias branch="bbmanager" |
|
alias fb="bbmanager legacy" |
|
|
|
# Auto-completion for bash (from brancher.sh) |
|
if [[ -n "${BASH_VERSION:-}" ]]; then |
|
_bbmanager_completion() { |
|
local cur prev |
|
COMPREPLY=() |
|
cur="${COMP_WORDS[COMP_CWORD]}" |
|
prev="${COMP_WORDS[COMP_CWORD-1]}" |
|
|
|
if [[ ${COMP_CWORD} == 1 ]]; then |
|
COMPREPLY=($(compgen -W "${!BRANCH_TYPES[*]} help list legacy select create merge delete config status check-gum" -- "$cur")) |
|
fi |
|
} |
|
complete -F _bbmanager_completion bbmanager |
|
fi |
|
|
|
# Main brancher function for legacy compatibility |
|
brancher() { |
|
# Validate git repository |
|
validate_git_repo || return 1 |
|
|
|
local first_arg="${1:-}" |
|
|
|
if [[ "$first_arg" == "help" ]] || [[ "$first_arg" == "-h" ]] || [[ "$first_arg" == "--help" ]]; then |
|
show_brancher_usage |
|
return 0 |
|
elif [[ "$first_arg" == "list" ]] || [[ "$first_arg" == "-l" ]] || [[ "$first_arg" == "--list" ]]; then |
|
list_branches |
|
return 0 |
|
elif [[ "$first_arg" == "legacy" ]]; then |
|
shift |
|
create_feature_branch_legacy "$@" |
|
return $? |
|
elif [[ -z "$first_arg" ]]; then |
|
bb_error "Branch type required" |
|
show_brancher_usage |
|
return 1 |
|
else |
|
local branch_type="$1" |
|
local issue_number="$2" |
|
local description="$3" |
|
|
|
# Additional arguments become part of description |
|
if [[ $# -gt 3 ]]; then |
|
shift 3 |
|
description="$description $*" |
|
fi |
|
|
|
validate_branch_type "$branch_type" || return 1 |
|
create_standard_branch "$branch_type" "$issue_number" "$description" |
|
return $? |
|
fi |
|
} |
|
|
|
# Show usage for brancher compatibility |
|
show_brancher_usage() { |
|
cat << EOF |
|
${CYAN}Git-Flow Branching Automation Utility (BBManager)${NC} |
|
|
|
${YELLOW}USAGE:${NC} |
|
bbmanager <type> [issue-number] [description] |
|
bbmanager help |
|
bbmanager list |
|
bbmanager legacy [description...] |
|
|
|
${YELLOW}BRANCH TYPES:${NC} |
|
EOF |
|
|
|
for type in "${!BRANCH_TYPES[@]}"; do |
|
printf " %-12s %s\n" "$type" "${BRANCH_DESCRIPTIONS[$type]}" |
|
done |
|
|
|
cat << EOF |
|
|
|
${YELLOW}EXAMPLES:${NC} |
|
bbmanager feature 123 add-login-api -> feature/123-add-login-api |
|
bbmanager bugfix 456 fix-nullpointer -> bugfix/456-fix-nullpointer |
|
bbmanager hotfix 789 patch-security -> hotfix/789-patch-security |
|
bbmanager release 1.4.0 -> release/1.4.0 |
|
bbmanager experiment new-graphql -> experiment/new-graphql-prototype |
|
bbmanager docs update-api -> docs/update-api-reference |
|
bbmanager chore update-deps -> chore/update-dependencies |
|
bbmanager ci add-actions -> ci/add-github-actions |
|
bbmanager refactor extract-service -> refactor/extract-user-service |
|
bbmanager perf cache-token -> perf/cache-auth-token |
|
bbmanager test integration -> test/add-integration-tests |
|
bbmanager legacy utils package -> feature/username/utils-package/2025-07-08/1 |
|
|
|
${YELLOW}SPECIAL COMMANDS:${NC} |
|
help Show this help message |
|
list List all branches by type |
|
legacy Use legacy date-indexed feature branch naming |
|
select Interactive branch selection |
|
create Interactive branch creation |
|
merge Interactive branch merging |
|
delete Interactive branch deletion |
|
config Configuration management |
|
status Repository status |
|
|
|
${YELLOW}ENVIRONMENT VARIABLES:${NC} |
|
DEBUG=1 Enable debug output |
|
EOF |
|
} |
|
|
|
# Run main function if script is executed directly |
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then |
|
main "$@" |
|
fi |