diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0376d7 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.PHONY: install uninstall test clean help + +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +SCRIPT_NAME = gh-switch + +help: + @echo "gh-switch - GitHub Account Switcher" + @echo "" + @echo "Usage:" + @echo " make install Install gh-switch" + @echo " make uninstall Uninstall gh-switch" + @echo " make test Run tests" + @echo " make clean Clean temporary files" + +install: + @echo "Installing gh-switch to $(BINDIR)..." + @chmod +x bin/gh-switch-standalone + @sudo cp bin/gh-switch-standalone $(BINDIR)/$(SCRIPT_NAME) + @echo "✓ Installed successfully" + @echo "Run 'gh-switch init' to get started" + +uninstall: + @echo "Uninstalling gh-switch..." + @sudo rm -f $(BINDIR)/$(SCRIPT_NAME) + @echo "✓ Uninstalled" + @echo "Note: Config files in ~/.config/gh-switch/ were preserved" + +test: + @echo "Running tests..." + @bash tests/test_basic.sh + +clean: + @echo "Cleaning temporary files..." + @rm -f *.tmp *.bak + @rm -rf build/ dist/ + @echo "✓ Cleaned" \ No newline at end of file diff --git a/bin/gh-switch-standalone b/bin/gh-switch-standalone new file mode 100755 index 0000000..d7c8dd7 --- /dev/null +++ b/bin/gh-switch-standalone @@ -0,0 +1,290 @@ +#!/usr/bin/env bash +set -e + +# gh-switch - GitHub Account Switcher +# Version 1.0.0 + +# === Configuration === +VERSION="1.0.0" +GH_SWITCH_DIR="${HOME}/.config/gh-switch" +PROFILES_DIR="${GH_SWITCH_DIR}/profiles" +CONFIG_FILE="${GH_SWITCH_DIR}/config" +SSH_CONFIG="${HOME}/.ssh/config" + +# === Colors === +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +# === Common Functions === +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } + +ensure_config_dir() { + mkdir -p "${GH_SWITCH_DIR}" + mkdir -p "${PROFILES_DIR}" +} + +# === Profile Functions === +create_profile() { + local name="$1" ssh_key="$2" git_name="$3" git_email="$4" github_user="$5" + cat > "${PROFILES_DIR}/${name}" </dev/null +} + +backup_ssh_config() { + cp "${SSH_CONFIG}" "${SSH_CONFIG}.gh-switch.backup" + log_info "SSH config backed up" +} + +add_ssh_host() { + local name="$1" ssh_key="$2" host_alias="github.com-${name}" + + if ssh_host_exists "${host_alias}"; then + log_info "SSH host ${host_alias} already exists" + return 0 + fi + + cat >> "${SSH_CONFIG}" </dev/null && return 1 + git config --${scope} --get "${key}" 2>/dev/null +} + +set_git_config() { + local key="$1" value="$2" scope="${3:-local}" + if [[ "$scope" == "local" ]] && ! git rev-parse --git-dir &>/dev/null; then + log_error "Not in a git repository" + return 1 + fi + git config --${scope} "${key}" "${value}" +} + +apply_git_profile() { + set_git_config "user.name" "${1}" "${3:-local}" + set_git_config "user.email" "${2}" "${3:-local}" +} + +update_remote_url() { + local profile="$1" + local current_url=$(git remote get-url origin 2>/dev/null) + if [[ -n "$current_url" ]]; then + local new_url=$(echo "$current_url" | sed "s/github\.com[-a-z]*/github.com-${profile}/") + git remote set-url origin "$new_url" + log_info "Updated origin URL to use ${profile} profile" + fi +} + +# === Commands === +cmd_init() { + echo "Initializing gh-switch..." + ensure_config_dir + + [[ ! -f "${CONFIG_FILE}" ]] && cat > "${CONFIG_FILE}" <" && exit 1 + + echo "Creating profile: ${name}" + read -p "SSH key path (~/.ssh/): " ssh_key + ssh_key="${ssh_key:-$HOME/.ssh/id_rsa}" + read -p "Git name: " git_name + read -p "Git email: " git_email + read -p "GitHub username: " github_user + + create_profile "$name" "$ssh_key" "$git_name" "$git_email" "$github_user" + add_ssh_host "$name" "$ssh_key" + + log_success "Profile ${name} added successfully!" + echo "Use 'gh-switch use ${name}' to activate" +} + +cmd_use() { + local profile_name="$1" scope="local" + [[ -z "$profile_name" ]] && log_error "Usage: gh-switch use [--global]" && exit 1 + [[ "$2" == "--global" ]] && scope="global" + + load_profile "$profile_name" || (log_error "Profile '${profile_name}' not found" && exit 1) + + apply_git_profile "${GIT_NAME}" "${GIT_EMAIL}" "${scope}" + echo "${profile_name}" > "${GH_SWITCH_DIR}/current" + + git rev-parse --git-dir &>/dev/null && update_remote_url "${profile_name}" + log_success "Switched to profile: ${profile_name}" +} + +cmd_current() { + local current_file="${GH_SWITCH_DIR}/current" + [[ ! -f "$current_file" ]] && echo "No profile currently active" && return + + local profile_name=$(cat "$current_file") + if load_profile "$profile_name"; then + echo -e "Current profile: ${GREEN}${profile_name}${NC}" + echo " Git user: ${GIT_NAME}" + echo " Git email: ${GIT_EMAIL}" + echo " GitHub user: ${GITHUB_USER}" + + if git rev-parse --git-dir &>/dev/null; then + echo -e "\nRepository config:" + echo " user.name: $(get_git_config user.name)" + echo " user.email: $(get_git_config user.email)" + fi + else + log_error "Current profile '${profile_name}' not found" + fi +} + +cmd_list() { + ensure_config_dir + local current_profile="" + [[ -f "${GH_SWITCH_DIR}/current" ]] && current_profile=$(cat "${GH_SWITCH_DIR}/current") + + echo -e "Available profiles:\n==================" + for profile_file in "${PROFILES_DIR}"/*; do + if [[ -f "$profile_file" ]]; then + local profile_name=$(basename "$profile_file") + source "$profile_file" + + [[ "$profile_name" == "$current_profile" ]] && echo -e "${GREEN}* ${profile_name}${NC} (active)" || echo " ${profile_name}" + echo -e " User: ${GIT_NAME}\n Email: ${GIT_EMAIL}\n" + fi + done +} + +cmd_delete() { + local profile_name="$1" + [[ -z "$profile_name" ]] && log_error "Usage: gh-switch delete " && exit 1 + + local profile_file="${PROFILES_DIR}/${profile_name}" + [[ ! -f "$profile_file" ]] && log_error "Profile '${profile_name}' not found" && exit 1 + + read -p "Delete profile '${profile_name}'? (y/n): " confirm + [[ "$confirm" != "y" ]] && echo "Cancelled" && exit 0 + + remove_ssh_host "github.com-${profile_name}" + rm -f "$profile_file" + + if [[ -f "${GH_SWITCH_DIR}/current" ]]; then + current=$(cat "${GH_SWITCH_DIR}/current") + [[ "$current" == "$profile_name" ]] && rm -f "${GH_SWITCH_DIR}/current" + fi + + log_success "Profile '${profile_name}' deleted" +} + +cmd_auto() { + if ! git rev-parse --git-dir &>/dev/null; then + log_error "Not in a git repository" + return 1 + fi + + local remote_url=$(git remote get-url origin 2>/dev/null) + [[ -z "$remote_url" ]] && log_error "No origin remote found" && return 1 + + if [[ "$remote_url" =~ github\.com-([a-z]+) ]]; then + local detected="${BASH_REMATCH[1]}" + log_info "Detected profile: ${detected}" + cmd_use "$detected" + else + log_info "No profile detected for this repository" + fi +} + +cmd_help() { + cat < [options] + +COMMANDS: + init Initialize gh-switch + add Add a new profile + use Switch to a profile + current Show current profile + list List all profiles + delete Delete a profile + auto Auto-detect profile for current repo + help Show this help message + +OPTIONS: + --global Apply profile globally (with 'use' command) + +EXAMPLES: + gh-switch init + gh-switch add personal + gh-switch use work + gh-switch use personal --global + +CONFIG: + ~/.config/gh-switch/ +EOF +} + +# === Main === +[[ "${1:-}" == "--version" ]] || [[ "${1:-}" == "-v" ]] && echo "gh-switch version ${VERSION}" && exit 0 + +ensure_config_dir + +case "${1:-help}" in + init|add|use|current|list|delete|auto|help) + cmd="cmd_${1}" + shift + $cmd "$@" + ;; + *) + cmd_help + exit 1 + ;; +esac \ No newline at end of file diff --git a/install.sh b/install.sh index 577ebe4..74edfd2 100644 --- a/install.sh +++ b/install.sh @@ -1,9 +1,8 @@ #!/usr/bin/env bash set -e -REPO="YOUR-ORG/gh-switch" INSTALL_DIR="/usr/local/bin" -CONFIG_DIR="$HOME/.config/gh-switch" +SCRIPT_NAME="gh-switch" echo "Installing gh-switch..." @@ -15,41 +14,73 @@ case "${OS}" in *) echo "Unsupported OS: ${OS}"; exit 1;; esac -# Create temp directory -TMP_DIR=$(mktemp -d) -cd "$TMP_DIR" +# Check if we're in the gh-switch directory +if [[ -f "bin/gh-switch-standalone" ]]; then + echo "Installing from local repository..." -# Download latest release -echo "Downloading gh-switch..." -curl -sL "https://github.com/${REPO}/archive/main.tar.gz" | tar xz + # Make executable + chmod +x bin/gh-switch-standalone -# Install -echo "Installing to ${INSTALL_DIR}..." -cd gh-switch-main + # Install + echo "Installing to ${INSTALL_DIR}/${SCRIPT_NAME}..." + sudo cp bin/gh-switch-standalone "${INSTALL_DIR}/${SCRIPT_NAME}" -# Create standalone script -cat > gh-switch-standalone <<'EOF' -#!/usr/bin/env bash -# Auto-generated standalone script -EOF +elif [[ -f "gh-switch-standalone" ]]; then + echo "Installing from current directory..." + chmod +x gh-switch-standalone + sudo cp gh-switch-standalone "${INSTALL_DIR}/${SCRIPT_NAME}" + +else + # Download from GitHub + echo "Downloading gh-switch from GitHub..." + TMP_FILE=$(mktemp) + + # Replace with your actual GitHub raw URL + DOWNLOAD_URL="https://raw.githubusercontent.com/YOUR-ORG/gh-switch/main/bin/gh-switch-standalone" + + if command -v curl &> /dev/null; then + curl -sL "${DOWNLOAD_URL}" -o "${TMP_FILE}" + elif command -v wget &> /dev/null; then + wget -q "${DOWNLOAD_URL}" -O "${TMP_FILE}" + else + echo "Error: curl or wget is required" + exit 1 + fi -# Combine all libs into standalone -for lib in lib/*.sh; do - echo "# --- $(basename $lib) ---" >> gh-switch-standalone - grep -v '^#!/' "$lib" >> gh-switch-standalone - echo "" >> gh-switch-standalone -done + # Check if download was successful + if [[ ! -s "${TMP_FILE}" ]]; then + echo "Error: Failed to download gh-switch" + rm -f "${TMP_FILE}" + exit 1 + fi -# Add main logic -grep -v '^#!/' bin/gh-switch | grep -v 'source.*lib' >> gh-switch-standalone + # Install + chmod +x "${TMP_FILE}" + sudo mv "${TMP_FILE}" "${INSTALL_DIR}/${SCRIPT_NAME}" +fi -# Install -sudo mv gh-switch-standalone "${INSTALL_DIR}/gh-switch" -sudo chmod +x "${INSTALL_DIR}/gh-switch" +# Verify installation +if [[ -f "${INSTALL_DIR}/${SCRIPT_NAME}" ]]; then + echo "✓ gh-switch installed successfully!" + echo "Version: $(${INSTALL_DIR}/${SCRIPT_NAME} --version)" + echo "" + echo "Run 'gh-switch init' to get started" + echo "Run 'gh-switch help' for usage information" +else + echo "Error: Installation failed" + exit 1 +fi -# Cleanup -cd / -rm -rf "$TMP_DIR" +# Optional: Install shell completions +read -p "Install shell completions? (y/n): " install_completions +if [[ "$install_completions" == "y" ]]; then + if [[ -f "completions/gh-switch.bash" ]] && [[ -d "/etc/bash_completion.d" ]]; then + sudo cp completions/gh-switch.bash /etc/bash_completion.d/ + echo "✓ Bash completions installed" + fi -echo "✓ gh-switch installed successfully!" -echo "Run 'gh-switch init' to get started" \ No newline at end of file + if [[ -f "completions/gh-switch.zsh" ]] && [[ -d "/usr/local/share/zsh/site-functions" ]]; then + sudo cp completions/gh-switch.zsh /usr/local/share/zsh/site-functions/_gh-switch + echo "✓ Zsh completions installed" + fi +fi \ No newline at end of file