Operações Avançadas de Commit no Git

GitBeginner
Pratique Agora

Introdução

Bem-vindo de volta, aventureiro do Git! Você já deu seus primeiros passos no mundo do controle de versão e agora é hora de elevar o nível de suas habilidades. Neste laboratório, vamos explorar algumas das operações de commit mais avançadas do Git. Essas técnicas darão a você ainda mais controle sobre o histórico do seu projeto, permitindo corrigir erros, reorganizar seu trabalho e colaborar de forma mais eficaz.

Pense neste laboratório como uma atualização da sua máquina do tempo. Você não apenas poderá viajar pelo tempo, mas agora aprenderá como alterar a própria linha temporal! Não se preocupe se isso parecer intimidador — guiaremos você por cada etapa, explicando não apenas como realizar essas operações, mas por que elas são úteis em cenários do mundo real.

Ao final deste laboratório, você será capaz de emendar commits, reverter alterações, selecionar commits específicos (cherry-pick), realizar rebase interativo e agrupar (squash) commits. Estas são ferramentas poderosas que desenvolvedores profissionais usam todos os dias para manter históricos de projetos limpos e organizados. Vamos mergulhar e levar suas habilidades de Git para o próximo nível!

Configurando seu Espaço de Trabalho

Antes de iniciarmos nossas operações avançadas, vamos configurar um novo espaço de trabalho. Criaremos um novo diretório e inicializaremos um repositório Git nele. Isso nos dará um espaço limpo para experimentar sem afetar nenhum de seus projetos existentes.

Abra seu terminal e digite estes comandos, pressionando Enter após cada linha:

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

Vamos detalhar o que esses comandos fazem passo a passo:

  1. cd ~/project: cd significa "change directory" (mudar diretório). ~/project é um caminho que geralmente aponta para uma pasta "project" dentro do seu diretório pessoal (~). Este comando navega seu terminal para dentro desse diretório. Se o diretório "project" não existir, você pode encontrar um erro. Se for o caso, crie-o primeiro usando mkdir ~/project e tente cd ~/project novamente.
  2. mkdir git-advanced-lab: mkdir significa "make directory" (criar diretório). Este comando cria uma nova pasta chamada "git-advanced-lab" dentro do seu diretório atual. Este novo diretório será a raiz do nosso repositório Git para este laboratório.
  3. cd git-advanced-lab: Este comando altera seu diretório atual novamente, desta vez movendo você para dentro do diretório "git-advanced-lab" recém-criado. Agora, quaisquer comandos que você executar serão executados dentro deste diretório.
  4. git init: Este é o comando crucial do Git para inicializar um repositório. O git init configura todas as estruturas necessárias do Git dentro do diretório atual, transformando-o em um repositório Git. Você verá uma pasta oculta chamada .git criada dentro de git-advanced-lab. Esta pasta .git é o coração do seu repositório e armazena todo o histórico de versões e configurações.

Agora que temos um repositório Git inicializado, vamos criar um arquivo simples para trabalhar e fazer nosso commit inicial:

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

Aqui está o que está acontecendo nestes comandos:

  1. echo "Hello, Advanced Git" > hello.txt: echo é um comando que exibe texto. > é um operador de redirecionamento. Ele pega a saída do comando echo e a redireciona para um arquivo chamado "hello.txt". Se o arquivo não existir, ele será criado; se existir, será sobrescrito.
  2. git add hello.txt: Antes que o Git possa rastrear alterações em um arquivo, você precisa dizer explicitamente para ele começar a rastreá-lo. Este comando coloca o arquivo na "staging area" (área de preparação), preparando-o para ser incluído no próximo commit.
  3. git commit -m "Initial commit": O git commit pega todas as alterações que estão atualmente na área de preparação e as salva como um novo registro no histórico do repositório. O -m adiciona uma mensagem de commit, que deve ser uma descrição curta das alterações.

Excelente! Agora temos um repositório com um commit. Vamos verificar o status para confirmar se tudo está configurado corretamente:

git status

Após executar git status, você deverá ver uma mensagem no terminal parecida com esta:

On branch master
nothing to commit, working tree clean

Esta mensagem indica que sua árvore de trabalho está limpa. "Working tree" refere-se ao diretório onde os arquivos do seu projeto estão localizados. "Clean" significa que não há alterações no seu diretório de trabalho que ainda não tenham sido commitadas. Isso confirma que estamos prontos para começar nossas operações avançadas com o pé direito!

Emendando seu Último Commit

Imagine que você acabou de fazer um commit, mas percebeu que esqueceu de incluir uma alteração em um arquivo ou cometeu um pequeno erro de digitação na mensagem do commit. Em vez de criar um commit totalmente novo para uma correção tão pequena, o Git permite que você corrija o commit mais recente usando a opção --amend. É como voltar ligeiramente no tempo para ajustar sua última ação.

Vamos testar. Primeiro, vamos modificar nosso arquivo hello.txt adicionando outra linha:

echo "This is an important file." >> hello.txt

Este comando anexa uma nova linha ao nosso arquivo. Vamos entender o operador >>:

  • > sobrescreveria todo o arquivo com o novo conteúdo.
  • >> anexa o novo conteúdo ao final do arquivo existente, preservando o conteúdo original.

Agora, digamos que percebemos que deveríamos ter incluído esta nota de "arquivo importante" no próprio commit inicial. Podemos emendar nosso commit anterior para incluir esta alteração e atualizar a mensagem do commit.

git add hello.txt
git commit --amend -m "Initial commit with important note"

Entenda o que aconteceu:

  1. git add hello.txt: Como modificamos o arquivo, precisamos prepará-lo novamente.
  2. git commit --amend -m "Initial commit with important note":
    • A flag --amend diz ao Git que, em vez de criar um novo commit, queremos modificar o último.
    • O -m fornece a nova mensagem. Se você omitir o -m, o Git abrirá seu editor de texto padrão para você editar a mensagem original.

O que acabou de acontecer?

Em vez de criar um novo commit no seu histórico, o Git efetivamente substituiu o "Initial commit" anterior por uma versão nova e aprimorada. O commit antigo desapareceu e o novo incorpora as alterações que preparamos e a mensagem atualizada.

Vamos verificar isso consultando o log de commits:

git log --oneline

Você deve ver apenas um commit na saída, mas a mensagem agora deve ser "Initial commit with important note".

<commit_hash> Initial commit with important note

Pressione q para sair da visualização do log, caso ela não feche automaticamente.

Considerações Importantes ao Emendar:

  • Apenas Commits Locais: Você deve emendar apenas commits que ainda não foram enviados (push) para um repositório remoto compartilhado (como GitHub ou GitLab). Emendar um commit que já foi compartilhado reescreve um histórico no qual outros podem estar baseando seu trabalho, o que pode causar confusão e conflitos graves.
  • Limpeza de Histórico Local: A emenda é útil principalmente para limpar seu histórico de commits local antes de compartilhar seu trabalho.

Revertendo um Commit

Às vezes, você faz um commit e depois percebe que ele introduziu um erro ou simplesmente deseja desfazer aquelas alterações. Você pode pensar em usar o git reset para voltar no tempo e remover o commit. No entanto, o git reset pode ser destrutivo, especialmente se você já enviou suas alterações. Uma maneira mais segura e colaborativa de desfazer mudanças é usando o git revert.

O git revert cria um novo commit que desfaz as alterações introduzidas por um commit específico. Ele não apaga o commit original do histórico; em vez disso, ele adiciona um novo registro que inverte os efeitos do commit indesejado. Isso preserva o histórico e é muito mais seguro para repositórios compartilhados.

Vamos criar um novo commit que iremos reverter em seguida:

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

Agora, decidimos que adicionar essa linha foi um erro. Podemos reverter o último commit usando:

git revert HEAD

Entendendo o comando:

  • git revert: O comando para desfazer alterações criando um novo commit inverso.
  • HEAD: No Git, HEAD é um ponteiro para o commit atual do seu branch. Na maioria dos casos, refere-se ao commit mais recente.

Quando você executa git revert HEAD, o Git faz o seguinte:

  1. Analisa as alterações: Ele olha o que foi feito no commit HEAD.
  2. Cria alterações inversas: Ele descobre como desfazer aquilo (neste caso, removendo a linha que acabamos de adicionar).
  3. Cria um novo commit: Ele gera automaticamente um novo commit com essas alterações inversas.
  4. Abre o Editor de Texto (Opcional): O Git geralmente abrirá seu editor padrão com uma mensagem pré-preenchida como "Revert 'Add line to be reverted'". Você pode aceitar ou modificar essa mensagem.

Lidando com o Editor de Texto durante o git revert:

Se o seu editor abrir (como o Vim) e você não estiver familiarizado:

  • No Vim: Pressione Esc, digite :wq e pressione Enter para salvar e sair.
  • No Nano: Pressione Ctrl + X, depois Y e Enter.

Vamos verificar nosso histórico agora:

git log --oneline

Você deve ver três commits no seu log, em ordem cronológica inversa (o mais novo primeiro):

  1. Um commit começando com "Revert" (o commit de reversão que acabamos de criar).
  2. "Add line to be reverted" (o commit que revertemos).
  3. "Initial commit with important note" (nosso commit original emendado).

Se você olhar o conteúdo de hello.txt, a linha "This line will be reverted" terá sumido.

Por que o git revert é preferível em repositórios compartilhados?

  • Histórico Não-Destrutivo: Ele não reescreve o passado. Ele adiciona um novo capítulo contando como o erro foi corrigido, o que é seguro para todos os colaboradores.
  • Evita Force Push: O git reset geralmente exige um "force push" (git push --force), que pode sobrescrever o trabalho de colegas. O revert evita esse problema.
  • Trilha de Auditoria Clara: Ele fornece um registro claro de quando e por que as alterações foram desfeitas.

Cherry-picking de Commits

O "Cherry-picking" no Git é como colher uma cereja de uma árvore – você seleciona um commit específico de um branch e o aplica ao seu branch atual. Isso é útil quando você deseja incorporar uma funcionalidade ou correção específica de outro branch sem trazer todo o histórico dele.

Vamos simular um cenário onde temos uma funcionalidade desenvolvida em um branch separado e queremos trazer apenas essa funcionalidade para o nosso branch principal.

Primeiro, vamos criar um novo branch e fazer um commit nele:

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

Entendendo os comandos:

  1. git checkout -b feature-branch: Cria um novo branch chamado "feature-branch" e muda para ele imediatamente.
  2. Criamos um arquivo e fazemos o commit normalmente neste novo branch.

Agora, imagine que terminamos essa funcionalidade e queremos integrá-la ao nosso branch principal (master). Primeiro, voltamos para o master:

git checkout master

Agora queremos aplicar o commit "Add new feature" do "feature-branch" no nosso branch master. Usamos o git cherry-pick:

git cherry-pick feature-branch

Neste caso simples, usar o nome do branch feature-branch fará o Git buscar o último commit desse branch. Se você quisesse um commit específico do meio do histórico, usaria o hash (identificador) do commit em vez do nome do branch.

Ao executar o comando, o Git:

  1. Localiza o commit.
  2. Tenta aplicar as mesmas alterações no seu branch atual.
  3. Cria um novo commit no master com essas alterações.

Verifique o log no branch master:

git log --oneline

Você verá o novo commit "Add new feature". Note que ele terá um hash diferente do commit original no feature-branch, pois é um novo registro no histórico do master.

Considerações Importantes sobre Cherry-picking:

  • Novo Commit: Ele sempre cria um novo commit. Não move nem remove o original.
  • Conflitos Potenciais: Se o código nos dois branches for muito diferente, podem ocorrer conflitos que você precisará resolver manualmente.
  • Use com Moderação: Embora poderoso, o uso excessivo de cherry-pick pode tornar o histórico difícil de seguir. É mais eficaz para correções isoladas (hotfixes) ou para trazer mudanças específicas quando um merge completo não é desejado.

Rebase Interativo

O rebase interativo é uma das funcionalidades mais poderosas e complexas do Git. Ele permite que você reescreva seu histórico de commits de formas muito flexíveis. Você pode reordenar commits, combinar (squash) commits, editar mensagens ou até remover commits inteiros. É como ter um editor de texto para o seu histórico.

Aviso: O rebase interativo reescreve o histórico. Nunca faça rebase de commits que já foram enviados para um repositório compartilhado, a menos que você entenda perfeitamente as implicações e tenha coordenado com sua equipe.

Vamos criar uma série de commits para ver o rebase interativo em ação:

echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"

A flag -am no git commit é um atalho que prepara todos os arquivos modificados e faz o commit em um único passo.

Agora, vamos limpar esses três commits. Talvez a "First change" e a "Second change" devessem ser um único commit, e queremos melhorar a mensagem da "Third change".

Inicie a sessão de rebase interativo:

git rebase -i HEAD~3
  • HEAD~3: Seleciona os últimos três commits a partir do atual.

O Git abrirá seu editor de texto com uma lista dos commits. Você verá algo assim:

pick 63c95db First change
pick 68e7909 Second change
pick 5371424 Third change

## Commands:
## p, pick <commit> = use commit
## r, reword <commit> = use commit, but edit the commit message
## s, squash <commit> = use commit, but meld into previous commit
## ...

Nosso Plano de Rebase:

  1. Agrupar (squash) "Second change" em "First change".
  2. Renomear (reword) "Third change" para uma mensagem melhor.

Modifique o arquivo no editor para ficar assim:

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

Não altere os hashes dos commits no arquivo.

Instruções para o Vim (se for seu editor):

  1. Pressione i para entrar no modo de inserção.
  2. Altere as palavras "pick" para "squash" e "reword".
  3. Pressione Esc.
  4. Digite :wq e pressione Enter.

O que acontece em seguida:

  1. Squashing: O Git combinará as alterações. Ele abrirá o editor novamente para você criar uma mensagem única para os commits combinados. Você pode escrever algo como: Combined first and second changes: Initial setup of hello.txt.
  2. Rewording: O Git abrirá o editor mais uma vez para você alterar a mensagem do terceiro commit. Mude para: Improved third change: Added a more descriptive line to hello.txt.

Verificando o Histórico:

git log --oneline

Você notará que agora tem menos commits, com mensagens mais limpas e descritivas. Os hashes mudaram porque você criou uma nova versão da história.

Lembrete: Se algo der errado durante o rebase, você pode cancelar tudo e voltar ao estado original com o comando:

git rebase --abort

Rebase a partir da Raiz

No Passo 5, usamos HEAD~3. Mas o rebase interativo pode ir além. O comando git rebase -i --root permite que você rebaseie todos os commits do seu repositório, desde o primeiríssimo (raiz). Isso dá o controle total para remodelar todo o histórico do projeto.

Cuidado Extremo: O rebase da raiz reescreve absolutamente tudo. Em projetos reais e compartilhados, isso é quase sempre uma má ideia, pois mudará os hashes de cada commit desde o início, quebrando o repositório para todos os outros colaboradores.

Como estamos em um ambiente isolado, vamos experimentar:

git rebase -i --root

O editor abrirá listando todos os commits que fizemos desde o início do laboratório.

Nosso Plano para o Rebase da Raiz:

  1. Alterar a mensagem do primeiríssimo commit.
  2. Agrupar o commit de "Revert" no commit que introduziu o erro original. (Isso é um exemplo para mostrar como podemos "limpar" erros do passado como se eles nunca tivessem acontecido).

Modifique as linhas conforme desejado (usando reword para o primeiro e squash para outros que queira combinar). Siga o mesmo processo de salvar e fechar o editor para cada etapa que o Git solicitar.

Ao final, verifique seu log:

git log --oneline

Você verá uma história linear e limpa, como se cada passo tivesse sido perfeito desde o início. Esta é a "mágica" do rebase: permitir que você apresente um histórico de trabalho organizado e lógico para seus colegas, escondendo as idas e vindas e os pequenos erros do processo de desenvolvimento.

Resumo

Parabéns, mestre do Git! Você acaba de elevar suas habilidades de controle de versão a um novo patamar. Vamos recapitular as técnicas poderosas que você aprendeu neste laboratório:

  1. Emendar Commits (Amend): Agora você sabe como corrigir pequenos erros no commit mais recente sem poluir o histórico.
  2. Reverter Commits (Revert): Você aprendeu a desfazer alterações de forma segura e colaborativa, criando um registro histórico da correção.
  3. Cherry-picking: Você pode selecionar e aplicar alterações específicas de um branch para outro com precisão.
  4. Rebase Interativo: Você dominou a arte de reescrever o histórico local, permitindo organizar, combinar e renomear commits para uma apresentação profissional.
  5. Rebase da Raiz: Você explorou o controle total sobre a narrativa do seu repositório, desde o seu primeiro dia.

Essas operações avançadas são ferramentas essenciais no arsenal de um desenvolvedor. Elas permitem manter um histórico de commits limpo e organizado, o que é crucial para a revisão de código, depuração e para que outros desenvolvedores entendam a evolução do projeto. Um histórico bem mantido não é apenas estética; é uma documentação valiosa do seu trabalho.