Skip to content
This page has been auto-translated and may contain errors.View in English

Ignorando arquivos e boas práticas

docs.scrimba.com

Você clona um projeto chamado weather-app, executa npm install e verifica o status do seu novo repositório.

bash
$ git status
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	node_modules/
	dist/
	.env

Três pastas e arquivos aparecem como não rastreados, e nenhum deles deve estar em um commit. node_modules/ é milhares de arquivos que npm install regenera a partir de package.json em segundos. dist/ é saída de build que é reconstruída a partir do seu código-fonte toda vez. E .env contém uma chave de API. Nada disso deveria nunca chegar no histórico do seu projeto, e digitar git add . sem pensar é exatamente como acontece.

Dizendo ao Git o que ignorar

Um arquivo .gitignore lista padrões para arquivos e pastas que Git nunca deve rastrear. Você o cria uma única vez na raiz do seu projeto, o mesmo lugar onde você executou git init quando configurou o repositório em Seu primeiro repositório, e a partir daí, git status e git add . silenciosamente pulam tudo que corresponde.

bash
# .gitignore
node_modules/
dist/
.env

Cada linha é um padrão: um nome de pasta simples como dist/ ignora toda essa pasta, onde quer que apareça no projeto. Faça commit do próprio arquivo .gitignore. Ele é pequeno, é útil para todos que clonam o projeto, e deve estar no histórico da mesma forma que seu código-fonte.

bash
$ git status
On branch main
nothing to commit, working tree clean

Com os padrões no lugar, git status fica silencioso. Esse é exatamente o ponto: as pastas e arquivos que você nunca quer fazer commit param de aparecer como ruído, então as coisas que aparecem são as que realmente importam.

Os padrões suportam globs, então *.log ignora todo arquivo que termina em .log, não importa seu nome. Uma barra à direita restringe um padrão a diretórios, então dist/ corresponde apenas a uma pasta chamada dist e deixa qualquer arquivo que compartilhe o nome em paz. Comece uma linha com ! para des-ignorar algo que um padrão mais amplo capturaria, útil quando um arquivo dentro de uma pasta ignorada precisa ser rastreado:

bash
# .gitignore
logs/
!logs/keep-this.log

Git também lê um arquivo de ignore global, um por máquina, para padrões que você quer em todo repositório independentemente do projeto: lixo de editor como .DS_Store ou *.swp. Configure uma única vez com git config --global core.excludesfile ~/.gitignore_global, e você nunca precisa adicionar esses padrões projeto por projeto novamente.

Padrões de ignore são correspondidos da mesma forma que globs de shell: * para qualquer sequência de caracteres, ? para um, ** para correspondência em diretórios. Um padrão sem barra corresponde em qualquer profundidade no projeto, enquanto um padrão contendo uma barra é ancorado nesse caminho. Corresponder esses padrões é um comportamento puramente local: Git só consulta .gitignore ao decidir o que git status e git add mostram a você. Não impede um arquivo de ser rastreado depois que Git já o tem, o que importa na próxima seção.

JunoDizendo ao Git o que ignorar Um arquivo .gitignore lista o que Git nunca deve rastrear: node_modules/, dist/, e .env são os suspeitos usuais em quase qualquer projeto. Faça commit do próprio arquivo .gitignore para que todos que clonam o projeto obtenham o mesmo status limpo. Uma vez perdi uma tarde inteira fazendo stage de milhares de arquivos de dependência por acidente antes de aprender isso.
JunoDizendo ao Git o que ignorar Padrões suportam globs como *.log, uma barra à direita significa "somente diretório", e um ! à esquerda des-ignora algo dentro de uma correspondência mais ampla. Um arquivo de ignore global, configurado com core.excludesfile, é onde lixo de editor e SO deve ficar para você parar de adicionar .DS_Store ao .gitignore de todo projeto manualmente.
JunoDizendo ao Git o que ignorar As regras de ignore são correspondência de glob estilo shell, aplicadas no cliente, e elas apenas mudam o que Git mostra a você como não rastreado. Lembre-se que uma regra pode apenas afetar um arquivo que Git ainda não conhece. Essa distinção é pequena no papel e cara na prática.

Nunca faça commit de secrets

Uma chave de API, uma senha de banco de dados, ou um certificado de assinatura nunca devem aparecer em um commit. Isso vale para um repositório privado tão firmemente quanto um público, e vale a partir do primeiro commit em diante. Coloque secrets em um arquivo como .env, adicione esse arquivo ao .gitignore no primeiro dia, e carregue os valores em seu app a partir de lá em vez de escrevê-los em seus arquivos-fonte.

bash
# .env
WEATHER_API_KEY=sk_live_9f8e7d6c5b4a
bash
# .gitignore
.env

Siga essa regra automaticamente, sempre, sem exceções. Uma chave em um commit é acessível por qualquer pessoa com acesso ao repositório, e em um repositório público, por qualquer pessoa.

Em um projeto real, secrets geralmente vivem em mais de um lugar: um arquivo .env para sua própria máquina, e um gerenciador de secrets ou as configurações de variáveis de ambiente do seu host para qualquer coisa implantada. Ambos funcionam da mesma forma na prática: o valor vive fora de seus arquivos-fonte e fora do Git, e seu código o lê do ambiente em tempo de execução. Envie um arquivo .env.example em vez disso, com os nomes de variáveis mas nenhum valor real, para que os colegas de trabalho saibam o que configurar sem nunca ver sua chave real.

Aqui está o fato que torna essa regra inegociável: um commit do qual você deleta um secret não o remove do histórico. Todo commit anterior que tinha a chave ainda a tem, salva para sempre no histórico do repositório, e todo clone que alguém fez carrega esses commits antigos junto com ele. Deletar o arquivo em um novo commit apenas significa que o snapshot mais novo não o tem mais; o objeto contendo o valor antigo ainda está sentado em .git, acessível por qualquer pessoa que faça checkout de um commit anterior ou escave pelo log.

"Deletar o arquivo e fazer commit novamente" nunca conserta um secret já vazado. Se um secret real nunca chegar em um commit, rotacione ou revogue-o no provedor (emita uma nova chave, invalide a antiga) para que o valor vazado pare de ser útil, independentemente do que você faça com o repositório depois. Reescrever histórico para tirar um secret é possível, mas não desfaz qualquer uso que a chave já teve enquanto estava exposta. Trate a rotação como o conserto que importa e a limpeza de histórico como um extra opcional por cima.

JunoNunca faça commit de secrets Nunca coloque uma chave de API real, senha, ou certificado em um commit. Mantenha secrets em um arquivo como .env e adicione .env ao seu .gitignore antes de rodar git add. Essa é a única regra neste capítulo inteiro que merece ser tratada como absoluta.
JunoNunca faça commit de secrets Secrets locais vão em .env, secrets implantados vão nas variáveis de ambiente do seu host ou um gerenciador de secrets, e nenhum nunca é commitado. Envie um .env.example com apenas os nomes de variáveis, para que os colegas saibam o que configurar sem ver um valor real.
JunoNunca faça commit de secrets Deletar um secret em um novo commit não o remove do histórico. Todo commit anterior e todo clone existente ainda o tem. Rotacione a chave no provedor no momento em que vazar: essa rotação é o conserto que realmente importa.

Adicionando um arquivo ao .gitignore depois que está rastreado

Essa é a armadilha que pega quase todos pelo menos uma vez. Você faz commit de um arquivo por acidente, percebe seu erro, adiciona ao .gitignore (a linha echo abaixo acrescenta o caminho a esse arquivo), e espera que Git esqueça dele. Não esquece.

bash
$ git add config/settings.json
$ git commit -m "Add app settings"

# depois, você percebe que foi um erro
$ echo "config/settings.json" >> .gitignore
$ git status
On branch main
nothing to commit, working tree clean

git status mostra uma árvore limpa, mas config/settings.json ainda é rastreado e ainda aparece em todo git log futuro e em todo clone. Regras de ignore se aplicam apenas a arquivos que Git ainda não conhece. Uma vez que um arquivo foi adicionado e commitado, faz parte do seu histórico, e .gitignore não tem opinião sobre arquivos já naquele histórico.

Para realmente parar de rastreá-lo a partir de agora, diga ao Git para soltar o arquivo do que segue deixando o arquivo no seu disco:

bash
$ git rm --cached config/settings.json
$ git commit -m "Stop tracking config/settings.json"

git rm --cached <file> remove o arquivo do rastreamento do Git sem deletá-lo do seu diretório de trabalho. Faça commit dessa remoção, e a partir daí seu padrão .gitignore faz o trabalho que você queria que fizesse desde o começo.

O mesmo conserto funciona em uma pasta inteira com git rm -r --cached <folder>, que é o movimento usual depois de perceber que node_modules/ ou dist/ foi commitado no início da vida de um projeto, antes de alguém adicionar um .gitignore de forma alguma. Execute uma vez, faça commit da remoção, e a pasta desaparece dos commits futuros enquanto a cópia local de cada colega dos arquivos fica intocada no disco.

Esse comportamento segue diretamente do que a staging area realmente é. Git rastreia arquivos através do index, o registro do exatamente o que irá para o próximo commit. Adicionar e fazer commit de um arquivo escreve uma entrada para ele no index e no snapshot do commit; .gitignore é apenas consultado quando Git está decidindo o que fazer com um arquivo que ainda não tem uma entrada no index.

Uma vez que uma entrada existe, padrões de ignore são irrelevantes para ela. git rm --cached solta a entrada no index mantendo o arquivo no disco, é por isso que é o comando que realmente conserta isso, onde editar apenas o padrão de ignore não faz nada.

JunoAdicionando um arquivo ao .gitignore depois que está rastreado Adicionar um arquivo ao .gitignore depois que Git já o rastreia não faz nada para esse arquivo. Regras de ignore se aplicam apenas a arquivos que Git nunca adicionou ainda. Para parar de rastrear um arquivo que escapou por acidente, execute git rm --cached <file> e faça commit dessa mudança.
JunoAdicionando um arquivo ao .gitignore depois que está rastreado.gitignore apenas afeta arquivos não rastreados. Para qualquer coisa já commitada, solte-a com git rm --cached <file>, ou git rm -r --cached <folder> para um diretório inteiro, depois faça commit. Os arquivos locais de todos ficam no lugar, apenas o rastreamento deles por Git muda.
JunoAdicionando um arquivo ao .gitignore depois que está rastreado O index contém uma entrada para cada arquivo rastreado, e .gitignore é apenas consultado quando não há entrada ainda. git rm --cached deleta a entrada no index sem tocar o arquivo no disco, que é o conserto real aqui. Adicione o padrão de ignore por boa medida para que o arquivo não possa escapar novamente.

Boas práticas: commits pequenos e repositório limpo

Um .gitignore organizado é metade de manter um repositório que vale a pena trabalhar. A outra metade é como você molda seus commits. Faça cada commit uma mudança focada: uma correção de bug, uma pequena feature, um único refactor. Se uma tarde de trabalho toca várias coisas não relacionadas, divida em vários commits em vez de jogar tudo em um só no final do dia.

bash
$ git add src/weather-widget.js
$ git commit -m "Fix temperature rounding in weather widget"

Commits pequenos são mais fáceis de revisar, mais fáceis de reverter se algo quebrar, e mais fáceis de ler novamente seis meses depois quando você está tentando lembrar por que uma linha de código existe. O commit loop cobre o que torna uma mensagem de commit útil; esse hábito é sobre manter a mudança em si pequena o suficiente para que uma boa mensagem seja até possível de escrever.

Combine esse hábito com um .gitignore limpo e o resultado se intensifica: um histórico feito de commits pequenos e propositais, sem pastas geradas ou arquivos de config vagando poluindo o diff. Revisar uma mudança cheia de dist/ churn junto com as duas linhas reais que alguém editou é miserável para todos envolvidos, e é totalmente evitável com um .gitignore configurado no primeiro dia.

Em escala isso compensa de formas além de legibilidade. Um repositório livre de arquivos gerados e pastas de dependência permanece pequeno o suficiente para clonar rapidamente e procurar por limpamente, e um histórico de commits focados é o que torna git log --oneline e git diff entre dois pontos no tempo realmente úteis para entender o que aconteceu e por quê. Nada disso funciona bem contra um histórico onde metade dos commits é node_modules/ churn e a outra metade mistura cinco mudanças não relacionadas juntas.

JunoBoas práticas: commits pequenos e repositório limpo Mantenha cada commit em uma mudança focada, e mantenha seu .gitignore cobrindo as pastas e secrets que nunca devem ser rastreados. Esses dois hábitos juntos são a maior parte do que torna um repositório agradável de trabalhar. Pequenos hábitos, formados cedo, economizam muita limpeza depois.
JunoBoas práticas: commits pequenos e repositório limpo Commits pequenos e focados mais um .gitignore limpo tornam reviews e diffs realmente legíveis em vez de enterrados em ruído gerado. Configure o .gitignore antes do seu primeiro commit. Esperar até node_modules/ aparecer em um pull request pela quinta vez custa mais limpeza do que economiza.
JunoBoas práticas: commits pequenos e repositório limpo Um repositório enxuto e um histórico de commits focados são o que mantêm clones rápidos e histórico legível quando você o procura. Todo arquivo gerado ou pasta de dependência que escapa para o histórico é um pequeno imposto que todo clone futuro e busca paga. Configure as regras de ignore na frente e para de ser um problema que vale a pena pensar novamente.