Profiles de git por diretório? Vem entender comigo!
Por um tempo eu fiz o controle de chaves SSH diretamente no clone local do repositório (leia mais em Usando chave ssh “custom” para comandos git). Porém, depois que eu troquei de computador, acabei fazendo umas bagunças não intencionais com as chaves SSH e com algumas informações de commit. Então fiquei pensando, será que não é possível eu poder determinar alguma espécie de divisão de quais dados do git eu poderia configurar uma vez só e ser feliz.
Valores padrão vs valores do repositório
Existem configurações que podemos colocar a nível de usuário. Uma delas bem comum é nome e email:
git config --global user.name 'Jefferson Quesado'
git config --global user.email 'jeff.quesado@gmail.com'
Essas configurações ficam salvas em ~/.gitconfig:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
Localmente eu consigo sobrepor esses valores simplesmente não informando ao
git que é --global:
git config user.name 'Abóbora'
git config user.email 'abobora@computaria.io'
De modo geral, eu queria que tudo que estivesse no diretório ~/computaria
tivesse alguns padrões. E para o trabalho, onde uso Claude Code, eu tenho uma
trava em que a minha chave SSH exige uma senha, e sem informar mais nada o
Claude não abre a opção de eu digitar a senha e ele fica perdidinho pensando
que a senha era unválida. Então eu preciso:
- um padrão para o sistema
- algo customizado para o computaria
- algo customizado para conseguir digitar a senha da chave SSH em um diretório
Diretrizes de inclusão
Pesquisando um pouco mais, encontrei a diretriz includeIf do gitconfig.
Basicamente, se uma expressão for verdade, ele permite a inclusão de um arquivo
para compor a configuração. Veja mais em
git-config (_includes).
Basicamente, a minha restrição era por diretório. Então, fui testar isso no
.gitconfig global:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"]
path = ~/confs/computaria
E coloquei essas informações no ~/conf/computaria:
[core]
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
[user]
name = Abóbora
email = abobora@computaria.io
Para o caso do Claude, que minha chacve SSH por padrão que ele pede usa senha,
eu teria de pedir que ele usasse um programinha (bobinho e inseguro, não façam
isso em casa, usem algo mais bem definido) que subia um popup pedindo a senha
da chave SSH. Para essa configuração eu precisava configurar o
core.sshCommand. Coloquei isso em ~/conf/claude-ssh:
[core]
sshCommand = env SSH_ASKPASS=~/bin/ssh-askpass.tcl SSH_ASKPASS_REQUIRE=force ssh
E no caso para a inclusão dessa configuração eu simplesmente confiei em uma
convenção: o trabalho que o Claude Code faria seria em cc-work. Logo, alterei
o ~/.gitconfig global:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"]
path = ~/confs/computaria
[includeIf "gitdir:~/source/firma/cc-work"]
path = ~/confs/claude-ssh
Resolução de configuração
Se estiver em dúvida de qual o valor de configuração que está valendo naquele momento, naquele diretório, você sempre pode pedir pro próprio git. Por exemplo, para pegar o comando SSH:
git config get core.sshCommand
E o git vai resolver a configuração e retornar pra você o valor correto.
Mas se você quise “pré-computar” o valor dessas variáveis manualmente, também é
possível fazer. Imagine que você vai ter uma tabela com os valores, e cada
linha que você lê das configurações do git você pode alterar o valor. E o
modelo de conf que o git usa (pelo menos no que usamos até o momento) é
sempre uma chave de “classe” seguido de um “específico”. Por exemplo, o
core.sshCommand="env SSH_ASKPASS=~/bin/ssh-askpass.tcl SSH_ASKPASS_REQUIRE=force ssh"
vira:
[core]
sshCommand="env SSH_ASKPASS=~/bin/ssh-askpass.tcl SSH_ASKPASS_REQUIRE=force ssh"
E a configuração do git não se importa se eu repetir a mesma chave de classe múltiplas vezes. Por tanto, vamos simular aqui para essa configuração:
[core]
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
[user]
name = Abóbora
email = abobora@computaria.io
[core]
sshCommand=cat
Vou identificar com um comentário onde estou lendo, e abaixo mostrar como está a tabela de valores. No começo é assim:
# leitura
[core]
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
[user]
name = Abóbora
email = abobora@computaria.io
[core]
sshCommand=cat
E a tabela vaiza. Então, lemos o primeiro bloco e fica assim:
[core]
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# leitura
[user]
name = Abóbora
email = abobora@computaria.io
[core]
sshCommand=cat
E os valores:
core.sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
Então, leio o próximo bloco:
[core]
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
[user]
name = Abóbora
email = abobora@computaria.io
# leitura
[core]
sshCommand=cat
E os valores:
core.sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
user.name = Abóbora
user.email = abobora@computaria.io
E finalmente, o último bloco. Como aqui houve um “choque” com um valor anterior, ele será sobrescrito:
[core]
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
[user]
name = Abóbora
email = abobora@computaria.io
[core]
sshCommand=cat
# leitura
Ficando assim o mapeamento:
core.sshCommand=cat
user.name = Abóbora
user.email = abobora@computaria.io
No caso do includeIf, ele vai ter uma espécie de “branch” de leitura. No
momento em que o caso da inclusão é resolvido como positivo, a leitura do
arquivo vai acontecer como se o conteúdo do arquivo estivesse ali.
Então, bora lá. Para o caso em que eu estou dentro de um subdiretório de
~/computaria:
# leitura
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"]
path = ~/confs/computaria
# conteúdo do ~/confs/computaria:
# [core]
# sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# [user]
# name = Abóbora
# email = abobora@computaria.io
[includeIf "gitdir:~/source/firma/cc-work"]
path = ~/confs/claude-ssh
E o mapeamento de valores está vazio no momento. Então, vamos ler o primeiro bloco:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
# leitura
[includeIf "gitdir:~/computaria/"]
path = ~/confs/computaria
# conteúdo do ~/confs/computaria:
# [core]
# sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# [user]
# name = Abóbora
# email = abobora@computaria.io
[includeIf "gitdir:~/source/firma/cc-work"]
path = ~/confs/claude-ssh
E fica assim o mapeamento:
user.name = Jefferson Quesado
user.email = jeff.quesado@gmail.com
Agora, entramos no bloco includeIf. Neste caso específico, como o diretório
em que me encontro é um subdiretório dentro de ~/computaria, a condição dá
true. Então é como se ele substituísse o conteúdo daquele bloco (tecnicamente
podemos colocar múltiplos path dentro do includeIf, mas o racional continua
o mesmo):
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"] # <== avaliou como true
path = ~/confs/computaria
# leitura
# conteúdo do ~/confs/computaria:
# [core]
# sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# [user]
# name = Abóbora
# email = abobora@computaria.io
[includeIf "gitdir:~/source/firma/cc-work"]
path = ~/confs/claude-ssh
Então, aqui, ela vai começar a ler o conteúdo do arquivo incluído:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"] # <== avaliou como true
path = ~/confs/computaria
# conteúdo do ~/confs/computaria:
# [core]
# sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# leitura
# [user]
# name = Abóbora
# email = abobora@computaria.io
[includeIf "gitdir:~/source/firma/cc-work"]
path = ~/confs/claude-ssh
Alterando agora os valores lidos para:
user.name = Jefferson Quesado
user.email = jeff.quesado@gmail.com
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
Ao ler o próximo bloco dentro do arquivo incluído:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"] # <== avaliou como true
path = ~/confs/computaria
# conteúdo do ~/confs/computaria:
# [core]
# sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# [user]
# name = Abóbora
# email = abobora@computaria.io
# leitura
[includeIf "gitdir:~/source/firma/cc-work"]
path = ~/confs/claude-ssh
Houve choque tanto em user.name como em user.email, ficando assim no final:
user.name = Abóbora
user.email = abobora@computaria.io
sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
O próximo canto é o includeIf que verifica se é um subdiretório de
~/source/firma/cc-work. Como havia em false, ele pula a sessão do
includeIf sem de fato fazer inclusão de nada:
[user]
name = Jefferson Quesado
email = jeff.quesado@gmail.com
[includeIf "gitdir:~/computaria/"] # <== avaliou como true
path = ~/confs/computaria
# conteúdo do ~/confs/computaria:
# [core]
# sshCommand="ssh -i ~/computaria/.ssh/id_ed25519 -o IdentitiesOnly=yes"
# [user]
# name = Abóbora
# email = abobora@computaria.io
[includeIf "gitdir:~/source/firma/cc-work"] # <== avaliou como false
path = ~/confs/claude-ssh
# leitura
Ordem de leitura
Além da questão das inclusões, o git segue alguns níveis de leitura de configurações:
- sistema
- global (ou usuário)
- local (ou repositório)
- worktree
- linha de comando
Nessa ordem. O da linha de comando você acrescenta usando a flag -c. O
funcionamento pode ser imaginado por cima como se colasse os arquivos um após o
outro (no caso do -c, bem, imagina como se fosse uma entrada no final, pode
ser?).
Inclusão não condicional
Além da inclusão condicional, que vimos acima com o includeIf, pela doc a
gente também toma conhecimento da inclusão não condicional. Ela tem a função de
abstrair um conjunto de informações.
Apesar de não servir para criar perfis, como foi feito usando a inclusão
opcional no .gitconfig a nível de usuário, pode ser usad com alguns fins:
- isolar para melhor gerenciar um conjunto de informações
- abstrair opções comuns usadas em diversos módulos
Fica a gosto do usuário a melhor alternativa de como fazer o uso.