#!/usr/bin/env bash # git-browse.sh - Open or copy the web URL for a file, directory, branch, # or commit in a git repository. # # Usage: git-browse.sh [--dry-run] [--copy] [--path RELPATH] [target] set -e SCRIPT_NAME="$(basename "$0")" DEFAULT_BRANCH_NAME="main" readonly SCRIPT_NAME readonly SUPPORTED_GIT_HOSTS=( "github.com" "gitlab.com" "bitbucket.org" "git.sr.ht" "codeberg.org" ) ####################################### # Print error message to STDERR. # Globals: # SCRIPT_NAME # Arguments: # $1 - Error message # Outputs: # Writes error message to stderr ####################################### print_error() { printf '%s: %s\n' "$SCRIPT_NAME" "$1" >&2 } ####################################### # Find the root of the git repository. # Outputs: # Writes absolute path to repo root to stdout # Returns: # 0 if found, 1 if not found ####################################### find_repo_root() { git rev-parse --show-toplevel 2>/dev/null || { print_error ".git directory not found" return 1 } } ####################################### # Get the remote URL from git config. # Arguments: # $1 - Repository root # Outputs: # Writes remote.origin.url to stdout # Returns: # 0 on success, 1 on error ####################################### get_remote_url() { git -C "$1" config --get remote.origin.url || { print_error "Failed to get remote.origin.url" return 1 } } ####################################### # Get the default branch from git config. # Globals: # DEFAULT_BRANCH_NAME # Arguments: # $1 - Repository root # Outputs: # Writes default branch name to stdout # Returns: # 0 on success ####################################### get_default_branch() { git -C "$1" symbolic-ref --short HEAD 2>/dev/null || \ echo "$DEFAULT_BRANCH_NAME" } ####################################### # Convert SSH/HTTPS git URL to web URL. # Arguments: # $1 - Git remote URL # Outputs: # Writes web URL to stdout # Returns: # 0 on success, 1 if unsupported host ####################################### git_url_to_web_url() { url="$1" for host in "${SUPPORTED_GIT_HOSTS[@]}"; do if [[ "$url" =~ ${host}[:/](.+)/(.+)(\.git)?$ ]]; then user="${BASH_REMATCH[1]}" repo="${BASH_REMATCH[2]}" echo "https://${host}/$user/${repo%.git}" return 0 fi done print_error "Unsupported git host: $url" return 1 } ####################################### # Detect platform and set PLATFORM global variable. # Globals: # PLATFORM ####################################### platform_detect() { uname_out="$(uname -s)" if [[ "$uname_out" == "Darwin" ]]; then PLATFORM="darwin" elif [[ "$uname_out" == CYGWIN* ]]; then PLATFORM="cygwin" elif [[ -n "${TERMUX_VERSION-}" ]]; then PLATFORM="termux" elif [[ "${XDG_SESSION_TYPE:-}" == 'wayland' ]]; then PLATFORM="wayland" elif command -v xclip >/dev/null 2>&1; then PLATFORM="xclip" elif command -v xsel >/dev/null 2>&1; then PLATFORM="xsel" else PLATFORM="none" fi } ####################################### # Copy a string to the clipboard (cross-platform). # Globals: # PLATFORM # Arguments: # $1 - String to copy # Returns: # 0 on success, 1 on error ####################################### copy_to_clipboard() { platform_detect case "$PLATFORM" in darwin) if ! command -v pbcopy >/dev/null 2>&1; then print_error "pbcopy not found" return 1 fi printf '%s' "$1" | pbcopy ;; cygwin) printf '%s' "$1" | tee > /dev/clipboard ;; termux) if ! command -v termux-clipboard-set >/dev/null 2>&1; then print_error "termux-clipboard-set not found" return 1 fi printf '%s' "$1" | termux-clipboard-set ;; wayland) if ! command -v wl-copy >/dev/null 2>&1; then print_error "wl-copy not found" return 1 fi printf '%s' "$1" | wl-copy ;; xclip) printf '%s' "$1" | xclip -selection clipboard -in ;; xsel) printf '%s' "$1" | xsel --clipboard --input ;; *) print_error "No clipboard utility found for platform: $PLATFORM" return 1 ;; esac } ####################################### # Open a URL in the default browser (cross-platform). # Globals: # PLATFORM # Arguments: # $1 - URL to open # Returns: # 0 on success, 1 on error ####################################### open_url() { platform_detect case "$PLATFORM" in darwin) if command -v open >/dev/null 2>&1; then open "$1" else print_error "'open' not found" fi ;; cygwin) if command -v cygstart >/dev/null 2>&1; then cygstart "$1" else print_error "'cygstart' not found" fi ;; termux) if command -v termux-open >/dev/null 2>&1; then termux-open "$1" else print_error "'termux-open' not found" fi ;; wayland|xclip|xsel|other) if command -v xdg-open >/dev/null 2>&1; then xdg-open "$1" else print_error "'xdg-open' not found" fi ;; *) print_error "No open command found for platform: $PLATFORM" return 1 ;; esac } ####################################### # Print usage information. # Globals: # SCRIPT_NAME # Outputs: # Writes usage to stdout ####################################### print_usage() { cat <