Git Commit (e a Escada Infinita)
Entender quais são os três estágios pelos quais um arquivo pode passar no controle de versão do Git é fundamental, porque é o que vai fazer com que as alterações que a gente produzir no projeto se tornem uma realidade permanente, uma "foto".
Modified: arquivos que foram alterados
O primeiro degrau, ou estágio, que um arquivo pode passar é o de modified, modificado. Isso sempre vai acontecer quando um arquivo que já participa do repositório de uma forma concreta, ou seja, é um arquivo conhecido pelo Git, sofre uma alteração. Quando esse é o caso, ele vai ficar destacado como modified.
Mas nem todos os arquivos que você mexeu ou alterou enquanto estava, por exemplo, cavucando atrás de um bug, você quer fazer o commit, ou seja, se comprometer com essas alterações. Às vezes, você revira o repositório e deixa ele de cabeça para baixo para perceber que o ajuste precisava ser feito só numa linha de um arquivo. E, apesar de toda a bagunça feita ao redor desse arquivo, você quer se comprometer só com a alteração feita nele, e não no resto.
Staged: o palco antes da foto
E isso faz a gente ir para o próximo estágio, que são os arquivos staged, que estão numa área de preparo onde você informa pro Git que, apesar de tudo que foi modificado no repositório, você quer considerar só aquele arquivo. É colocar esse arquivo em cima do palco para, quando chegar a hora de tirar a foto, ou seja, de fazer de fato o commit, o resto fique nos bastidores por enquanto.
Committed: a foto tirada
E agora que a gente tem um arquivo modificado ali no palco, chegou a hora de se comprometer com essa versão e realizar o commit. Isso conclui a escada de estágios que um arquivo pode passar, que é infinita, na verdade. Porque, logo em seguida, tem mais um degrau com arquivos modificados, novas alterações, que depois vão ser selecionadas pra área de staging, um commit de fato, e daí por diante.
flowchart LR
A[Modified] --> B[Staged]
B --> C[Committed]
C -.-> A
Acompanhando o status do repositório
É muito importante acompanhar o status do que está acontecendo no repositório. E, para isso, usamos o comando git status:
$ git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
.next/
.gitignore
package.json
pages/
nothing added to commit but untracked files present
O Git vai comparar tudo que tem de diferente desde o último commit. E o mais importante aqui é notar que podem existir arquivos num estágio de untracked.
Untracked: o quarto estágio (que nem é estágio)
Espera, então não tem só três estágios, tem quatro? Sim e não. Pelo menos não na visão do Git. Porque um arquivo que não está sendo rastreado pelo Git, que nunca entrou num commit, para o Git esse arquivo não existe e, no limite, não importa.
Não é só porque um arquivo brotou na pasta de trabalho que é algo que a gente quer se preocupar, e muito menos se comprometer com ele. Pega como exemplo o diretório .next, criado pelo Next.js, pra guardar cache e configurações de várias coisas que formam de fato o servidor web.
Esse diretório tem um monte de coisa que a gente não vai usar diretamente e nem encostar. Se é o Next quem fica gerando esses arquivos para o controle e funcionamento dele, legal, mas eu não quero trazer isso para dentro do meu repositório. Eu não quero ir acompanhando as modificações disso, porque eu tenho os arquivos originais, que é onde eu trabalho de fato. Fora que os conteúdos dali são dinâmicos e cada ambiente passa por um processo de build para gerar eles tudo de novo.
Então, nesse contexto todo, vale mais a pena ignorar esse diretório do que acabar trazendo ele sem querer para dentro do controle de versão.
O arquivo .gitignore
Mas como manter o .next (ou outro diretório/arquivo do tipo) ali dentro da pasta de trabalho, porque é importante que ele exista para o Next funcionar na máquina, mas ao mesmo tempo fazer o Git não dar bola para ele e ignorar qualquer coisa que aconteça ali dentro?
Para isso existe um arquivo especial chamado .gitignore. Basta criar ele na raiz do projeto e listar dentro o que deve ser ignorado:
.next
Ao executar de novo o comando git status, o diretório .next não vai mais estar listado como untracked. Ele ainda existe no disco, mas agora não aparece mais no radar do status.
E você vai notar que o próprio .gitignore agora apareceu no radar como untracked. Isso porque, apesar dele ser um arquivo especial, pelo menos pro Git ele não tem nada de especial, é um arquivo. E, se você quer se comprometer com o efeito que ele causa no seu repositório, você também precisa fazer o commit dele.
git add: subindo o arquivo pro palco
Para subir o arquivo pro palco, basta digitar git add seguido do nome dele:
$ git add .gitignore
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: .gitignore
Agora o .gitignore está listado na seção Changes to be committed, ou alterações que serão confirmadas ou comitadas, que é o jeito que a turma fala. Ele está sendo listado como um novo arquivo e não está mais como untracked.
git commit: tirando a foto
A bagunça está nos bastidores, e parte dela foi expulsa do teatro e não pode mais entrar, como no caso do diretório .next. Mas ele vai ficar lá fora vendo a foto massa que a gente vai tirar agora, através do comando git commit, onde tudo que estiver staged vai ser incluído no novo commit que será criado.
$ git commit
Ao apertar enter, vai ser aberta uma janela no seu editor para você editar uma mensagem explicando o que esse commit faz. Por padrão, o Git está configurado para abrir o editor Vim, mas isso é configurável, e em editores como o VS Code uma aba vai abrir automaticamente.
Não se assuste com a tela cheia de texto que vai abrir. Nada do que está lá como observação vai entrar como mensagem do commit, porque tudo que tiver com a cerquilha (#) no início vai ser ignorado. Basta, então, você digitar no início uma mensagem que você acredita representar o que o commit faz, e ao salvar e fechar a aba, o commit é feito.
Agora, se você digitar git log, a lista agora vai incluir o commit mais novo, listado em cima, que é o que acabou de ser criado.
E se eu esquecer de ignorar algo?
Ah, mas e se, logo depois do commit, você perceber que esqueceu de ignorar outro diretório? Por exemplo, o node_modules, que também é gerado de forma automática, guardando os módulos instalados pelo comando npm install. Não importa muito como isso está sendo feito, até porque existe o manifesto package.json que consegue recriar essas instalações do zero.
Dado que você já se comprometeu com a versão atual do .gitignore, dá pra simplesmente alterar o arquivo, adicionar node_modules dentro dele, dar um stage nessa alteração e fazer um novo commit*:
.next
node_modules
E está tudo certo. Mas, em situações como essa, existem truques mais avançados do Git, quase uma viagem no tempo, que permitem ajustar o commit anterior em vez de criar um novo. Coisas pra explorar conforme a intimidade com o Git vai aumentando.
* Isso só funciona se o node_modules ainda não tiver sido commitado. Se ele já tivesse entrado em algum commit anterior, o Git continuaria rastreando o diretório mesmo após adicionar ele ao .gitignore, exigindo passos adicionais.