diff --git a/AGENTS.md b/AGENTS.md index 1ae0adf..700c92a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/bin/git-assume b/bin/git-assume new file mode 100755 index 0000000..feb2aed --- /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..5ecd6eb --- /dev/null +++ b/bin/git-github-auth @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +set -e + +usage() { + echo "Usage: git-github-auth [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")" diff --git a/git/README.md b/git/README.md index 7b65930..558312f 100644 --- a/git/README.md +++ b/git/README.md @@ -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 @@ -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 @@ -71,8 +72,9 @@ When you run `git-assume `, 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. diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 9b44f64..6913e05 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -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 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 "$@" }