Skip to content

Instantly share code, notes, and snippets.

@WomB0ComB0
Last active March 29, 2025 09:47
Show Gist options
  • Save WomB0ComB0/3671e95a53e70069e4da41eff0c6acff to your computer and use it in GitHub Desktop.
Save WomB0ComB0/3671e95a53e70069e4da41eff0c6acff to your computer and use it in GitHub Desktop.
organize.sh and related files - with AI-generated descriptions
#!/usr/bin/env bash
# GitHub Repository Organizer - Final Fixed Version
# Enable strict mode but don't exit on arithmetic errors
set -euo pipefail
IFS=$'\n\t'
# Configuration
BASE_DIR="${GITHUB_DIR:-$HOME/github}"
LOG_DIR="$BASE_DIR/logs"
CONFIG_DIR="$BASE_DIR/config"
REPO_LIST_FILE="$CONFIG_DIR/repositories.txt"
CATEGORY_CONFIG="$CONFIG_DIR/categories.conf"
# Create necessary directories
mkdir -p "$BASE_DIR" "$LOG_DIR" "$CONFIG_DIR"
# Color definitions
COLOR_RED='\033[0;31m'
COLOR_GREEN='\033[0;32m'
COLOR_YELLOW='\033[0;33m'
COLOR_BLUE='\033[0;34m'
COLOR_MAGENTA='\033[0;35m'
COLOR_CYAN='\033[0;36m'
COLOR_RESET='\033[0m'
# Initialize logging
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="$LOG_DIR/repo_organization_$TIMESTAMP.log"
REPORT_FILE="$LOG_DIR/organization_report_$TIMESTAMP.md"
# Default categories and patterns
declare -A CATEGORY_DIRS=(
["dev"]="$BASE_DIR/dev"
["work"]="$BASE_DIR/wrk"
["edu"]="$BASE_DIR/edu"
["personal"]="$BASE_DIR/personal"
)
declare -A CATEGORY_PATTERNS=(
["edu"]="CSC|MTH|TIP|edu|-Problems|learning|course|study|assignment"
["work"]="wrk|work|client|job|project-"
["dev"]="dev|template|test|testing|advent-of|starter|boilerplate|example"
["personal"]="personal|portfolio|website|blog|hobby"
)
# Create category directories
for dir in "${CATEGORY_DIRS[@]}"; do
mkdir -p "$dir"
done
# Load repositories
load_repositories() {
local repos=()
if [[ -f "$REPO_LIST_FILE" ]]; then
# Read from file, properly handling lines with spaces
while IFS= read -r line || [[ -n "$line" ]]; do
[[ -z "$line" || "$line" =~ ^# ]] && continue
repos+=("$line")
done < "$REPO_LIST_FILE"
echo -e "${COLOR_BLUE}Loaded ${#repos[@]} repositories from:${COLOR_RESET} $REPO_LIST_FILE" >&2
else
# Discover repositories from filesystem
while IFS= read -r -d $'\0' dir; do
repos+=("$(basename "$dir")")
done < <(find "$BASE_DIR" -maxdepth 1 -mindepth 1 -type d \
-not -path "$BASE_DIR/dev" \
-not -path "$BASE_DIR/wrk" \
-not -path "$BASE_DIR/edu" \
-not -path "$BASE_DIR/personal" \
-not -path "$BASE_DIR/logs" \
-not -path "$BASE_DIR/config" \
-print0)
echo -e "${COLOR_BLUE}Discovered ${#repos[@]} repositories in:${COLOR_RESET} $BASE_DIR" >&2
# Save discovered repositories
if (( ${#repos[@]} > 0 )); then
{
printf "# GitHub repositories list - created %s\n" "$(date)"
printf "# Add one repository name per line\n"
printf "%s\n" "${repos[@]}"
} > "$REPO_LIST_FILE"
echo -e "${COLOR_BLUE}Created repository list file:${COLOR_RESET} $REPO_LIST_FILE" >&2
fi
fi
# Return the repositories
printf '%s\n' "${repos[@]}"
}
# Determine destination directory
get_destination_dir() {
local repo="$1"
for category in "${!CATEGORY_PATTERNS[@]}"; do
if [[ "$repo" =~ ${CATEGORY_PATTERNS[$category]} ]]; then
echo "${CATEGORY_DIRS[$category]}"
return
fi
done
# Default to dev if no match
echo "${CATEGORY_DIRS[dev]}"
}
# Progress tracking
init_progress() {
PROGRESS_WIDTH=50
PROGRESS_TOTAL=$1
PROGRESS_CURRENT=0
echo -n "Processing: [$(printf '%*s' $PROGRESS_WIDTH)] 0% (0/$PROGRESS_TOTAL)"
}
update_progress() {
((PROGRESS_CURRENT++)) || true
local percentage=$((PROGRESS_CURRENT * 100 / PROGRESS_TOTAL))
local completed=$((PROGRESS_WIDTH * PROGRESS_CURRENT / PROGRESS_TOTAL))
printf "\rProcessing: [%s%*s] %d%% (%d/%d)" \
"$(printf '#%.0s' $(seq 1 $completed))" \
$((PROGRESS_WIDTH - completed)) "" \
"$percentage" "$PROGRESS_CURRENT" "$PROGRESS_TOTAL"
}
complete_progress() {
printf "\n"
}
# Main processing
main() {
# Initialize logging
{
echo "GitHub Repository Organization Log"
echo "Date: $(date)"
echo "User: $(whoami)"
echo "System: $(uname -a)"
echo "----------------------------------------"
} > "$LOG_FILE"
# Load repositories
mapfile -t REPOSITORIES < <(load_repositories)
local repo_count=${#REPOSITORIES[@]}
local success_count=0
local error_count=0
local already_correct=0
echo -e "${COLOR_BLUE}Starting repository organization...${COLOR_RESET}"
echo -e "${COLOR_CYAN}Total repositories to process:${COLOR_RESET} $repo_count"
# Initialize progress
init_progress "$repo_count"
# Process each repository
for REPO in "${REPOSITORIES[@]}"; do
update_progress
# Skip empty repository names
[[ -z "$REPO" ]] && continue
echo "$(date '+%Y-%m-%d %H:%M:%S') - Processing: $REPO" >> "$LOG_FILE"
# Determine destination
local DEST_DIR=$(get_destination_dir "$REPO")
echo "$(date '+%Y-%m-%d %H:%M:%S') - Determined category: $(basename "$DEST_DIR")" >> "$LOG_FILE"
# Find current location
local REPO_PATH=""
for dir in "$BASE_DIR" "${CATEGORY_DIRS[@]}"; do
if [[ -d "$dir/$REPO" ]]; then
REPO_PATH="$dir/$REPO"
break
fi
done
# Handle missing repositories
if [[ -z "$REPO_PATH" ]]; then
echo -e "\n${COLOR_YELLOW}⚠️ Repository not found:${COLOR_RESET} $REPO" >&2
echo "$(date '+%Y-%m-%d %H:%M:%S') - Status: Not found (skipped)" >> "$LOG_FILE"
((error_count++)) || true
continue
fi
# Skip if already correct
if [[ "$(dirname "$REPO_PATH")" == "$DEST_DIR" ]]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - Status: Already in correct location" >> "$LOG_FILE"
((already_correct++)) || true
continue
fi
# Check for conflicts
if [[ -d "$DEST_DIR/$REPO" ]]; then
echo -e "\n${COLOR_RED}❌ Destination conflict:${COLOR_RESET} $REPO" >&2
echo "$(date '+%Y-%m-%d %H:%M:%S') - Status: Destination conflict (skipped)" >> "$LOG_FILE"
((error_count++)) || true
continue
fi
# Move repository
echo "$(date '+%Y-%m-%d %H:%M:%S') - Action: Moving to $(basename "$DEST_DIR")" >> "$LOG_FILE"
if mv "$REPO_PATH" "$DEST_DIR/"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - Status: Successfully moved" >> "$LOG_FILE"
((success_count++)) || true
else
echo -e "\n${COLOR_RED}❌ Failed to move:${COLOR_RESET} $REPO" >&2
echo "$(date '+%Y-%m-%d %H:%M:%S') - Status: Move operation failed" >> "$LOG_FILE"
((error_count++)) || true
fi
done
complete_progress
# Generate report
{
echo "# GitHub Repository Organization Report"
echo ""
echo "## Summary"
echo ""
echo "| Category | Count |"
echo "|----------|-------|"
echo "| Successfully moved | $success_count |"
echo "| Already organized | $already_correct |"
echo "| Failed/not found | $error_count |"
echo "| **Total processed** | $repo_count |"
echo ""
echo "## Organization by Category"
echo ""
echo "| Category | Directory | Repository Count |"
echo "|----------|-----------|-----------------|"
for category in "${!CATEGORY_DIRS[@]}"; do
local count=$(find "${CATEGORY_DIRS[$category]}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
echo "| $category | ${CATEGORY_DIRS[$category]} | $count |"
done
echo ""
echo "## Details"
echo ""
echo "Detailed log available at: \`$LOG_FILE\`"
} > "$REPORT_FILE"
# Display summary
echo -e "\n${COLOR_GREEN}Organization Summary:${COLOR_RESET}" >&2
echo -e "${COLOR_GREEN}✅ Successfully moved:${COLOR_RESET} $success_count" >&2
echo -e "${COLOR_BLUE}🔵 Already organized:${COLOR_RESET} $already_correct" >&2
echo -e "${COLOR_RED}❌ Failed/not found:${COLOR_RESET} $error_count" >&2
echo -e "\n${COLOR_MAGENTA}Category Counts:${COLOR_RESET}" >&2
for category in "${!CATEGORY_DIRS[@]}"; do
local count=$(find "${CATEGORY_DIRS[$category]}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
echo -e " ${COLOR_CYAN}$category:${COLOR_RESET} $count" >&2
done
echo -e "\n${COLOR_GREEN}Report generated:${COLOR_RESET} $REPORT_FILE" >&2
echo -e "${COLOR_GREEN}Log file created:${COLOR_RESET} $LOG_FILE" >&2
if (( error_count == 0 )); then
echo -e "\n${COLOR_GREEN}🎉 All repositories organized successfully!${COLOR_RESET}" >&2
else
echo -e "\n${COLOR_YELLOW}⚠️ Completed with some issues. Check the log for details.${COLOR_RESET}" >&2
fi
}
# Run main function
main

organize.sh Description

File Type: sh

Generated Description:

organize.sh: A GitHub Repository Organizer

This bash script, organize.sh, automates the organization of GitHub repositories into categorized directories based on configurable patterns. It aims to improve the structure and accessibility of a user's local GitHub repository collection.

Summary

The script takes a list of GitHub repositories (either from a user-provided file or by automatically discovering directories in a base directory) and moves or renames them into predefined categories. These categories are defined by regular expression patterns matching repository names and corresponding target directories. The script provides logging, progress updates, and a final report. It handles potential errors gracefully and offers a mechanism for automatically generating and updating the repository list file.

Key Components and Functions

  • Configuration: The script uses a configuration system based on files (repositories.txt, categories.conf) for flexibility. Default configurations are also included. Environment variables can override some settings.
  • load_repositories(): This function is crucial. It loads the list of repositories either from a configuration file or by discovering them within the base directory using find. If discovered, it automatically creates or updates the configuration file. This handles both user-specified and automatic repository discovery.
  • get_destination_dir(): This function takes a repository name as input and determines the appropriate destination directory based on the configured category patterns. It uses regular expression matching to classify repositories.
  • main(): The main function orchestrates the entire process: loading repositories, determining destination directories, moving or renaming repositories, and generating logs and reports.
  • Progress Tracking: The script incorporates functions (init_progress, update_progress, complete_progress) for displaying a progress bar during the repository processing, enhancing user experience.
  • Logging and Reporting: Detailed logs are written to a log file, including timestamps, processed repositories, and any errors encountered. A markdown report file is also generated.
  • Error Handling: The set -euo pipefail shell options enable strict error checking, improving the robustness of the script.
  • Colorized Output: The script uses ANSI escape codes to colorize the output to the terminal for better visual feedback.

Notable Patterns and Techniques

  • Configuration Files: Use of configuration files allows for easy customization and reusability.
  • Regular Expressions: Regular expressions are used for pattern matching in repository names to categorize them.
  • find command: Efficiently discovers repositories within a directory.
  • Null-terminated output (-print0) with find: Handles repository names with spaces correctly.
  • Bash Arrays and Associative Arrays: Used effectively to store and access repository information and category mappings.
  • Progress Bar: Improves user experience during potentially long-running operations.
  • Modular Design: The script is broken into functions, making it more readable and maintainable.
  • Robust Error Handling: The use of set -euo pipefail ensures that the script handles errors effectively and provides informative messages.
  • Automatic Repository List Generation: The script can discover and add new repositories to the configuration file, reducing the manual effort required for maintenance.

Potential Use Cases

  • Organizing a large number of GitHub repositories: Consolidates and categorizes many repositories, improving organization and findability.
  • Maintaining consistent directory structure: Enforces a structured approach to local repository management.
  • Automated repository backups: Can be integrated into a larger backup script to ensure that repositories are categorized correctly during backups.
  • Setting up a new development environment: Helps quickly organize repositories during the setup of a new workstation.
  • Streamlining project management: Improves the efficiency of finding and managing related repositories.

The script's configurable nature and automatic discovery features make it a versatile tool for managing a diverse collection of GitHub repositories.

Description generated on 3/29/2025, 5:45:57 AM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment