Skip to content

Instantly share code, notes, and snippets.

@unixsurfer
Created October 9, 2025 18:12
Show Gist options
  • Select an option

  • Save unixsurfer/9518a1e579a0661dcad4f0966714ee0d to your computer and use it in GitHub Desktop.

Select an option

Save unixsurfer/9518a1e579a0661dcad4f0966714ee0d to your computer and use it in GitHub Desktop.
Script to generate a Git hosting URL (GitHub/GitLab) pointing to a specific file location in the repository with optional line numbers
#!/usr/bin/env bash
# A script to generate a Git hosting URL (GitHub/GitLab) pointing to a specific file location in the repository with optional line numbers.
# Usage: git-url.sh <file_path> [line_start[-line_end]] [--copy]
#
# Examples:
# $ git-url.sh file.txt
# https://github.com/user/repo/blob/main/file.txt
#
# $ git-url.sh file.txt 42
# https://github.com/user/repo/blob/main/file.txt#L42
#
# $ git-url.sh file.txt 42-50
# https://gitlab.com/mygroup/myproject/-/blob/main/file.txt#L42-50
#
# $ git-url.sh file.txt 42 --copy
# https://github.com/user/repo/blob/main/file.txt#L42
# ✓ Copied to clipboard
# Exit immediately if a command fails
set -e
# --- Helper Functions ---
# URL encode a string (handles spaces and special characters)
# Converts special characters to percent-encoded format for safe use in URLs
#
# Parameters:
# $1 - The string to encode
#
# Returns:
# URL-encoded string where special chars are converted to %HEX format
#
# Examples:
# Input: my file.txt
# Output: my%20file.txt
#
# Input: file#test.txt
# Output: file%23test.txt
#
# Input: path/to/my document.md
# Output: path/to/my%20document.md
#
# Note: Preserves alphanumeric chars, . ~ _ - and forward slashes
url_encode() {
local string="$1"
local length="${#string}"
local encoded=""
# Loop through each character and encode if necessary
for (( i = 0; i < length; i++ )); do
local c="${string:i:1}" # Extract single character at position i
case "$c" in
[a-zA-Z0-9.~_-])
# Keep alphanumeric and URL-safe chars (a-z, A-Z, 0-9, . ~ _ -) as-is
encoded+="$c"
;;
/)
# Keep forward slashes as-is (for directory separators in paths)
encoded+="/"
;;
*) # Default case: all other characters (spaces, #, %, etc.)
# Convert char to %HEX format (e.g., space → %20, # → %23)
# The '$c trick gets ASCII value, %02X formats as 2-digit hex
printf -v encoded "%s%%%02X" "$encoded" "'$c"
;;
esac
done
echo "$encoded"
}
# Convert remote URL to HTTPS base URL
# Handles both SSH and HTTPS format Git remote URLs
#
# Parameters:
# $1 - The Git remote URL (from 'git remote get-url origin')
#
# Returns:
# HTTPS base URL without .git suffix
#
# Examples:
# Input: [email protected]:mygroup/myproject.git
# Output: https://gitlab.com/mygroup/myproject
#
# Input: https://gitlab.com/mygroup/myproject.git
# Output: https://gitlab.com/mygroup/myproject
#
# Input: [email protected]:adyen/adyen-main.git
# Output: https://gitlab.is.adyen.com/adyen/adyen-main
parse_remote_url() {
local remote_url="$1"
local base_url=""
# Handle SSH format: [email protected]:group/project.git
if [[ "$remote_url" =~ ^git@([^:]+):(.+)$ ]]; then
local host="${BASH_REMATCH[1]}"
local path="${BASH_REMATCH[2]}"
path="${path%.git}" # Remove .git suffix
base_url="https://${host}/${path}"
# Handle HTTPS format: https://gitlab.com/group/project.git
elif [[ "$remote_url" =~ ^https?://(.+)$ ]]; then
base_url="${remote_url%.git}" # Remove .git suffix
else
echo "Error: Unsupported remote URL format: $remote_url" >&2
exit 1
fi
echo "$base_url"
}
# --- Pre-flight Checks ---
# 1. Ensure we are inside a git repository
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo "Error: Not a git repository." >&2
exit 1
fi
# 2. Ensure a filename argument was provided
if [ -z "$1" ]; then
echo "Usage: git-url.sh <file_path> [line_start[-line_end]] [--copy]" >&2
echo "" >&2
echo "Options:" >&2
echo " --copy Copy URL to clipboard (macOS only)" >&2
echo "" >&2
echo "Examples:" >&2
echo " git-url.sh file.txt" >&2
echo " git-url.sh file.txt 42" >&2
echo " git-url.sh file.txt 42-50" >&2
echo " git-url.sh file.txt 42 --copy" >&2
exit 1
fi
file_arg="$1"
line_arg="$2"
copy_flag="$3"
# Handle --copy flag in either position
if [[ "$line_arg" == "--copy" ]]; then
copy_flag="--copy"
line_arg=""
fi
# 3. Ensure the file actually exists
if [ ! -f "$file_arg" ]; then
echo "Error: File '$file_arg' not found." >&2
exit 1
fi
# --- Main Logic ---
# 1. Get the repository's remote URL
remote_url=$(git remote get-url origin 2>/dev/null)
if [ -z "$remote_url" ]; then
echo "Error: No remote 'origin' found." >&2
exit 1
fi
# 2. Get the current branch name or commit SHA (for detached HEAD)
branch=$(git branch --show-current)
if [ -z "$branch" ]; then
# Detached HEAD state - use commit SHA instead
branch=$(git rev-parse HEAD)
echo "Warning: Detached HEAD state, using commit SHA: $branch" >&2
fi
# 3. Convert the remote URL into a clean base URL for the project
base_url=$(parse_remote_url "$remote_url")
# 4. Detect if it's GitHub or GitLab (GitHub doesn't use /-/ prefix)
if [[ "$base_url" =~ github\.com ]]; then
blob_path="/blob"
else
# GitLab and others use /-/blob/
blob_path="/-/blob"
fi
# 5. Get the file's path relative to the repository root
current_dir_path="$(git rev-parse --show-prefix)"
file_path="${current_dir_path}${file_arg}"
# 6. URL encode the file path
encoded_file_path=$(url_encode "$file_path")
# 7. Build the URL with optional line numbers
url="${base_url}${blob_path}/${branch}/${encoded_file_path}"
# Add line number fragment if provided
if [ -n "$line_arg" ]; then
# Check if it's a range (e.g., "42-50") or single line (e.g., "42")
if [[ "$line_arg" =~ ^([0-9]+)-([0-9]+)$ ]]; then
# Line range
url="${url}#L${BASH_REMATCH[1]}-${BASH_REMATCH[2]}"
elif [[ "$line_arg" =~ ^[0-9]+$ ]]; then
# Single line
url="${url}#L${line_arg}"
else
echo "Error: Invalid line number format. Use '42' or '42-50'." >&2
exit 1
fi
fi
# 7. Output the URL
echo "$url"
# 8. Optionally copy to clipboard (macOS only)
if [[ "$copy_flag" == "--copy" ]]; then
# Check if running on macOS
if [[ "$OSTYPE" == "darwin"* ]]; then
if command -v pbcopy > /dev/null 2>&1; then
echo "$url" | pbcopy
echo "✓ Copied to clipboard" >&2
else
echo "Warning: pbcopy not found, cannot copy to clipboard" >&2
fi
elif [[ "$OSTYPE" == "linux"* ]]; then
# Linux: skip clipboard copy
echo "Note: --copy flag is only supported on macOS" >&2
else
echo "Warning: --copy flag is only supported on macOS" >&2
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment