O que é um ficheiro .gitignore?
Um ficheiro .gitignore é um ficheiro de texto que diz ao Git quais os ficheiros a deixar fora do controlo de versões. Cada linha é um padrão — *.log, node_modules/, .env — e qualquer caminho não seguido que corresponda a um padrão desaparece do git status, é saltado pelo git add . e nunca é submetido.
A expressão não seguido carrega quase todo o significado dessa frase e, por si só, explica mais casos de «o gitignore não funciona» do que todos os erros de padrão juntos. O Git só consulta o .gitignore para ficheiros que ainda não segue. A partir do momento em que um ficheiro é submetido, o Git continua a segui-lo indefinidamente, por muito que acrescente depois ao .gitignore.
O que faz o Gitignore Debugger
Cole um caminho de ficheiro e o conteúdo do seu .gitignore: este depurador de .gitignore devolve o mesmo veredicto que o Git — ignorado ou não ignorado — juntamente com a regra exacta e o número de linha responsáveis. Depois lista todas as regras que correspondem ao caminho, na ordem em que o Git as avalia, e assinala a que realmente decidiu.
É esta última parte que um simples verificador de gitignore deixa de fora. Saber que um ficheiro está ignorado raramente é a parte difícil. Saber qual das suas quarenta regras o fez — e porque é que a que esperava perdeu — é que é. Funciona nos dois sentidos: porque é que o Git ignora o ficheiro que quer submeter, e porque é que não ignora aquele de que se quer livrar.
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 / reservados a diretórios, as classes de caracteres, os espaços finais escapados. O seu comportamento é verificado contra a saída real do git check-ignore, não presumido.
Como utilizar
- Introduza o caminho tal como aparece no seu repositório, relativo à raiz —
src/config/local.json, nãoC:\projects\app\src\config\local.json. Use barras; as barras invertidas do Windows são convertidas por nós. - Cole o seu
.gitignorepor inteiro. Não o reduza à única regra de que suspeita — a precedência entre regras é geralmente o verdadeiro erro, e cortar o ficheiro deita fora essa informação. - Leia o veredicto 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 ficheiro, termine o caminho com uma barra — build/. A distinção importa, porque uma regra terminada em / só corresponde a diretórios.
Porque é que as regras de gitignore às vezes não funcionam
Os culpados, mais ou menos por ordem de frequência:
- O ficheiro já está a ser seguido. O
.gitignorenão é retroactivo. Se o ficheiro foi submetido antes de a regra existir, a regra é totalmente ignorada. Executegit rm --cached path/to/filee submeta: o ficheiro permanece no disco e o Git deixa de o seguir. - Uma regra posterior sobrepôs-se-lhe. O Git não pára no primeiro padrão que corresponde.
- Um diretório superior está ignorado. A regra que escreveu para o ficheiro nem chega a ser alcançada.
- O padrão está ancorado e pensava 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, pelo quesrc/*.logcorresponde asrc/debug.loge a nada mais profundo. Um padrão sem qualquer barra corresponde por nome base a qualquer profundidade:*.logapanha tantodebug.logcomosrc/debug.logea/b/c/debug.log. - O padrão está mal formado. Uma classe de caracteres não fechada como
[abcnão passa a ser um[literal. O Git desiste da correspondência e a regra deixa silenciosamente de corresponder a absolutamente 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: vence a última correspondência
Esta é a que apanha todos. Dentro do mesmo .gitignore, é o último padrão que corresponde a um caminho que decide o seu destino, não o primeiro. Estes dois ficheiros contê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 é ignoradoPortanto ponha as regras amplas primeiro e as excepções depois. Entre ficheiros, um .gitignore num subdiretório vence outro acima dele para os caminhos abaixo. O Git também lê o .git/info/exclude, próprio do repositório e nunca submetido, e o seu core.excludesFile global. Uma regra que não encontra em nenhum .gitignore esconde-se muitas vezes num desses dois lugares — git check-ignore -v mostra o ficheiro e a linha que decidiram.
Regras de negação, e porque é que ! falha tantas vezes
Prefixar um padrão com ! volta a incluir algo que uma regra anterior tinha ignorado. Isto tem dois limites, e o segundo explica praticamente todos os casos de «a negação do gitignore não funciona».
Primeiro, algo anterior tem de ter ignorado o caminho. !important.log sozinho não faz nada, porque nunca houve nada a sobrepor.
Segundo, e bem mais surpreendente: não é possível voltar a incluir um ficheiro 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 lá. Não examina build/keep.txt, pelo que nunca chega à linha 2. Ignore em vez disso o conteúdo do diretório, o que deixa o próprio diretório percorrível:
build/*
!build/keep.txtPara salvar algo mais profundo, cada diretório intermédio também tem de continuar acessível — build/*, depois !build/assets/, depois !build/assets/logo.svg.
Diretórios superiores ignorados
O percurso de diretórios do Git pára no primeiro diretório ignorado que encontra, e tudo o que está abaixo herda o veredicto sem ser examinado. É por isso que um diretório superior ignorado vence qualquer regra escrita para o próprio ficheiro, por específica que pareça.
Nesse caso, o depurador nomeia o diretório que bloqueou o percurso em vez de dizer apenas que o ficheiro está ignorado, e continua a mostrar as regras de negação que escreveu para o ficheiro — marcadas como inalcançáveis, porque o Git nunca foi longe o suficiente para as considerar. Corrija primeiro o diretório e as regras abaixo voltam a funcionar por si.
Erros comuns de .gitignore
- Acrescentar a regra depois de submeter o ficheiro. A regra está correcta e não faz nada. Veja
git rm --cachedacima. - Comentários no fim da linha. O
#só abre um comentário no início de uma linha, pelo que*.log # debug outputé um único padrão comprido que não corresponde a nada do que tem. - Barras invertidas como separadores. Num
.gitignore,\é um carácter de escape, nunca um separador de caminhos.src\configsignifica o nome literalsrcconfig. Escreva sempresrc/config. - Esperar que
*atravesse diretórios. O*pára numa barra, pelo quesrc/*/test.jsdesce apenas um nível. Usesrc/**/test.jspara qualquer profundidade. - Esquecer a barra final.
logscorresponde tanto a um ficheiro chamadologscomo ao diretório;logs/corresponde apenas ao diretório. - Pensar que ignorar apaga o histórico. Uma regra impede os commits futuros. Não remove um segredo já enviado — rode a credencial.
Alguns exemplos práticos
O caminho src/logs/debug.log face a estas regras não está ignorado. A negação é a última correspondência e nenhum diretório superior bloqueia o percurso, pelo que é alcançada e vence:
*.log
!src/logs/debug.logO caminho build/keep.txt face a estas regras está ignorado, por causa de build/ na linha 1, que corresponde ao diretório superior. A linha 2 nunca é activada:
build/
!build/keep.txtO caminho docs/api/notes.md face a esta regra não está ignorado, porque o padrão está ancorado pela sua barra e o * não atravessa 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 integrado — nomeia o ficheiro de origem, o número de linha e o padrão que decidiram, e vale a pena conhecê-lo.