Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions bin/git-assume
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

set -e

identity="${1:-}"

if [[ -z "$identity" ]]; then
echo "Usage: git-assume <identity>" >&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
Comment on lines +23 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Write identity changes to main worktree config

When extensions.worktreeConfig is enabled on the main worktree, Git reads config.worktree after .git/config (git-worktree docs), but this condition only selects --worktree for linked worktrees. If the main worktree already has a worktree-scoped user.name, user.email, signing key, or commit.gpgsign, git-assume <identity> writes the new identity to shared .git/config while the existing config.worktree value still wins, so commits in that checkout continue using the old identity.

Useful? React with 👍 / 👎.

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
200 changes: 200 additions & 0 deletions bin/git-github-auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/usr/bin/env bash

set -e

usage() {
echo "Usage: git-github-auth <gh|https|ssh> [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/}"
Comment thread
lox marked this conversation as resolved.
;;
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove inherited push URLs before adding replacements

When remote.<name>.pushurl is inherited from global or included config, this unset only edits the repository-local file by default (git-config docs), so the inherited SSH pushurl remains alongside the newly added HTTPS one. Git pushes to all defined pushurls (git-push docs), so git-github-auth gh can still attempt the original SSH/1Password push destination even after reporting that the repo was switched.

Useful? React with 👍 / 👎.

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
Comment on lines +142 to +145

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve main worktree-local config scope

When extensions.worktreeConfig is enabled on the main worktree, Git reads config.worktree after .git/config and updates it via git config --worktree (git-worktree docs); this condition only selects --worktree for linked worktrees. Because the earlier git config --get-all remote.$remote.url also sees worktree-scoped values, running git-github-auth from the main worktree can copy a main-only remote or pushurl into shared .git/config, changing the remotes seen by other worktrees instead of just the current one.

Useful? React with 👍 / 👎.

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")"
3 changes: 0 additions & 3 deletions git/gitconfig.symlink
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 1 addition & 22 deletions git/identity.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
}
Loading