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

O ciclo de commit ​

docs.scrimba.com

Digamos que você esteja trabalhando no weather-app nos últimos vinte minutos. Você corrigiu um bug real em src/index.js (a conversão de temperatura estava errada) e, enquanto estava lá, também começou a reestilizar o styles.css, mas essa mudança está pela metade e não está pronta para outras pessoas verem. Você quer salvar a correção do bug como um checkpoint próprio agora mesmo, sem arrastar junto a estilização inacabada.

É exatamente para isso que existe o ciclo de commit. Se você ainda não configurou um repositório, Seu primeiro repositório mostra passo a passo o git init e a clonagem. A partir daqui, este capítulo assume que você já tem um repositório aberto.

As três paradas: diretório de trabalho, área de staging, commit ​

Toda mudança que você faz no Git passa por três paradas antes de se tornar histórico permanente. Seu diretório de trabalho é o projeto exatamente como ele está no disco agora, os arquivos que você está editando ativamente. A área de staging é um local temporário onde você coloca exatamente as mudanças que quer salvar a seguir. Um commit é o próprio instantâneo salvo, depois que você o registra, junto com a mensagem que o descreve.

Imagine se preparar para uma mudança de casa. Sua casa inteira é o diretório de trabalho: tudo que você possui, no estado em que estiver. As caixas que você já embalou e fechou com fita perto da porta são a área de staging: só o que você escolheu deliberadamente levar. O caminhão de mudança saindo com essas caixas é o commit: um registro exato do que saiu, naquele momento, com uma etiqueta do lado de fora dizendo o que tem dentro.

Veja esse modelo mental aparecendo em um terminal de verdade. Depois de editar os dois arquivos no weather-app, o git status lê o estado atual sem mudar nada:

bash
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   src/index.js
        modified:   styles.css

no changes added to commit (use "git add" to commit)

Os dois arquivos aparecem como "not staged" porque editar um arquivo só altera o seu diretório de trabalho. Nada é movido para a área de staging até você dizer ao Git para colocá-lo lá com git add, que é o assunto da próxima seção.

É aqui que fica claro para que a área de staging existe. A área de staging permite que você escolha exatamente o que vai entrar no seu commit, independentemente do que ainda está editado no seu diretório de trabalho. Sem ela, salvar um checkpoint obrigaria você a levar todas as mudanças do disco de uma vez, prontas ou não.

Com ela, você pode colocar em staging a correção já concluída em src/index.js, deixar de fora o styles.css pela metade, e fazer commit apenas da parte que realmente está pronta. Esse detalhe, o de que o staging e o seu diretório de trabalho podem conter coisas diferentes ao mesmo tempo, confunde quase todo mundo na primeira semana. Depois que isso faz sentido, o resto do fluxo diário do Git fica muito mais claro.

No trabalho do dia a dia, torne o git status um passo de rotina antes de cada add e de cada commit, mesmo que nada pareça estranho. Não custa nada, nunca altera seu projeto e evita o erro comum de fazer commit de mais coisas do que você pretendia. Acostume-se a checar o status, colocar em staging e checar o status de novo antes de commitar: leva segundos e garante que a mensagem do seu commit sempre corresponda ao que realmente entrou nele.

A área de staging é um arquivo real, chamado de índice, que fica em .git/index dentro do seu repositório. Toda vez que você executa git add, o Git atualiza esse arquivo para registrar qual versão de qual arquivo está em staging. Quando você executa git commit, o Git constrói o novo commit puramente a partir do que o índice diz, sem checar seu diretório de trabalho novamente. Esse é todo o mecanismo por trás da divisão que você viu há pouco no git status: dois arquivos editados no disco, mas o índice ainda não rastreando nenhum dos dois, então o commit seguinte, no momento, não conteria nada.

JunoAs três áreas do Git Seu diretório de trabalho são seus arquivos exatamente como estão agora. A área de staging é onde você coloca exatamente as mudanças que quer salvar a seguir, e um commit é o instantâneo salvo depois que você o registra. Editar um arquivo nunca o coloca em staging por si só, você escolhe o que entra com git add, e é isso que permite salvar uma mudança concluída deixando outra pela metade de fora.
JunoAs três áreas do Git Diretório de trabalho, área de staging, commit: as edições vivem no primeiro, o git add move exatamente o que você escolhe para o segundo, e o git commit salva o segundo como um instantâneo permanente. Execute o git status antes de colocar em staging e de novo antes de commitar, é gratuito e mantém seus commits alinhados com o que você pretendia salvar.
JunoAs três áreas do Git A área de staging é um arquivo real, o índice em .git/index, e o git commit constrói seu instantâneo a partir do índice, nunca diretamente do seu diretório de trabalho. É por isso que dois arquivos editados podem aparecer como não staged: o índice ainda não foi informado sobre nenhum dos dois. Entender o índice desse jeito faz cada comando de staging parecer menos mágico e mais como ler e escrever em um único arquivo.

Colocando mudanças em staging: git add ​

O git add move uma mudança do seu diretório de trabalho para a área de staging. Aponte-o para o arquivo cujas mudanças você quer no próximo commit:

bash
$ git add src/index.js
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   src/index.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   styles.css

O src/index.js foi movido para "Changes to be committed": a correção do bug está em staging e pronta. O styles.css continua listado em "not staged", exatamente onde você quer que fique enquanto a reestilização não termina. Executar também git add styles.css colocaria esse arquivo em staging; executar git add . coloca em staging todos os arquivos alterados na pasta atual de uma vez, o que é conveniente quando você confia que tudo no disco realmente está pronto para seguir.

Às vezes um único arquivo tem tanto uma mudança que você quer salvar quanto uma que não quer, e dividi-las em dois arquivos não é prático. O git add -p <arquivo> (abreviação de --patch) percorre as mudanças do arquivo em pequenos trechos, chamados de hunks, e pede que você decida sobre cada um:

bash
$ git add -p src/index.js
@@ -12,7 +12,7 @@ function celsiusToFahrenheit(celsius) {
-  return celsius * (9 / 5 + 32)
+  return (celsius * 9) / 5 + 32
Stage this hunk [y,n,q,a,d,e,?]?

Responda y para colocar esse hunk em staging, n para deixá-lo para depois, e q para parar. Isso te dá controle no tamanho de um commit até dentro de um único arquivo, algo que vale a pena usar sempre que "metade deste arquivo está pronta" descreve sua situação.

O git add faz duas coisas ao mesmo tempo, e conhecer as duas explica uma pegadinha que você vai encontrar em algum momento. Primeiro, ele escreve o conteúdo atual do arquivo em um novo blob, um objeto que o Git armazena permanentemente, identificado por um hash da mesma forma que os commits, que guarda o conteúdo exato de um arquivo em um determinado momento. Segundo, ele atualiza o índice para apontar para esse blob referente a esse arquivo. Depois disso, o add não olha mais para o arquivo novamente.

É por isso que, se você faz git add src/index.js e continua editando o mesmo arquivo antes de commitar, o git status mostra o arquivo como staged e modified ao mesmo tempo: o índice ainda aponta para o blob de quando você executou o add, enquanto seu diretório de trabalho seguiu adiante. O git commit só vê o blob em staging, então é um segundo git add que traz as edições mais recentes antes de você salvar.

JunoColocando mudanças em staginggit add <arquivo> move as mudanças atuais desse arquivo para a área de staging, prontas para o próximo commit. Arquivos que você ainda não adicionou ficam de fora, então você pode terminar uma coisa e deixar outra pela metade. git add . coloca em staging todos os arquivos alterados de uma vez, útil quando tudo realmente já está pronto.
JunoColocando mudanças em staginggit add <arquivo> coloca um arquivo inteiro em staging, e git add -p <arquivo> permite colocá-lo em staging hunk por hunk quando só parte do arquivo está pronta. Esse segundo é a ferramenta certa no momento em que "metade deste arquivo está pronta" é verdade.
JunoColocando mudanças em staginggit add escreve o conteúdo atual do seu arquivo em um blob e faz o índice apontar para ele, mas não fica observando o arquivo depois disso. Edite o arquivo de novo antes de commitar e o status vai mostrá-lo como staged e modified ao mesmo tempo, porque o índice ainda guarda o blob mais antigo. Execute o add de novo para trazer a edição mais recente. Isso me custou dez minutos de confusão da primeira vez que aconteceu comigo.

Salvando um instantâneo: git commit ​

Quando a área de staging contém o que você quer, o git commit salva isso como um instantâneo permanente, com a flag -m (abreviação de "message") fornecendo a descrição que acompanha o commit:

bash
$ git commit -m "Fix temperature conversion in Celsius-to-Fahrenheit formula"
[main 4f2a1c9] Fix temperature conversion in Celsius-to-Fahrenheit formula
 1 file changed, 1 insertion(+), 1 deletion(-)

Só o src/index.js entrou, porque foi só isso que você colocou em staging. O styles.css continua editado no seu diretório de trabalho, intocado, esperando o momento em que a reestilização for concluída. O id curto, 4f2a1c9, identifica exatamente esse commit, e o capítulo Histórico mostra como ler a lista completa de commits de um projeto, como esse.

Escreva mensagens de commit no modo imperativo, como uma instrução. "Fix temperature conversion" combina com a forma como o próprio Git se refere a isso ("this commit will...") e fica limpo em ferramentas que listam commits um por linha. Para qualquer coisa mais elaborada do que uma correção de uma linha, adicione um corpo explicando por que a mudança foi necessária. O diff já mostra o que mudou, então guarde o corpo da mensagem para o raciocínio por trás da mudança:

bash
$ git commit -m "Fix temperature conversion in Celsius-to-Fahrenheit formula

The formula was applying operator precedence in the wrong order,
which rounded warm temperatures down by a degree in the UI."

Se você notar um erro de digitação ou um detalhe esquecido logo depois de commitar, o git commit --amend substitui seu commit mais recente por um novo em vez de adicionar outro por cima:

bash
$ git commit --amend -m "Fix temperature conversion in Celsius-to-Fahrenheit formula"

Só faça amend em um commit que você ainda não compartilhou em lugar nenhum. Depois que você já fez push (enviou para uma cópia compartilhada do projeto), o amend reescreve um histórico que outras pessoas já podem ter, e Desfazendo coisas mostra as formas seguras de corrigir um commit depois desse ponto.

Um commit em si é um objeto pequeno com três partes: um ponteiro para uma tree, o autor e a mensagem, e um ponteiro para seu commit pai. A tree é um instantâneo de toda a estrutura de diretórios do seu projeto naquele momento: para cada arquivo e pasta, ela registra um nome e um ponteiro para um blob (o conteúdo de um arquivo) ou para outra tree (uma subpasta). Executar git add escreve blobs e atualiza o índice um arquivo por vez; o git commit é o momento em que o Git transforma o índice atual em uma tree finalizada e a embrulha em um objeto de commit.

Dois arquivos com conteúdo idêntico apontam para exatamente o mesmo blob, mesmo estando em pastas diferentes, então um commit nunca armazena esses bytes duas vezes. Essa estrutura também torna eficiente comparar dois commits: o Git percorre as duas trees lado a lado e só abre os blobs cujos ponteiros realmente são diferentes, pulando todo arquivo que permaneceu igual.

JunoSalvando um commitgit commit -m "mensagem" salva o que estiver em staging no momento como um instantâneo permanente, com a mensagem que você fornecer. Qualquer coisa que você não colocou em staging fica de fora e continua editável. Mantenha a mensagem curta e clara sobre o que o commit faz, o seu eu do futuro vai lê-la de novo mais vezes do que você imagina.
JunoSalvando um commit Escreva mensagens de commit como uma instrução, "Fix" em vez de "Fixed", e adicione um corpo sempre que o diff sozinho deixaria alguém sem saber por que você fez a mudança. O git commit --amend substitui seu último commit em vez de empilhar um novo, o que é perfeito para corrigir um erro de digitação na mensagem, desde que você ainda não tenha feito push desse commit para lugar nenhum.
JunoSalvando um commit Um commit aponta para uma tree, e uma tree mapeia nomes para blobs e outras trees, um instantâneo completo e aninhado do projeto construído a partir do que o índice continha no momento do commit. O add escreve os blobs e o commit transforma o índice em uma tree finalizada, e conteúdo idêntico em qualquer lugar do projeto compartilha um único blob em vez de ser armazenado duas vezes. Tendo essa estrutura em mente, duas coisas deixam de parecer mágica: por que nada é duplicado, e por que comparar dois commits significa percorrer duas trees lado a lado em vez de reler cada arquivo.