Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save bgauduch/06a8c4ec2fec8fef6354afe94358c89e to your computer and use it in GitHub Desktop.

Select an option

Save bgauduch/06a8c4ec2fec8fef6354afe94358c89e to your computer and use it in GitHub Desktop.
Multiple git identities on one machine: config per folder and per provider (personal vs work). Every command verified.

Multiple git identities on one machine

TL;DR

One machine, several git identities: typically personal vs work, GitHub vs GitLab, or two accounts on the same provider. This setup makes every repository pick the right name, email, and keys on its own, so no work commit ever goes out under your personal address (or the reverse). In a hurry: run the Appendix B script once per identity.

⚠️ Read this first. A misconfigured git identity fails silently: git keeps committing, just with the wrong author. Go through Part 1 step by step and verify at the end. That is what step 5 is for.

Requirements

Feature Minimum git
includeIf "gitdir:" / gitdir/i: (identity per folder) 2.13
includeIf "onbranch:" 2.23
SSH commit signing (gpg.format = ssh) 2.34
includeIf "hasconfig:remote.*.url:" (identity per provider) 2.36

Check yours with git --version. Every command and pattern in this document was executed against git 2.55 before publication.

Contents


Part 1. Make it work, make it quick

Five steps, all through git config commands: no config file is edited by hand, so no typo can silently break your whole git setup. Prefer editing the files yourself? See Appendix A.

Step 1. Pick your key: folder or remote?

gitdir: (identity per folder) hasconfig:remote.*.url: (identity per remote)
The identity follows where you cloned who hosts the repo
Needs discipline about keeping clones inside the right folder nothing
Breaks when you clone outside the tree (/tmp, a scratch dir) a repo has no remote yet (git init)

They combine: gitdir: for the general case, hasconfig: to force the right identity wherever a repo happens to sit. Start with one. Trade-offs: 2.4. Neither fits? 2.8.

Step 2. One SSH key per identity (optional)

SSH is optional: it authenticates ssh remotes (git@...). Repos cloned over https authenticate with a credential helper instead; in that case skip to step 3.

Generate one key per identity:

ssh-keygen -t ed25519 -C "you@personal.example"   -f ~/.ssh/github_personal
ssh-keygen -t ed25519 -C "you@work.example"       -f ~/.ssh/gitlab_work

Add each public key (*.pub) to the matching provider account, then map one key per host in ~/.ssh/config:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_personal
    IdentitiesOnly yes
    AddKeysToAgent yes

Host gitlab.com
    HostName gitlab.com
    User git
    IdentityFile ~/.ssh/gitlab_work
    IdentitiesOnly yes
    AddKeysToAgent yes

IdentitiesOnly yes is not optional: without it, ssh offers every key it knows until the server rejects it, and you may authenticate as the wrong account. On macOS add UseKeychain yes to unlock the passphrase from the keychain.

Keys authenticate, git config names the author: two independent layers, detailed in 2.5.

Two accounts on the same provider? See Same provider, two accounts.

Step 3. Write one file per identity

git config --file="$HOME/.gitconfig-personal" --replace-all user.name  "personal_username"
git config --file="$HOME/.gitconfig-personal" --replace-all user.email "personal@users.noreply.github.com"

git config --file="$HOME/.gitconfig-work" --replace-all user.name  "work_username"
git config --file="$HOME/.gitconfig-work" --replace-all user.email "you@company.example"

Use --replace-all, not --add. --add appends a new line every time you run it, and you end up with a file holding three different emails.

The …@users.noreply.github.com address comes from GitHub β†’ Settings β†’ Emails, with "Keep my email addresses private" enabled.

Step 4. Wire the conditional include

Per folder (~/code/personal/, ~/code/work/):

git config --global --replace-all "includeif.gitdir:~/code/personal/.path" "~/.gitconfig-personal"
git config --global --replace-all "includeif.gitdir:~/code/work/.path"     "~/.gitconfig-work"

Per provider, one rule per URL shape - the ssh and the https form of a remote need their own pattern (2.3 explains why):

git config --global --replace-all "includeif.hasconfig:remote.*.url:https://github.com/**.path" "~/.gitconfig-personal"
git config --global --replace-all "includeif.hasconfig:remote.*.url:git@github.com:*/**.path"   "~/.gitconfig-personal"
git config --global --replace-all "includeif.hasconfig:remote.*.url:https://gitlab.com/**.path" "~/.gitconfig-work"
git config --global --replace-all "includeif.hasconfig:remote.*.url:git@gitlab.com:*/**.path"   "~/.gitconfig-work"

Reading the key: includeif . <the condition> . path. The condition is the part between the quotes in the file syntax, and it keeps its own colons and slashes, which is why the whole key is quoted for the shell. Include ordering and precedence: 2.1.

The trailing slash in gitdir: is mandatory. ~/code/personal/ matches every repository below that folder; ~/code/personal (no slash) matches nothing. This one line is the single most common cause of "it doesn't work"; see 2.3.

Remove the global identity and forbid guessing, so a repo that matches nothing fails loudly instead of committing as the wrong person:

git config --global --unset-all user.email
git config --global --unset-all user.name
git config --global user.useConfigOnly true

(If the user.* keys were already absent, git config exits with status 5 and prints nothing; that is the "nothing to unset" code, not an error.)

user.useConfigOnly carries the guarantee: without it, git builds an identity from your OS username and hostname and commits with it. With it, a commit in an unmatched repo stops with:

fatal: no email was given and auto-detection is disabled

That error is the setup working: fix it by moving the repo under the right folder, or with a local git config user.email ….

Step 5. Verify, from inside a repository

cd ~/code/personal/some-repo          # ⚠️ a real git repository, not its parent
git config --show-origin --get user.email
git config --show-origin --get user.name
git var GIT_AUTHOR_IDENT              # the identity the next commit will use
ssh -T git@github.com                 # says which account you authenticate as

--show-origin prints which file the value came from; that is the whole verification. Expected output:

file:/Users/you/.gitconfig-personal    personal@users.noreply.github.com

Nothing at all? You are almost certainly outside a repository: a conditional include is evaluated against the repository git dir, so git config --get user.email in a plain folder legitimately returns nothing; the full evaluation rules (symlinks, worktrees) are in 2.2. Wrong file, or the global one? Go to 2.7.

Optional: sign your commits

SSH key, no GPG install:

git config --file="$HOME/.gitconfig-personal" --replace-all gpg.format ssh
git config --file="$HOME/.gitconfig-personal" --replace-all user.signingkey "~/.ssh/github_personal.pub"
git config --file="$HOME/.gitconfig-personal" --replace-all commit.gpgsign true

Then register that same public key on the provider as a signing key (a GitHub authentication key is not automatically a signing key). GPG instead, and the local-verification setup, are in 2.6.

What you end up with

~/.gitconfig              includeIf rules + user.useConfigOnly, no identity
~/.gitconfig-personal     name, email, keys of the personal identity
~/.gitconfig-work         name, email, keys of the work identity
~/.ssh/config             one Host block per key
~/.ssh/github_personal    + .pub, one key pair per identity

Part 2. Make me learn

Everything above is four mechanisms in a trench coat. This part explains them, so that the next time something is off you can diagnose instead of retry.

2.1 Where git config lives, and who wins

Git reads configuration from several files and concatenates them in order; for a plain key, the last value read wins:

Scope File Flag
system /etc/gitconfig --system
global ~/.gitconfig or ~/.config/git/config --global
local <repo>/.git/config --local
worktree <repo>/.git/config.worktree --worktree

Consequences worth remembering:

  • An included file is read at the point of the includeIf line, so it overrides what the global file set before it. That is why step 4 works.
  • A local value always beats an included one: a user.email set once in a repo will quietly outrank your whole scheme. git config --show-origin is what exposes it.
  • git config --list --show-scope labels values from an included file as global (that is the scope that pulled them in). Use --show-origin when you want the actual file.

2.2 How includeIf is evaluated

The condition is evaluated when the configuration is loaded, against the repository git directory git resolved for the current command. Three consequences:

  1. Outside a repository there is no git dir, so no gitdir: condition can match and git config --get user.email prints nothing. Nothing is broken.
  2. Matching uses the resolved, absolute path. Symlinked trees (/var β†’ /private/var on macOS, a symlinked ~/code) match the real path, not the one you typed.
  3. Git checks the path of the git dir, so a linked worktree or a submodule whose git dir lives under .git/modules/… may not match a pattern written for the working tree.

2.3 The pattern grammar

Behavior against a repo in ~/code/perso/deep/nested/r1:

Pattern Matches Why
gitdir:~/code/perso/ βœ… a pattern ending with / gets ** appended: everything below, recursively
gitdir:~/code/perso ❌ no trailing slash: this matches that exact path only
gitdir:perso/ βœ… a pattern that does not start with ~/, ./, / or **/ gets **/ prepended
gitdir:~/code/perso/**/.git βœ… explicit globbing also works, but it is not required
gitdir:~/CODE/perso/ ❌ conditions are case-sensitive
gitdir/i:~/CODE/perso/ βœ… /i makes the match case-insensitive (Windows, macOS)

The trailing slash does the recursive match on its own; a /.git suffix adds nothing. Add /i on Windows, where the path case you see is not always the path case git resolves.

Other conditions:

  • onbranch:<glob>: identity (or any config) per branch name, e.g. onbranch:release/*.
  • hasconfig:remote.*.url:<glob>: matches if any remote URL of the repo matches the glob. Careful: ** is only special when it touches a slash (**/, /**); anywhere else it silently degrades to *, and * never crosses /. So one pattern cannot cover both URL shapes. Use one rule per shape: https://github.com/** for https clones, git@github.com:*/** for ssh (scp-style) clones. Note it needs a remote to exist: a fresh git init matches nothing until git remote add.

2.4 Choosing your key

gitdir: states "repos in this folder are personal". It is stable, works offline and predates everything, but it only holds while you clone where you said you would.

hasconfig:remote.*.url: states "repos hosted there are personal", which is what you actually mean, and it keeps working for a clone in /tmp.

A robust combination: hasconfig: rules for the providers you use, plus one gitdir: rule for the work tree, listed after them so the folder wins when both apply.

2.5 Git config and SSH config are two independent layers

Two independent layers, two failure modes:

  • SSH decides which account authenticates: the key ends up mapped to a provider account. Wrong key β‡’ permission denied, or a push landing on the wrong account.
  • git config decides whose name is written in the commit. Wrong identity β‡’ the push succeeds and the commit shows the wrong author.

They are set independently and can disagree, which is exactly how you commit as personal and push as work.

Same provider, two accounts

SSH cannot pick a key from the URL path, so give each account its own Host alias:

Host github-personal
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_personal
    IdentitiesOnly yes

Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_work
    IdentitiesOnly yes

then clone with git clone git@github-personal:me/repo.git. Alternatively, keep real URLs and bind the key to the identity file instead:

git config --file="$HOME/.gitconfig-work" --replace-all \
  core.sshCommand "ssh -i ~/.ssh/github_work -o IdentitiesOnly=yes"

The key now follows the same condition as the name and email (one rule instead of two), and git clone of an existing repo keeps its normal URL. (Rewriting URLs globally with url.<base>.insteadOf is a third option; it is the least readable of the three when something goes wrong.)

2.6 Signing: GPG or SSH

SSH signing reuses a key you already have; see Part 1. To verify signatures locally, git needs to know which key belongs to whom:

echo "personal@users.noreply.github.com $(cat ~/.ssh/github_personal.pub)" >> ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile "~/.ssh/allowed_signers"
git log --show-signature -1

GPG signing, per identity:

git config --file="$HOME/.gitconfig-personal" --replace-all user.signingkey "<GPG_KEY_ID>"
git config --file="$HOME/.gitconfig-personal" --replace-all commit.gpgsign true
git config --global --replace-all gpg.program "/path/to/gpg"

The config key is gpg.program. A [program] pgp = … block is ignored by git, which silently keeps calling the first gpg in your PATH.

Whichever you use, the signature is only shown as verified by the provider when the commit email matches an email registered on that account. This is why per-identity signing and per-identity email have to move together: sign with the work key while committing with the personal noreply address and the web UI shows the commit as unverified.

2.7 Troubleshooting

Start here, always:

git rev-parse --git-dir                       # am I in a repo, and which git dir?
git config --list --show-origin --show-scope  # every value and the file it came from
Symptom Cause Fix
git config --get user.email prints nothing you are not inside a repository cd into a repo
Identity not applied in a repo under the folder missing trailing / in gitdir: gitdir:~/code/perso/
Not applied, path looks right case mismatch, or symlinked path use gitdir/i:, or the resolved path, see 2.2
Applied, but the value is wrong a user.email sits in .git/config git config --local --unset user.email, see 2.1
Three emails in an identity file --add used repeatedly git config --file=… --replace-all …
Right author, wrong account on push SSH key, not git config ssh -T git@host, IdentitiesOnly yes, see 2.5
hasconfig: never matches repo has no remote yet add the remote, or add a gitdir: rule
hasconfig: matches ssh clones but not https, or the reverse one glob cannot cover both URL shapes one rule per shape, see 2.3
Commit shows as unverified signing key β‰  commit email on the account align email and key, see 2.6

2.8 Alternatives, and when not to use includeIf

  • Per-project include. Keep a .gitconfig inside the project and pull it in on demand with a global alias: git config --global alias.set-config '!git config --local include.path "$(git rev-parse --show-toplevel)/.gitconfig"', then run git set-config once per clone. Useful when the identity is a property of the project rather than of a folder or a host.
  • Third-party tools (identity switchers, clone hooks) exist. They solve the same problem with an extra dependency and a state to keep in sync; the built-in conditions cover the cases above without either.
  • Do not use includeIf for a one-off: a single repository with a different author is just git config --local user.email ….

Appendix A. The manual editing workflow

The same setup through hand-edited files. Same result; the risk is a typo in ~/.gitconfig, which makes all git config fail silently.

~/.ssh/config:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_private_key
    IdentitiesOnly yes

~/.gitconfig: replace the [user] block with:

[user]
    useConfigOnly = true
[includeIf "gitdir:~/code/personal/"]
    path = ~/.gitconfig-personal
[includeIf "gitdir:~/code/professional/"]
    path = ~/.gitconfig-professional

Provider rules (hasconfig:) take the same shape, with the patterns from step 4.

~/.gitconfig-personal:

[user]
    email = personal@users.noreply.github.com
    name = personal_username
    signingkey = SIGNING_KEY_ID
[commit]
    gpgsign = true

~/.gitconfig-professional: same shape, work values. signingkey here is the GPG variant; the SSH variant is in 2.6. Then verify as in step 5.

Appendix B. Idempotent bootstrap script

Re-runnable: --replace-all overwrites instead of appending.

#!/usr/bin/env bash
set -euo pipefail

# usage: ./git-identity.sh personal ~/code/personal/ "personal_username" "personal@users.noreply.github.com" ~/.ssh/github_personal
profile="$1"; name="$3"; email="$4"; key="${5:-}"
dir="${2%/}/"   # normalize the mandatory trailing slash
file="$HOME/.gitconfig-$profile"

git config --file="$file" --replace-all user.name  "$name"
git config --file="$file" --replace-all user.email "$email"
if [ -n "$key" ]; then
  git config --file="$file" --replace-all core.sshCommand "ssh -i $key -o IdentitiesOnly=yes"
fi

git config --global --replace-all "includeif.gitdir:${dir}.path" "$file"
git config --global user.useConfigOnly true
git config --global --unset-all user.name  || [ $? -eq 5 ]   # status 5 = already absent
git config --global --unset-all user.email || [ $? -eq 5 ]

echo "wrote $file and its includeIf rule for ${dir}"
echo "verify from inside a repo: git config --show-origin --get user.email"

Appendix C. Credits

Thanks to the commenters this guide is improved on:

@slmg

slmg commented May 20, 2020

Copy link
Copy Markdown

Thanks for the gist. To partially address your roadmap, here's how to deal with the includeIf section only using git config commands:

git config --file=.gitconfig-personal --add user.name personal_username
git config --file=.gitconfig-personal --add user.email user.personal@users.noreply.github.com

git config --global --add includeif.gitdir:~/code/personal/.path .gitconfig-personal

@bgauduch

bgauduch commented May 26, 2020

Copy link
Copy Markdown
Author

Thanks for the tip @slmg !

ghost commented Oct 8, 2020

Copy link
Copy Markdown

Thanks for the gist. To partially address your roadmap, here's how to deal with the includeIf section only using git config commands:

git config --file=.gitconfig-personal --add user.name personal_username
git config --file=.gitconfig-personal --add user.email user.personal@users.noreply.github.com

git config --global --add includeif.gitdir:~/code/personal/.path .gitconfig-personal

I dont see includeif as a option under git config --gloabl --add , am i missing something? running git 2.28.0

@slmg

slmg commented Oct 8, 2020

Copy link
Copy Markdown

It is shown under git config.

> git --version
git version 2.25.1

> git config --help | grep includeIf
       The include and includeIf sections allow you to include config directives from another source. These sections behave identically to each other with the exception
       that includeIf sections may be ignored if their condition does not evaluate to true; see "Conditional includes" below.
       You can include a config file from another by setting the special include.path (or includeIf.*.path) variable to the name of the file to be included. The
       You can include a config file from another conditionally by setting a includeIf.<condition>.path variable to the name of the file to be included.
           [includeIf "gitdir:/path/to/foo/.git"]
           [includeIf "gitdir:/path/to/group/"]
           [includeIf "gitdir:~/to/group/"]
           [includeIf "gitdir:/path/to/group/"]
           [includeIf "onbranch:foo-branch"]

@slmingol

slmingol commented Dec 6, 2020

Copy link
Copy Markdown

Keep in mind that every time you run git config --file=.gitconfig-personal --add user.name or whatever git config cmd it'll keep adding entries to the specified file. It's likely better to use --replace-all.

@offwork

offwork commented Dec 6, 2020

Copy link
Copy Markdown

I repeated the steps over and over but the name and mail are not recognized by git. I had already tried something similar to this before but failed. I can try if I see something different.

@igorbrites

Copy link
Copy Markdown

I repeated the steps over and over but the name and mail are not recognized by git. I had already tried something similar to this before but failed. I can try if I see something different.

I was having the same problem, but I realised that the path without the trailing / does not work. The commands from @slmg saved the day! Thanks guys!

@YaoC

YaoC commented Jul 9, 2021

Copy link
Copy Markdown

I repeated the steps over and over but the name and mail are not recognized by git. I had already tried something similar to this before but failed. I can try if I see something different.

Are you trying it in a git repo, it doesn't work if the working directory is not a git repo. See this.

@ThierryBerger

Copy link
Copy Markdown

On windows I had to include the case insensitive postfix /i [includeIf "gitdir/i:~/Documents/work/"]

Or maybe I should have put every letters in small letters despite windows showing capitals..? either way it's working now, thanks !

@shelllee

Copy link
Copy Markdown

Is there a way we could set .gitconfig for per domain such as github.com and gitlab.com?

@bgauduch

Copy link
Copy Markdown
Author

@shelllee not that I'm aware of !

Not sure what you are trying to do here, but it seem's to be the use case of this gist : separate ssh config depending on the host as described here

Be aware that Git config is independent from your ssh config, which git uses to connect to the Git hosts !

@shelllee

Copy link
Copy Markdown

@shelllee not that I'm aware of !

Not sure what you are trying to do here, but it seem's to be the use case of this gist : separate ssh config depending on the host as described here

Be aware that Git config is independent from your ssh config, which git uses to connect to the Git hosts !

I found this: https://github.com/DrVanScott/git-clone-init, which automatic setup of user identity on git clone by post-checkout hook.

@bgauduch

Copy link
Copy Markdown
Author

@shelllee not that I'm aware of !
Not sure what you are trying to do here, but it seem's to be the use case of this gist : separate ssh config depending on the host as described here
Be aware that Git config is independent from your ssh config, which git uses to connect to the Git hosts !

I found this: https://github.com/DrVanScott/git-clone-init, which automatic setup of user identity on git clone by post-checkout hook.

Well okay, I think I initially misunderstood πŸ˜…

but I really don't see the point adding another external tool since you can configure the exact same behavior with git includeif instruction as described here.

Up to you πŸ˜‰

@tw-yshuang

tw-yshuang commented Dec 14, 2021

Copy link
Copy Markdown

I create a CLI command to handle this!
Checkout my repo~~
This repo uses ssh-agent to switch your ssh account.

Git_SSH-Account_Switch

A CLI tool can switch an ssh account to your current shell. You will easily switch to your git account & ssh key when using the server, and using your account to manipulate the project on the server.

Installation

$ bash ./setup.sh

it will add some code in your profile & $logout_profile, and setup git-acc & .gitacc on the $HOME.
file:

git-acc.sh -> $HOME/.git-acc, git-acc function.
.gitacc -> $HOME/.gitacc, save info. that regist on git-acc.

Control

        +---------------+
        |    git-acc    |
        +---------------+

SYNOPSIS

  git-acc [account]|[option]

OPTIONS

  [account]               use which accounts on this shell, type the account name that you register.
  -h, --help              print help information.
  -add, --add_account     build git_account info. & ssh-key.
      -t, --type          ssh-key types, follow `ssh-keygen` rule, 
                          types: dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa(default)
  -rm, --remove_account   remove git_account info. & ssh-key from this device
  -out, --logout          logout your current ssh-acc.


EXAMPLES

  $ git-acc tw-yshuang

@cbbdev

cbbdev commented Mar 9, 2022

Copy link
Copy Markdown

Hello, sorry to be a little late to the party but after running into a similar issue and finding this solution, it inspired a more dynamic way to include the custom config files. When having multiple projects at the same time, the "IncludeIf..." can became too verbose and may also lead to confusion if some of those configs contain similar settings (or names). In order to alleviate this, we still placed the custom ".gitconfig" file inside each project, but in the global ".gitconfig" (in windows should be under C:\Users$user) and add an alias there like this:
[alias] set-config = !git config --global include.path $(git rev-parse --show-toplevel)/.gitconfig
We named it "set-config" but of course you can change that to your liking. after that, just do:

  • git init (to reload)
  • git set-config
    This will automatically set the path to the current project being used, meaning that it can be used without limitations or having to manually add multiple "if" statements.
    Thanks to @bgauduch for this post and hope this helps!

@Xat59

Xat59 commented Jun 22, 2022

Copy link
Copy Markdown

For your information you must now specify the .git folder in the gitdir such as :

[includeIf "gitdir:~/code/personal/repo1/.git"]

Another useful tip, you can use globbing on parent directory to detect new repos without editing your git-config file :

[includeIf "gitdir:~/code/personal/**/.git"]

@825i

825i commented Nov 22, 2023

Copy link
Copy Markdown

For your information you must now specify the .git folder in the gitdir such as :

[includeIf "gitdir:~/code/personal/repo1/.git"]

Another useful tip, you can use globbing on parent directory to detect new repos without editing your git-config file :

[includeIf "gitdir:~/code/personal/**/.git"]

Thanks! I will pulling my hair out wondering why it didn't work. Also thanks for the globbing advice because that would have been my next question!

@MGREMY

MGREMY commented Apr 24, 2024

Copy link
Copy Markdown

Thanks man ! Note that git config --get xxxx.xxxx works only when you are inside a repository, otherwise it doesn't show anything πŸ‘

@offwork

offwork commented Apr 25, 2024

Copy link
Copy Markdown

Hi there!

Simple solution for Mac and fish-shell users like me:
After the ssh keys are created, run the agent command for fish:

eval $(ssh-agent -c)

and then install the ssh keys on the mac keychain:
ssh-add --apple-load-keychain -A ~/.ssh/github_personal
ssh-add --apple-load-keychain -A ~/.ssh/bitbucket_work

and then install the ssh keys on the mac keychain.

Screenshot 2024-04-25 at 15 42 29

@shellheim

Copy link
Copy Markdown

I originally had a problem with using two hosts and when I signed my commits, the signature would be invalid on the web UI because my global git email was set to github only. What I wanted to do was figure out a way to automatically change the user.email variable to the respective noreply addresses.

That can be done using the IncludeIf directive, just have to use the right globbing pattern.
My config is like this :

[includeIf "hasconfig:remote.*.url:**github.com:*/*.git"]
	path = github_config 

[includeIf "hasconfig:remote.*.url:**codeberg.org:*/*.git"]
	path = codeberg_config

Where github_config and codeberg_config are files with their respective emails. The globbing pattern is just :

**example.com:*/*.git

for ssh remote urls.

@amaury-d

amaury-d commented Sep 2, 2024

Copy link
Copy Markdown

@shellheim nice tip πŸ‘

@bgauduch

bgauduch commented Aug 23, 2026

Copy link
Copy Markdown
Author

πŸ“£ 2026 rewrite is live!

This gist dated back to 2018. Git moved on, and your comments piled up 8 years of wisdom, so the whole thing has been rewritten:

  • Quick path through git config commands only, no manual file editing (the long-standing roadmap item, finally done)
  • Identity per provider with includeIf "hasconfig:remote.*.url:" (git 2.36+), next to the per-folder setup
  • Fixed GPG section: the key is gpg.program; the old [program] pgp block was silently ignored by git
  • New safety net: user.useConfigOnly true, so an unmatched repo fails loudly instead of committing with a guessed identity
  • Pattern grammar table (trailing slash, ** gotchas), troubleshooting table, SSH commit signing
  • Every command and pattern executed against git 2.55 before publication

⚠️ Two fixes suggested in this thread turned out to be off the root cause, and the rewrite debunks them with measurements (section 2.3):

  • The /.git suffix (or **/.git globbing) is not required. The real cause is the missing trailing slash: gitdir:~/code/personal/ matches every repo below it on its own.
  • The **example.com:*/*.git pattern only matches simple ssh clones. It misses https clones, nested groups, and URLs without .git: in this glob language ** is only special next to a slash. The guide now uses one pattern per URL shape.

πŸ™ Many thanks to everyone who commented over the years, this rewrite is built on your feedback:

Fresh eyes welcome: if something reads wrong or fails on your setup, comment away.

Take care πŸ™Œ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment