diff --git a/bin/git-assume b/bin/git-assume new file mode 100755 index 0000000..8f89572 --- /dev/null +++ b/bin/git-assume @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -e + +identity="${1:-}" + +if [[ -z "$identity" ]]; then + echo "Usage: git-assume " >&2 + exit 2 +fi + +if ! name=$(git config "identity.$identity.name"); then + echo "Missing name for $identity" >&2 + exit 1 +fi + +if ! email=$(git config "identity.$identity.email"); then + echo "Missing email for $identity" >&2 + exit 1 +fi + +config_scope=() +if git_dir=$(git rev-parse --git-dir 2>/dev/null) && + git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) && + [[ "$git_dir" != "$git_common_dir" ]] && + [[ "$(git config --bool --get extensions.worktreeConfig 2>/dev/null)" == "true" ]]; then + config_scope=(--worktree) +fi + +git config "${config_scope[@]}" user.identity "$identity" +git config "${config_scope[@]}" user.name "$name" +git config "${config_scope[@]}" user.email "$email" + +if signingkey=$(git config "identity.$identity.signingkey"); then + git config "${config_scope[@]}" user.signingkey "$signingkey" + git config "${config_scope[@]}" commit.gpgsign true +else + git config "${config_scope[@]}" --unset user.signingkey 2>/dev/null || true + git config "${config_scope[@]}" commit.gpgsign false +fi diff --git a/bin/git-github-auth b/bin/git-github-auth new file mode 100755 index 0000000..3ebfe74 --- /dev/null +++ b/bin/git-github-auth @@ -0,0 +1,200 @@ +#!/usr/bin/env bash + +set -e + +usage() { + echo "Usage: git-github-auth [remote]" >&2 +} + +parse_github_url() { + local url="$1" + local repo_path + local owner + local repo + local rest + + repo_path="${url%.git}" + case "$repo_path" in + https://github.com/*) + repo_path="${repo_path#https://github.com/}" + ;; + http://github.com/*) + repo_path="${repo_path#http://github.com/}" + ;; + git@github.com:*) + repo_path="${repo_path#git@github.com:}" + ;; + ssh://git@github.com/*) + repo_path="${repo_path#ssh://git@github.com/}" + ;; + github.com/*) + repo_path="${repo_path#github.com/}" + ;; + *) + return 1 + ;; + esac + + IFS=/ read -r owner repo rest <<<"$repo_path" + if [[ -z "$owner" || -z "$repo" || -n "$rest" ]]; then + return 1 + fi + + printf '%s/%s\n' "$owner" "$repo" +} + +target_url_for_repo() { + local repo_path="$1" + + case "$transport" in + https) + printf 'https://github.com/%s.git\n' "$repo_path" + ;; + ssh) + printf 'git@github.com:%s.git\n' "$repo_path" + ;; + esac +} + +remove_repo_rewrites() { + local repo_path="$1" + + git config "${config_scope[@]}" --remove-section "url.https://github.com/$repo_path" 2>/dev/null || true + git config "${config_scope[@]}" --remove-section "url.https://github.com/$repo_path.git" 2>/dev/null || true + git config "${config_scope[@]}" --remove-section "url.git@github.com:$repo_path" 2>/dev/null || true + git config "${config_scope[@]}" --remove-section "url.git@github.com:$repo_path.git" 2>/dev/null || true +} + +add_worktree_rewrite() { + local source_url="$1" + local repo_path + local target_url + + if [[ "$source_url" != *.git ]]; then + echo "Cannot safely rewrite non-.git URL from a linked worktree: $source_url" >&2 + echo "Normalize the shared remote URL with .git first, or run this from the main checkout." >&2 + exit 1 + fi + + if ! repo_path=$(parse_github_url "$source_url"); then + echo "Cannot infer GitHub owner/repo from remote URL: $source_url" >&2 + exit 1 + fi + + target_url=$(target_url_for_repo "$repo_path") + git config "${config_scope[@]}" --add "url.$target_url.insteadOf" "$source_url" +} + +set_direct_urls() { + local key="$1" + shift + local url + local repo_path + local target_url + + git config --unset-all "$key" 2>/dev/null || true + for url in "$@"; do + if ! repo_path=$(parse_github_url "$url"); then + echo "Cannot infer GitHub owner/repo from remote URL: $url" >&2 + exit 1 + fi + target_url=$(target_url_for_repo "$repo_path") + git config --add "$key" "$target_url" + done +} + +mode="${1:-}" +remote="${2:-origin}" + +if [[ -z "$mode" ]]; then + usage + exit 2 +fi + +case "$mode" in +gh | https) + transport="https" + ;; +ssh) + transport="ssh" + ;; +*) + usage + exit 2 + ;; +esac + +fetch_urls=() +while IFS= read -r url; do + fetch_urls+=("$url") +done < <(git config --get-all "remote.$remote.url" 2>/dev/null || true) + +push_urls=() +while IFS= read -r url; do + push_urls+=("$url") +done < <(git config --get-all "remote.$remote.pushurl" 2>/dev/null || true) + +if ((${#fetch_urls[@]} == 0)); then + echo "Missing remote: $remote" >&2 + exit 1 +fi + +config_scope=() +if git_dir=$(git rev-parse --git-dir 2>/dev/null) && + git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) && + [[ "$git_dir" != "$git_common_dir" ]]; then + if [[ "$(git config --bool --get extensions.worktreeConfig 2>/dev/null)" != "true" ]]; then + echo "Refusing to change shared config from a linked worktree without extensions.worktreeConfig=true" >&2 + exit 1 + fi + config_scope=(--worktree) +fi + +if ((${#config_scope[@]} > 0)); then + for url in "${fetch_urls[@]}" "${push_urls[@]}"; do + if [[ -z "$url" ]]; then + continue + fi + if [[ "$url" != *.git ]]; then + echo "Cannot safely rewrite non-.git URL from a linked worktree: $url" >&2 + echo "Normalize the shared remote URL with .git first, or run this from the main checkout." >&2 + exit 1 + fi + if ! repo_path=$(parse_github_url "$url"); then + echo "Cannot infer GitHub owner/repo from remote URL: $url" >&2 + exit 1 + fi + remove_repo_rewrites "$repo_path" + done + + for url in "${fetch_urls[@]}"; do + add_worktree_rewrite "$url" + done + + if ((${#push_urls[@]} > 0)); then + for url in "${push_urls[@]}"; do + add_worktree_rewrite "$url" + done + fi +else + for url in "${fetch_urls[@]}" "${push_urls[@]}"; do + if [[ -z "$url" ]]; then + continue + fi + if ! repo_path=$(parse_github_url "$url"); then + echo "Cannot infer GitHub owner/repo from remote URL: $url" >&2 + exit 1 + fi + remove_repo_rewrites "$repo_path" + done + + set_direct_urls "remote.$remote.url" "${fetch_urls[@]}" + if ((${#push_urls[@]} > 0)); then + set_direct_urls "remote.$remote.pushurl" "${push_urls[@]}" + else + git config --unset-all "remote.$remote.pushurl" 2>/dev/null || true + fi +fi + +echo "$remote fetch: $(git remote get-url "$remote")" +echo "$remote push: $(git remote get-url --push "$remote")" diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 9b44f64..a0e46a8 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -59,9 +59,6 @@ [gpg "ssh"] program = /Applications/1Password.app/Contents/MacOS/op-ssh-sign -[url "git@github.com:lox/"] - insteadOf = https://github.com/lox/ - insteadOf = github.com/lox/ [credential "https://github.com"] helper = helper = !gh auth git-credential diff --git a/git/identity.zsh b/git/identity.zsh index 64d96ef..3e1f2f6 100644 --- a/git/identity.zsh +++ b/git/identity.zsh @@ -6,26 +6,5 @@ git-identities() { } git-assume() { - local identity="$1" - local name - local email - - if ! name=$(git config "identity.$identity.name") ; then - echo "Missing name for $identity" - return 1 - fi - - if ! email=$(git config "identity.$identity.email") ; then - echo "Missing email for $identity" - return 1 - fi - - git config user.identity "$identity" - git config user.name "$name" - git config user.email "$email" - - if signingkey=$(git config "identity.$identity.signingkey") ; then - git config user.signingkey "$signingkey" - git config commit.gpgsign true - fi + command git-assume "$@" }