O que é um arquivo .gitignore?
Um arquivo .gitignore é um arquivo de texto que diz ao Git quais arquivos deixar de fora do controle de versão. Cada linha é um padrão — *.log, node_modules/, .env — e qualquer caminho não rastreado que corresponda a um padrão desaparece do git status, é pulado pelo git add . e nunca vai para um commit.
A expressão não rastreado carrega quase todo o sentido dessa frase e, sozinha, explica mais casos de “o gitignore não funciona” do que todos os erros de padrão somados. O Git só consulta o .gitignore para arquivos que ele ainda não rastreia. A partir do momento em que um arquivo entrou num commit, o Git continua rastreando ele para sempre, não importa o que você adicione depois ao .gitignore.
O que o Gitignore Debugger faz
Cole um caminho de arquivo e o conteúdo do seu .gitignore: este depurador de .gitignore dá o mesmo veredito que o Git — ignorado ou não ignorado — junto com a regra exata e o número da linha responsáveis. Depois ele lista todas as regras que correspondem ao caminho, na ordem em que o Git as avalia, e aponta a que realmente decidiu.
É essa última parte que um simples verificador de gitignore deixa de fora. Saber que um arquivo está ignorado raramente é a parte difícil. Saber qual das suas quarenta regras fez isso — e por que a que você esperava perdeu — é que é. Funciona nos dois sentidos: por que o Git ignora o arquivo que você quer commitar, e por que ele não ignora aquele que você quer tirar do caminho.
O motor reimplementa as regras de correspondência de padrões do Git: padrões por nome base contra padrões ancorados, * que nunca atravessa uma /, as três formas de **, os sufixos / só para diretórios, as classes de caracteres, os espaços finais escapados. O comportamento dele é verificado contra a saída real do git check-ignore, não presumido.
Como usar
- Digite o caminho como ele aparece no seu repositório, relativo à raiz —
src/config/local.json, nãoC:\projects\app\src\config\local.json. Use barras normais; as barras invertidas do Windows são convertidas para você. - Cole o seu
.gitignoreinteiro. Não reduza ele à única regra da qual você suspeita — a precedência entre regras geralmente é o bug de verdade, e recortar o arquivo joga essa informação no lixo. - Leia o veredito e depois a cadeia de regras abaixo. É aí que uma regra de gitignore que não funciona deixa de ser um mistério.
Para testar regras de gitignore contra um diretório em vez de um arquivo, termine o caminho com uma barra — build/. A distinção importa, porque uma regra terminada em / só corresponde a diretórios.
Por que as regras de gitignore às vezes não funcionam
Os culpados, mais ou menos em ordem de frequência:
- O arquivo já está sendo rastreado. O
.gitignorenão é retroativo. Se o arquivo entrou num commit antes de a regra existir, a regra é totalmente ignorada. Rodegit rm --cached path/to/filee faça o commit: o arquivo continua no disco e o Git para de rastrear ele. - Uma regra posterior sobrescreveu ela. O Git não para no primeiro padrão que corresponde.
- Um diretório pai está ignorado. A regra que você escreveu para o arquivo nem chega a ser alcançada.
- O padrão está ancorado e você achava que era livre. Um padrão com uma barra em qualquer posição que não seja o fim fica ancorado ao diretório que contém o
.gitignore, entãosrc/*.logcorresponde asrc/debug.loge a nada mais fundo. Um padrão sem barra nenhuma corresponde por nome base em qualquer profundidade:*.logpega tantodebug.logquantosrc/debug.logea/b/c/debug.log. - O padrão está malformado. Uma classe de caracteres não fechada como
[abcnão vira um[literal. O Git desiste da correspondência e a regra silenciosamente não corresponde a nada — indistinguível de uma regra que nunca foi escrita. No depurador, ela simplesmente nunca aparece na lista de regras correspondentes.
Precedência das regras de gitignore: a última correspondência vence
Essa é a que pega todo mundo. Dentro do mesmo .gitignore, é o último padrão que corresponde a um caminho que decide o destino dele, não o primeiro. Estes dois arquivos têm regras idênticas e dão resultados opostos para important.log:
*.log
!important.log → important.log fica no Git!important.log
*.log → important.log é ignoradoEntão coloque as regras amplas primeiro e as exceções depois. Entre arquivos, um .gitignore num subdiretório vence outro acima dele para os caminhos que ficam abaixo. O Git também lê o .git/info/exclude, que é do repositório e nunca vai para um commit, e o seu core.excludesFile global. Uma regra que você não acha em nenhum .gitignore costuma estar escondida num desses dois lugares — git check-ignore -v mostra o arquivo e a linha que decidiram.
Regras de negação, e por que ! falha tanto
Colocar ! na frente de um padrão reinclui algo que uma regra anterior tinha ignorado. Isso tem dois limites, e o segundo explica praticamente todos os casos de “a negação do gitignore não funciona”.
Primeiro, alguma coisa antes precisa ter ignorado o caminho. !important.log sozinho não faz nada, porque nunca houve nada para sobrescrever.
Segundo, e bem mais surpreendente: você não pode reincluir um arquivo que está dentro de um diretório ignorado. Isto não funciona, e nenhuma reordenação resolve:
build/
!build/keep.txtQuando build/ está ignorado, o Git nunca entra ali. Ele não examina build/keep.txt, então nunca chega à linha 2. Ignore em vez disso o conteúdo do diretório, o que deixa o diretório em si percorrível:
build/*
!build/keep.txtPara salvar algo mais fundo, cada diretório intermediário também precisa continuar acessível — build/*, depois !build/assets/, depois !build/assets/logo.svg.
Diretórios pais ignorados
A varredura de diretórios do Git para no primeiro diretório ignorado que ela encontra, e tudo que está abaixo herda o veredito sem ser examinado. É por isso que um pai ignorado vence qualquer regra escrita para o próprio arquivo, por específica que ela pareça.
Nesse caso o depurador nomeia o pai que bloqueou a varredura em vez de apenas dizer que o arquivo está ignorado, e ainda mostra as regras de negação que você escreveu para o arquivo — marcadas como inalcançáveis, porque o Git nunca foi longe o suficiente para considerar elas. Conserte o diretório primeiro e as regras abaixo voltam a funcionar por conta própria.
Erros comuns de .gitignore
- Adicionar a regra depois de commitar o arquivo. A regra está certa e não faz nada. Veja
git rm --cachedacima. - Comentários no fim da linha. O
#só abre um comentário no começo de uma linha, então*.log # debug outputé um único padrão comprido que não corresponde a nada do que você tem. - Barras invertidas como separadores. Num
.gitignore,\é um caractere de escape, nunca um separador de caminho.src\configsignifica o nome literalsrcconfig. Escreva sempresrc/config. - Esperar que
*atravesse diretórios. O*para numa barra, entãosrc/*/test.jsdesce só um nível. Usesrc/**/test.jspara qualquer profundidade. - Esquecer a barra no fim.
logscorresponde tanto a um arquivo chamadologsquanto ao diretório;logs/corresponde só ao diretório. - Achar que ignorar apaga o histórico. Uma regra impede os commits futuros. Ela não remove um segredo que já foi enviado — troque a credencial.
Alguns exemplos reais
O caminho src/logs/debug.log contra estas regras não está ignorado. A negação é a última correspondência e nenhum diretório pai bloqueia a varredura, então ela é alcançada e vence:
*.log
!src/logs/debug.logO caminho build/keep.txt contra estas regras está ignorado, por causa do build/ na linha 1, que corresponde ao diretório pai. A linha 2 nunca é acionada:
build/
!build/keep.txtO caminho docs/api/notes.md contra esta regra não está ignorado, porque o padrão está ancorado pela barra dele e o * não vai atravessar a de api/notes.md. O que se queria era docs/**/*.md:
docs/*.mdCole qualquer um destes exemplos no testador de gitignore acima para ver a cadeia de regras completa. De volta ao terminal, git check-ignore -v path/to/file é o equivalente embutido — ele nomeia o arquivo de origem, o número da linha e o padrão que decidiram, e vale a pena conhecer.