713 lines
21 KiB
Bash
Executable File
713 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# Interactive User Removal Tool for GitHub Repositories
|
||
# Supports wildcard matching and handles both user and organization repositories
|
||
|
||
set -euo pipefail
|
||
|
||
# Color definitions
|
||
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 PURPLE='\033[0;35m'
|
||
readonly BOLD='\033[1m'
|
||
readonly NC='\033[0m' # No Color
|
||
|
||
# Global variables
|
||
GITHUB_TOKEN=""
|
||
ROOT_USER=""
|
||
USER_TO_REMOVE=""
|
||
REPO_PATTERN=""
|
||
CONFIRMATION_MODE="ask" # ask, yes_all, no_all
|
||
TOTAL_REPOS=0
|
||
PROCESSED_REPOS=0
|
||
SUCCESSFUL_REMOVALS=0
|
||
FAILED_REMOVALS=0
|
||
SKIPPED_REMOVALS=0
|
||
NON_INTERACTIVE=false
|
||
|
||
# Required tools
|
||
REQUIRED_TOOLS=(curl jq)
|
||
|
||
# Utility 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}"
|
||
}
|
||
|
||
log_progress() {
|
||
echo -e "${CYAN}🔄 $1${NC}"
|
||
}
|
||
|
||
log_header() {
|
||
echo -e "\n${BOLD}${PURPLE}$1${NC}"
|
||
echo -e "${PURPLE}$(printf '=%.0s' {1..50})${NC}"
|
||
}
|
||
|
||
# Check required tools
|
||
check_dependencies() {
|
||
log_header "Checking Dependencies"
|
||
for tool in "${REQUIRED_TOOLS[@]}"; do
|
||
if ! command -v "$tool" &>/dev/null; then
|
||
log_error "Required tool '$tool' is not installed. Please install it and try again."
|
||
exit 1
|
||
fi
|
||
log_success "$tool is available"
|
||
done
|
||
}
|
||
|
||
# Input collection functions
|
||
collect_github_token() {
|
||
log_header "GitHub Authentication"
|
||
echo -e "${YELLOW}Enter your GitHub Personal Access Token:${NC}"
|
||
echo -e "${CYAN}(Token needs 'repo' and 'admin:org' permissions)${NC}"
|
||
read -s -r -p "Token: " GITHUB_TOKEN
|
||
echo
|
||
|
||
if [[ -z "$GITHUB_TOKEN" ]]; then
|
||
log_error "GitHub token cannot be empty"
|
||
exit 1
|
||
fi
|
||
|
||
# Validate token
|
||
log_progress "Validating GitHub token..."
|
||
local response
|
||
response=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user)
|
||
local http_code="${response: -3}"
|
||
|
||
if [[ "$http_code" != "200" ]]; then
|
||
log_error "Invalid GitHub token or insufficient permissions (HTTP: $http_code)"
|
||
if [[ "$http_code" == "401" ]]; then
|
||
log_error "Token is invalid or expired"
|
||
elif [[ "$http_code" == "403" ]]; then
|
||
log_error "Token lacks required permissions (need 'repo' and 'admin:org')"
|
||
fi
|
||
exit 1
|
||
fi
|
||
|
||
# Get user info for confirmation
|
||
local user_info
|
||
user_info=$(echo "${response%???}" | jq -r '.login // "unknown"' 2>/dev/null)
|
||
log_success "GitHub token validated successfully (authenticated as: $user_info)"
|
||
}
|
||
|
||
collect_root_user() {
|
||
log_header "Root Account Information"
|
||
echo -e "${YELLOW}Enter the root GitHub username/organization:${NC}"
|
||
read -r -p "Root user: " ROOT_USER
|
||
|
||
if [[ -z "$ROOT_USER" ]]; then
|
||
log_error "Root user cannot be empty"
|
||
exit 1
|
||
fi
|
||
|
||
log_success "Root user set to: $ROOT_USER"
|
||
}
|
||
|
||
collect_user_to_remove() {
|
||
log_header "User Removal Target"
|
||
echo -e "${YELLOW}Enter the username to remove from repositories:${NC}"
|
||
read -r -p "User to remove: " USER_TO_REMOVE
|
||
|
||
if [[ -z "$USER_TO_REMOVE" ]]; then
|
||
log_error "User to remove cannot be empty"
|
||
exit 1
|
||
fi
|
||
|
||
log_success "Target user: $USER_TO_REMOVE"
|
||
}
|
||
|
||
collect_repo_pattern() {
|
||
log_header "Repository Pattern"
|
||
echo -e "${YELLOW}Enter repository pattern (supports wildcards):${NC}"
|
||
echo -e "${CYAN}Examples: *-go-api*, frontend-*, *-service, specific-repo${NC}"
|
||
read -r -p "Repository pattern: " REPO_PATTERN
|
||
|
||
if [[ -z "$REPO_PATTERN" ]]; then
|
||
log_error "Repository pattern cannot be empty"
|
||
exit 1
|
||
fi
|
||
|
||
log_success "Repository pattern: $REPO_PATTERN"
|
||
}
|
||
|
||
# GitHub API functions
|
||
fetch_user_repos() {
|
||
local user="$1"
|
||
local page=1
|
||
local all_repos=()
|
||
|
||
# Don't log here to avoid capturing log messages as repo names
|
||
while true; do
|
||
local response
|
||
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||
"https://api.github.com/users/$user/repos?per_page=100&page=$page&type=all")
|
||
|
||
# Check if response is valid JSON and contains repositories
|
||
if ! echo "$response" | jq empty 2>/dev/null; then
|
||
log_error "Invalid JSON response from GitHub API"
|
||
break
|
||
fi
|
||
|
||
# Check for authentication errors
|
||
local error_message
|
||
error_message=$(echo "$response" | jq -r '.message // empty' 2>/dev/null)
|
||
if [[ "$error_message" == "Bad credentials" ]]; then
|
||
log_error "GitHub token is invalid or expired"
|
||
log_error "Please check your GITHUB_TOKEN environment variable"
|
||
exit 1
|
||
fi
|
||
|
||
local repos
|
||
repos=$(echo "$response" | jq -r '.[].full_name // empty' 2>/dev/null)
|
||
|
||
if [[ -z "$repos" ]]; then
|
||
break
|
||
fi
|
||
|
||
while IFS= read -r repo; do
|
||
# Validate repository name format (should be owner/repo)
|
||
if [[ "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
|
||
all_repos+=("$repo")
|
||
else
|
||
log_warning "Skipping invalid repository name: $repo"
|
||
fi
|
||
done <<< "$repos"
|
||
|
||
((page++))
|
||
done
|
||
|
||
# Only print if there are repos to avoid unbound variable error
|
||
if [[ ${#all_repos[@]} -gt 0 ]]; then
|
||
printf '%s\n' "${all_repos[@]}"
|
||
fi
|
||
}
|
||
|
||
fetch_org_repos() {
|
||
local org="$1"
|
||
local page=1
|
||
local all_repos=()
|
||
|
||
# Don't log here to avoid capturing log messages as repo names
|
||
while true; do
|
||
local response
|
||
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||
"https://api.github.com/orgs/$org/repos?per_page=100&page=$page&type=all")
|
||
|
||
# Check if response is valid JSON and contains repositories
|
||
if ! echo "$response" | jq empty 2>/dev/null; then
|
||
log_error "Invalid JSON response from GitHub API for organization: $org"
|
||
break
|
||
fi
|
||
|
||
# Check for authentication errors
|
||
local error_message
|
||
error_message=$(echo "$response" | jq -r '.message // empty' 2>/dev/null)
|
||
if [[ "$error_message" == "Bad credentials" ]]; then
|
||
log_error "GitHub token is invalid or expired"
|
||
log_error "Please check your GITHUB_TOKEN environment variable"
|
||
exit 1
|
||
fi
|
||
|
||
local repos
|
||
repos=$(echo "$response" | jq -r '.[].full_name // empty' 2>/dev/null)
|
||
|
||
if [[ -z "$repos" ]]; then
|
||
break
|
||
fi
|
||
|
||
while IFS= read -r repo; do
|
||
# Validate repository name format (should be owner/repo)
|
||
if [[ "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
|
||
all_repos+=("$repo")
|
||
else
|
||
log_warning "Skipping invalid repository name: $repo"
|
||
fi
|
||
done <<< "$repos"
|
||
|
||
((page++))
|
||
done
|
||
|
||
# Only print if there are repos to avoid unbound variable error
|
||
if [[ ${#all_repos[@]} -gt 0 ]]; then
|
||
printf '%s\n' "${all_repos[@]}"
|
||
fi
|
||
}
|
||
|
||
fetch_user_orgs() {
|
||
local user="$1"
|
||
|
||
local response
|
||
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||
"https://api.github.com/users/$user/orgs")
|
||
|
||
# Check if response is valid JSON
|
||
if ! echo "$response" | jq empty 2>/dev/null; then
|
||
log_error "Invalid JSON response from GitHub API for user organizations"
|
||
log_error "Response: $response"
|
||
return 1
|
||
fi
|
||
|
||
# Check for authentication errors
|
||
local error_message
|
||
error_message=$(echo "$response" | jq -r '.message // empty' 2>/dev/null)
|
||
if [[ "$error_message" == "Bad credentials" ]]; then
|
||
log_error "GitHub token is invalid or expired"
|
||
log_error "Please check your GITHUB_TOKEN environment variable"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if response is an array
|
||
local org_count
|
||
org_count=$(echo "$response" | jq 'length' 2>/dev/null)
|
||
|
||
if [[ "$org_count" == "0" ]] || [[ "$org_count" == "null" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
echo "$response" | jq -r '.[].login // empty' 2>/dev/null
|
||
}
|
||
|
||
# Check if user is a collaborator on a repository
|
||
check_user_collaboration() {
|
||
local repo="$1"
|
||
local user="$2"
|
||
|
||
# Validate repository name format
|
||
if [[ ! "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
|
||
log_warning "Invalid repository name format: $repo"
|
||
return 1
|
||
fi
|
||
|
||
local response
|
||
response=$(curl -s -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \
|
||
"https://api.github.com/repos/$repo/collaborators/$user")
|
||
|
||
local http_code="${response: -3}"
|
||
|
||
case "$http_code" in
|
||
204)
|
||
return 0 # User is a collaborator
|
||
;;
|
||
404)
|
||
return 1 # User is not a collaborator
|
||
;;
|
||
403)
|
||
log_warning "Access forbidden for $repo - may not have admin permissions"
|
||
return 1
|
||
;;
|
||
*)
|
||
log_warning "Failed to check collaboration status for $user on $repo (HTTP: $http_code)"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Get repositories where user is actually a collaborator
|
||
get_user_collaborations() {
|
||
local repos=("$@")
|
||
local user="$1"
|
||
local collaborator_repos=()
|
||
|
||
log_header "Checking User Collaborations"
|
||
log_progress "Checking which repositories $user is actually a collaborator on..."
|
||
|
||
local total=${#repos[@]}
|
||
local checked=0
|
||
|
||
for repo in "${repos[@]}"; do
|
||
((checked++))
|
||
printf "\r${CYAN}Checking collaborations: [%3d%%] %d/%d repositories checked${NC}" \
|
||
$((checked * 100 / total)) "$checked" "$total"
|
||
|
||
if check_user_collaboration "$repo" "$user"; then
|
||
collaborator_repos+=("$repo")
|
||
log_info "✓ $user is a collaborator on $repo"
|
||
fi
|
||
done
|
||
|
||
echo # New line after progress
|
||
return 0
|
||
}
|
||
|
||
# Repository matching function
|
||
match_repo_pattern() {
|
||
local repo_name="$1"
|
||
local pattern="$2"
|
||
|
||
# Convert shell wildcard pattern to regex
|
||
local regex_pattern
|
||
regex_pattern="${pattern//\*/.*}"
|
||
|
||
if [[ "$repo_name" =~ $regex_pattern ]]; then
|
||
return 0
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# User confirmation functions
|
||
get_confirmation() {
|
||
local repo="$1"
|
||
local current="$2"
|
||
local total="$3"
|
||
|
||
case "$CONFIRMATION_MODE" in
|
||
"yes_all")
|
||
return 0
|
||
;;
|
||
"no_all")
|
||
return 1
|
||
;;
|
||
"ask")
|
||
echo -e "\n${YELLOW}[$current/$total] Remove user '$USER_TO_REMOVE' from '$repo'?${NC}"
|
||
echo -e "${CYAN}Options: [y]es, [n]o, [a]ll yes, [q]uit, [s]kip all${NC}"
|
||
read -p "Choice: " -n 1 -r choice
|
||
echo
|
||
|
||
case "$choice" in
|
||
y|Y)
|
||
return 0
|
||
;;
|
||
n|N)
|
||
return 1
|
||
;;
|
||
a|A)
|
||
CONFIRMATION_MODE="yes_all"
|
||
log_info "Switched to 'yes to all' mode"
|
||
return 0
|
||
;;
|
||
s|S)
|
||
CONFIRMATION_MODE="no_all"
|
||
log_info "Switched to 'skip all' mode"
|
||
return 1
|
||
;;
|
||
q|Q)
|
||
log_warning "Operation cancelled by user"
|
||
exit 0
|
||
;;
|
||
*)
|
||
log_warning "Invalid choice, skipping..."
|
||
return 1
|
||
;;
|
||
esac
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# User removal function
|
||
remove_user_from_repo() {
|
||
local repo="$1"
|
||
|
||
log_progress "Removing $USER_TO_REMOVE from $repo..."
|
||
|
||
local response
|
||
response=$(curl -s -w "%{http_code}" -X DELETE \
|
||
-H "Authorization: token $GITHUB_TOKEN" \
|
||
"https://api.github.com/repos/$repo/collaborators/$USER_TO_REMOVE")
|
||
|
||
local http_code="${response: -3}"
|
||
|
||
case "$http_code" in
|
||
204)
|
||
log_success "Successfully removed $USER_TO_REMOVE from $repo"
|
||
((SUCCESSFUL_REMOVALS++))
|
||
return 0
|
||
;;
|
||
404)
|
||
log_warning "$USER_TO_REMOVE is not a collaborator on $repo or repo not found"
|
||
((SKIPPED_REMOVALS++))
|
||
return 1
|
||
;;
|
||
*)
|
||
log_error "Failed to remove $USER_TO_REMOVE from $repo (HTTP: $http_code)"
|
||
((FAILED_REMOVALS++))
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Progress display
|
||
show_progress() {
|
||
local current="$1"
|
||
local total="$2"
|
||
local percentage=$((current * 100 / total))
|
||
|
||
printf "\r${CYAN}Progress: [%3d%%] %d/%d repositories processed${NC}" "$percentage" "$current" "$total"
|
||
}
|
||
|
||
# Summary report
|
||
show_summary() {
|
||
log_header "Operation Summary"
|
||
echo -e "${BOLD}Total repositories processed: $PROCESSED_REPOS${NC}"
|
||
echo -e "${GREEN}Successful removals: $SUCCESSFUL_REMOVALS${NC}"
|
||
echo -e "${RED}Failed removals: $FAILED_REMOVALS${NC}"
|
||
echo -e "${YELLOW}Skipped removals: $SKIPPED_REMOVALS${NC}"
|
||
echo -e "${CYAN}Success rate: $(( SUCCESSFUL_REMOVALS * 100 / (SUCCESSFUL_REMOVALS + FAILED_REMOVALS + SKIPPED_REMOVALS) ))%${NC}"
|
||
}
|
||
|
||
# Parse command line arguments
|
||
parse_arguments() {
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
-u|--user)
|
||
USER_TO_REMOVE="$2"
|
||
shift 2
|
||
;;
|
||
-r|--root)
|
||
ROOT_USER="$2"
|
||
shift 2
|
||
;;
|
||
-p|--pattern)
|
||
REPO_PATTERN="$2"
|
||
shift 2
|
||
;;
|
||
-t|--token)
|
||
GITHUB_TOKEN="$2"
|
||
shift 2
|
||
;;
|
||
--non-interactive)
|
||
NON_INTERACTIVE=true
|
||
CONFIRMATION_MODE="yes_all"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
echo "Usage: $0 [OPTIONS]"
|
||
echo "Options:"
|
||
echo " -u, --user USER Username to remove from repositories"
|
||
echo " -r, --root USER Root GitHub username/organization"
|
||
echo " -p, --pattern PATTERN Repository pattern (supports wildcards)"
|
||
echo " -t, --token TOKEN GitHub Personal Access Token"
|
||
echo " --non-interactive Run without prompts (auto-confirm all)"
|
||
echo " -h, --help Show this help message"
|
||
echo ""
|
||
echo "Example:"
|
||
echo " $0 -u i-ayushh18 -r saravanakumardb -p '*' -t \$GITHUB_TOKEN --non-interactive"
|
||
exit 0
|
||
;;
|
||
*)
|
||
log_error "Unknown option: $1"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Main execution function
|
||
main() {
|
||
log_header "GitHub User Removal Tool"
|
||
|
||
# Parse command line arguments
|
||
parse_arguments "$@"
|
||
|
||
# Check dependencies
|
||
check_dependencies
|
||
|
||
# Collect inputs (interactive if not provided via command line)
|
||
if [[ -z "$GITHUB_TOKEN" ]]; then
|
||
collect_github_token
|
||
else
|
||
log_success "Using GitHub token from command line"
|
||
fi
|
||
|
||
if [[ -z "$ROOT_USER" ]]; then
|
||
collect_root_user
|
||
else
|
||
log_success "Root user: $ROOT_USER"
|
||
fi
|
||
|
||
if [[ -z "$USER_TO_REMOVE" ]]; then
|
||
collect_user_to_remove
|
||
else
|
||
log_success "Target user: $USER_TO_REMOVE"
|
||
fi
|
||
|
||
if [[ -z "$REPO_PATTERN" ]]; then
|
||
collect_repo_pattern
|
||
else
|
||
log_success "Repository pattern: $REPO_PATTERN"
|
||
fi
|
||
|
||
# Collect all repositories
|
||
log_header "Repository Discovery"
|
||
local all_repos=()
|
||
|
||
# Fetch root user repositories
|
||
log_info "Fetching repositories for user: $ROOT_USER"
|
||
local user_repos_output
|
||
user_repos_output=$(fetch_user_repos "$ROOT_USER")
|
||
local user_repos_exit_code=$?
|
||
|
||
log_info "User repos fetch exit code: $user_repos_exit_code"
|
||
log_info "User repos output: '$user_repos_output'"
|
||
|
||
if [[ -n "$user_repos_output" ]]; then
|
||
while IFS= read -r repo; do
|
||
if [[ -n "$repo" ]] && [[ "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
|
||
all_repos+=("$repo")
|
||
log_info "Found user repo: $repo"
|
||
elif [[ -n "$repo" ]]; then
|
||
log_warning "Skipping invalid repository name: $repo"
|
||
fi
|
||
done <<< "$user_repos_output"
|
||
else
|
||
log_info "No repositories found for user: $ROOT_USER"
|
||
fi
|
||
|
||
# Fetch organization repositories
|
||
log_info "Fetching organizations for user: $ROOT_USER"
|
||
local orgs=()
|
||
local org_output
|
||
org_output=$(fetch_user_orgs "$ROOT_USER")
|
||
local org_exit_code=$?
|
||
|
||
log_info "Organization fetch exit code: $org_exit_code"
|
||
log_info "Organization output: '$org_output'"
|
||
|
||
if [[ $org_exit_code -eq 0 ]] && [[ -n "$org_output" ]]; then
|
||
while IFS= read -r org; do
|
||
if [[ -n "$org" ]] && [[ "$org" =~ ^[a-zA-Z0-9._-]+$ ]]; then
|
||
orgs+=("$org")
|
||
log_info "Found organization: $org"
|
||
elif [[ -n "$org" ]]; then
|
||
log_warning "Skipping invalid organization name: $org"
|
||
fi
|
||
done <<< "$org_output"
|
||
else
|
||
log_info "No organizations found for user: $ROOT_USER"
|
||
fi
|
||
|
||
# Fetch repositories for each organization
|
||
if [[ ${#orgs[@]} -gt 0 ]]; then
|
||
for org in "${orgs[@]}"; do
|
||
log_info "Fetching repositories for organization: $org"
|
||
while IFS= read -r repo; do
|
||
if [[ -n "$repo" ]] && [[ "$repo" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
|
||
all_repos+=("$repo")
|
||
log_info "Found org repo: $repo"
|
||
elif [[ -n "$repo" ]]; then
|
||
log_warning "Skipping invalid repository name: $repo"
|
||
fi
|
||
done < <(fetch_org_repos "$org")
|
||
done
|
||
fi
|
||
|
||
# Filter repositories by pattern
|
||
log_header "Repository Filtering"
|
||
local matching_repos=()
|
||
|
||
log_info "Total repositories found: ${#all_repos[@]}"
|
||
|
||
for repo in "${all_repos[@]}"; do
|
||
local repo_name
|
||
repo_name=$(basename "$repo")
|
||
|
||
if match_repo_pattern "$repo_name" "$REPO_PATTERN"; then
|
||
matching_repos+=("$repo")
|
||
log_info "Matched: $repo"
|
||
fi
|
||
done
|
||
|
||
TOTAL_REPOS=${#matching_repos[@]}
|
||
|
||
if [[ $TOTAL_REPOS -eq 0 ]]; then
|
||
log_warning "No repositories match the pattern '$REPO_PATTERN'"
|
||
log_info "Available repositories:"
|
||
for repo in "${all_repos[@]}"; do
|
||
log_info " • $repo"
|
||
done
|
||
exit 0
|
||
fi
|
||
|
||
log_success "Found $TOTAL_REPOS matching repositories"
|
||
|
||
# Check which repositories the user is actually a collaborator on
|
||
log_header "Checking User Collaborations"
|
||
log_progress "Checking which repositories '$USER_TO_REMOVE' is actually a collaborator on..."
|
||
|
||
local collaborator_repos=()
|
||
local checked=0
|
||
|
||
for repo in "${matching_repos[@]}"; do
|
||
((checked++))
|
||
printf "\r${CYAN}Checking collaborations: [%3d%%] %d/%d repositories checked${NC}" \
|
||
$((checked * 100 / TOTAL_REPOS)) "$checked" "$TOTAL_REPOS"
|
||
|
||
if check_user_collaboration "$repo" "$USER_TO_REMOVE"; then
|
||
collaborator_repos+=("$repo")
|
||
fi
|
||
done
|
||
|
||
echo # New line after progress
|
||
|
||
local collaborator_count=${#collaborator_repos[@]}
|
||
|
||
if [[ $collaborator_count -eq 0 ]]; then
|
||
log_warning "User '$USER_TO_REMOVE' is not a collaborator on any of the $TOTAL_REPOS matching repositories"
|
||
log_info "No action needed - user is already not a collaborator on any matching repositories"
|
||
exit 0
|
||
fi
|
||
|
||
log_success "User '$USER_TO_REMOVE' is a collaborator on $collaborator_count out of $TOTAL_REPOS matching repositories"
|
||
|
||
# Show preview of repositories where user will be removed
|
||
log_header "Preview: Repositories where '$USER_TO_REMOVE' will be removed"
|
||
for repo in "${collaborator_repos[@]}"; do
|
||
log_info "• $repo"
|
||
done
|
||
|
||
# Confirmation before proceeding
|
||
if [[ "$NON_INTERACTIVE" == "false" ]]; then
|
||
echo -e "\n${YELLOW}About to remove user '$USER_TO_REMOVE' from $collaborator_count repositories${NC}"
|
||
read -p "Continue? [y/N]: " -n 1 -r
|
||
echo
|
||
|
||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||
log_warning "Operation cancelled"
|
||
exit 0
|
||
fi
|
||
else
|
||
log_info "Non-interactive mode: proceeding with removal from $collaborator_count repositories"
|
||
fi
|
||
|
||
# Process repositories
|
||
log_header "User Removal Process"
|
||
|
||
for repo in "${collaborator_repos[@]}"; do
|
||
((PROCESSED_REPOS++))
|
||
|
||
show_progress "$PROCESSED_REPOS" "$collaborator_count"
|
||
|
||
if get_confirmation "$repo" "$PROCESSED_REPOS" "$collaborator_count"; then
|
||
remove_user_from_repo "$repo"
|
||
else
|
||
log_info "Skipped: $repo"
|
||
((SKIPPED_REMOVALS++))
|
||
fi
|
||
done
|
||
|
||
echo # New line after progress
|
||
|
||
# Show final summary
|
||
show_summary
|
||
|
||
log_success "Operation completed successfully!"
|
||
}
|
||
|
||
# Script execution
|
||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||
main "$@"
|
||
fi |