Skip to content
Merged
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
## Git & Development Workflow
- **Branch naming**: Use simple descriptive names without prefixes
- **Commits**: Commit signing enabled, write clear commit messages
- **Agent commits**: Codex-authored commits should run `git-assume bk-codex` before committing; Amp-authored commits should run `git-assume bk-amp`. Use plain `git commit` without `-S`. These identities are intentionally unsigned so agents do not block on 1Password signing approval.
- **Dependencies**: Keep `go.mod` clean, use `go mod tidy` regularly
- **Security**: Never commit secrets, use environment variables or secure vaults
- **CI/CD**: Ensure tests pass before merging, use automated linting
Expand Down
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
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
110 changes: 110 additions & 0 deletions bin/git-github-auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env bash

set -e

usage() {
echo "Usage: git-github-auth <gh|https|ssh> [remote]" >&2
}

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

if ! current_url=$(git config --get "remote.$remote.url" 2>/dev/null); then
current_url=$(git remote get-url "$remote" 2>/dev/null || true)
fi

if [[ -z $current_url ]]; then
echo "Missing remote: $remote" >&2
exit 1
fi

repo_path="${current_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/}"
;;
*)
echo "Cannot infer GitHub owner/repo from remote URL: $current_url" >&2
exit 1
;;
esac

IFS=/ read -r owner repo _ <<<"$repo_path"
if [[ -z $owner || -z $repo ]]; then
echo "Cannot infer GitHub owner/repo from remote URL: $current_url" >&2
exit 1
fi

repo="${repo%.git}"

case "$transport" in
https)
target_base="https://github.com/$owner/$repo"
source_bases=(
"git@github.com:$owner/$repo"
"ssh://git@github.com/$owner/$repo"
"github.com/$owner/$repo"
"http://github.com/$owner/$repo"
)
;;
ssh)
target_base="git@github.com:$owner/$repo"
source_bases=(
"https://github.com/$owner/$repo"
"http://github.com/$owner/$repo"
"ssh://git@github.com/$owner/$repo"
"github.com/$owner/$repo"
)
;;
esac

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

git config "${config_scope[@]}" --remove-section "url.https://github.com/$owner/$repo" 2>/dev/null || true
git config "${config_scope[@]}" --remove-section "url.git@github.com:$owner/$repo" 2>/dev/null || true

for source_base in "${source_bases[@]}"; do
git config "${config_scope[@]}" --add "url.$target_base.insteadOf" "$source_base"
done

echo "$remote fetch: $(git remote get-url "$remote")"
echo "$remote push: $(git remote get-url --push "$remote")"
20 changes: 11 additions & 9 deletions git/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ The Git configuration in these dotfiles provides:
- `gitconfig.symlink`: Main Git configuration file (symlinked to `~/.gitconfig`)
- `gitignore.symlink`: Global Git ignore file (symlinked to `~/.gitignore`)
- `identity.zsh`: Contains functions for managing Git identities
- `../bin/git-assume`: Standalone command for switching Git identities from shells or agents
- `../bin/git-github-auth`: Switches a repository between HTTPS and SSH GitHub auth rewrites
- `install.sh`: Sets up the Git identity system during installation

## Managing Git Identities
Expand All @@ -34,20 +36,19 @@ The `~/.gitidentities` file should contain identity configurations in this forma

```gitconfig
[identity "work"]
name = Your Work Name
email = your.email@work.com
name = Person Example
email = person@example.com
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJi+0V3m...

[identity "personal"]
name = Your Personal Name
email = your.email@personal.com
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBl7w9A5f...
[identity "agent"]
name = Agent Example
email = agent@example.com
```

Each identity section includes:
- `name`: Your display name for commits
- `email`: Your email for commits
- `signingkey`: The SSH public key for signing (managed in 1Password)
- `signingkey`: Optional SSH public key for signing (managed in 1Password)

### Using Identities

Expand All @@ -71,8 +72,9 @@ When you run `git-assume <identity>`, it:

1. Reads the identity configuration from `~/.gitidentities`
2. Sets `user.name` and `user.email` for the current repository
3. Configures the SSH signing key from 1Password
4. Enables commit signing automatically
3. Configures the SSH signing key from 1Password and enables commit signing when `signingkey` is present
4. Clears `user.signingkey` and disables commit signing when `signingkey` is omitted
5. Uses worktree-local config in linked worktrees that have `extensions.worktreeConfig=true`

This allows for seamless switching between different GitHub/Git accounts while maintaining proper commit attribution and signing.

Expand Down
7 changes: 2 additions & 5 deletions git/gitconfig.symlink
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,9 @@
[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
helper = !/opt/homebrew/bin/gh auth git-credential
[credential "https://gist.github.com"]
helper =
helper = !gh auth git-credential
helper = !/opt/homebrew/bin/gh auth git-credential
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