Git na Prática: Commits, Branches e Merges sem Medo

[62] Git na Prática: Commits, Branches e Merges sem Medo

Git no fluxo real de trabalho: os três estados de um arquivo, o ciclo status/add/commit, leitura do histórico, branches e merge, resolução de conflitos e a diferença entre reset e revert em histórico já compartilhado.
DevOps

10 min de leitura

Antes do Git, equipes de desenvolvimento compartilhavam código por e-mail, pen drives ou pastas em servidores de rede. Cada mudança era um risco. Desfazer algo era manual e impreciso. Trabalhar em paralelo era um pesadelo de conflitos.

O Git resolveu tudo isso de forma elegante: ele rastreia cada mudança em cada arquivo, permite que múltiplas pessoas trabalhem simultaneamente no mesmo projeto e oferece um histórico completo e auditável de tudo que aconteceu. Em DevOps, o Git não é apenas uma ferramenta de desenvolvimento — é a base sobre a qual pipelines, deploys e automações são construídos. Nenhum pipeline de CI/CD existe sem um repositório Git na origem.

Este artigo cobre o que se usa no dia a dia: commits, branches e merges. Sem teoria desnecessária, direto ao fluxo real de trabalho.

Configuração Inicial

Antes de qualquer coisa, é necessário informar ao Git quem está fazendo as alterações. Essa informação aparece em cada commit:

git config --global user.name "Ricardo Matos"
git config --global user.email "ricardo@exemplo.com"

# Define o VSCode como editor padrão (opcional)
git config --global core.editor "code --wait"

# Define 'main' como nome padrão da branch principal
git config --global init.defaultBranch main

Para verificar todas as configurações:

git config --list

Iniciando um Repositório

Dentro do diretório do projeto:

mkdir meu-projeto
cd meu-projeto
git init

O comando git init cria um diretório oculto .git que armazena todo o histórico e configurações do repositório. Esse diretório não deve ser modificado manualmente.

Para clonar um repositório existente do GitHub:

git clone https://github.com/usuario/repositorio.git

# Clona para uma pasta com nome diferente
git clone https://github.com/usuario/repositorio.git meu-nome-local

O Ciclo Fundamental: Status, Add, Commit

O fluxo básico do Git envolve três estados para cada arquivo: não rastreado (novo arquivo que o Git ainda não conhece), staged (preparado para o próximo commit) e committed (salvo no histórico).

# Verifica o estado atual do repositório
git status

Cria-se um arquivo e observa-se o que o Git reporta:

echo "# Meu Projeto DevOps" > README.md
git status
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        README.md

Adiciona-se ao stage e faz-se o commit:

# Adiciona um arquivo específico
git add README.md

# Adiciona todos os arquivos modificados
git add .

# Confirma o que está no stage
git status

# Faz o commit com uma mensagem descritiva
git commit -m "feat: adiciona README inicial do projeto"

A mensagem do commit deve descrever o que foi feito e por quê — não o como. Mensagens vagas como "atualização" ou "fix" são problemáticas em projetos com múltiplos colaboradores.

Visualizando o Histórico

# Histórico completo
git log

# Formato compacto — um commit por linha
git log --oneline

# Com gráfico de branches
git log --oneline --graph --all

# Últimos 5 commits
git log --oneline -5

Para ver o que mudou em um commit específico:

git show abc1234

Para ver as diferenças entre o estado atual e o último commit:

git diff

Trabalhando com Branches

Uma branch é uma linha independente de desenvolvimento. O uso de branches permite trabalhar em uma nova funcionalidade sem afetar o código principal, testar mudanças com segurança e colaborar em paralelo com outros desenvolvedores.

# Lista todas as branches
git branch

# Cria uma nova branch
git branch feature/configurar-nginx

# Muda para a branch criada
git checkout feature/configurar-nginx

# Atalho: cria e já muda para a branch
git checkout -b feature/configurar-nginx

# Forma moderna (Git 2.23+)
git switch -c feature/configurar-nginx

Trabalhando na nova branch:

# Cria um arquivo de configuração fictício
mkdir config
echo "server { listen 80; }" > config/nginx.conf

git add config/nginx.conf
git commit -m "feat: adiciona configuração base do Nginx"

Neste momento, a branch main não tem esse arquivo. As mudanças existem apenas na branch feature/configurar-nginx.

Merge: Integrando o Trabalho

Quando o trabalho em uma branch está pronto, integra-se de volta à branch principal:

# Volta para a branch principal
git checkout main

# Integra as mudanças da feature branch
git merge feature/configurar-nginx

O Git tentará fazer o merge automaticamente. Se não houver conflitos, o resultado é um fast-forward (quando a branch principal não teve nenhum commit novo desde que a feature foi criada) ou um merge commit (quando ambas as branches avançaram).

Após o merge, a branch de feature pode ser removida:

git branch -d feature/configurar-nginx

Resolvendo Conflitos

Conflitos acontecem quando dois commits modificam a mesma linha do mesmo arquivo de formas diferentes. O Git não decide qual versão é a correta — ele marca o conflito e aguarda resolução manual.

O arquivo conflitante terá a seguinte aparência:

<<<<<<< HEAD
server { listen 80; server_name localhost; }
=======
server { listen 8080; }
>>>>>>> feature/porta-alternativa

A seção entre <<<<<<< HEAD e ======= é o conteúdo da branch atual. Entre ======= e >>>>>>> é o conteúdo da branch sendo integrada. Edita-se o arquivo manualmente, remove-se os marcadores e deixa-se o resultado desejado:

server { listen 80; server_name localhost; }

Depois:

git add config/nginx.conf
git commit -m "merge: resolve conflito de configuração de porta"

Desfazendo Coisas

Uma das maiores vantagens do Git é a capacidade de desfazer. As situações mais comuns:

# Desfaz mudanças não commitadas em um arquivo
git checkout -- arquivo.txt

# Remove um arquivo do stage (sem perder as mudanças)
git restore --staged arquivo.txt

# Desfaz o último commit, mantendo as mudanças no stage
git reset --soft HEAD~1

# Desfaz o último commit e descarta as mudanças — irreversível
git reset --hard HEAD~1

# Cria um novo commit que inverte um commit anterior (seguro para histórico compartilhado)
git revert abc1234

A diferença entre reset --hard e revert é fundamental: o reset reescreve o histórico — perigoso quando o código já foi enviado para um repositório remoto. O revert cria um novo commit de desfazimento, preservando o histórico intacto.

Conectando ao GitHub

# Adiciona o repositório remoto
git remote add origin https://github.com/usuario/meu-projeto.git

# Verifica os remotos configurados
git remote -v

# Envia a branch main para o repositório remoto
git push -u origin main

# Nos envios seguintes, basta
git push

# Baixa e integra as mudanças do remoto
git pull

Referências para Aprofundamento

Documentação oficial

Referência rápida

  • Git Cheat Sheet — GitHub Education — PDF com os comandos mais usados do Git. Útil nos primeiros meses até que os comandos se tornem automáticos.
  • Oh Shit, Git! — Guia direto em português para as situações em que algo deu errado e é preciso desfazer. Prático e bem-humorado.

Prática interativa

  • Learn Git Branching — Simulador visual e interativo de branches e merges no navegador. É o melhor recurso disponível para desenvolver intuição sobre o modelo de branches do Git.
  • GitHub Skills — Cursos práticos do próprio GitHub ensinando Git e GitHub com repositórios reais.

Exercícios

Exercício 1

Quais são os três estados de um arquivo no Git, e qual comando move o arquivo de um estado para o outro? Por que o estado intermediário existe?

Ver resposta

✓ Resposta: Não rastreado (o Git ainda não conhece o arquivo), staged (preparado para o próximo commit) e committed (gravado no histórico). O git add leva do primeiro ao segundo; o git commit leva do segundo ao terceiro. O estado intermediário existe para que você escolha o que entra em cada commit: é ele que permite separar, numa mesma sessão de trabalho, a correção de um bug da reorganização de indentação que você fez no caminho. Sem o stage, todo commit seria "tudo que eu mexi desde a última vez".

Exercício 2

Qual a diferença entre um merge fast-forward e um merge commit? O que determina qual dos dois o Git vai fazer?

Ver resposta

✓ Resposta: O que determina é se a branch principal avançou desde que a feature nasceu. Se não avançou, o Git faz fast-forward: apenas move o ponteiro da main até o último commit da feature, sem criar commit algum, e o histórico continua linear. Se as duas avançaram em paralelo, não existe ponteiro a simplesmente adiantar — o Git cria um merge commit, que tem dois pais e une as duas linhas. O primeiro produz histórico mais limpo; o segundo registra de forma explícita que houve trabalho paralelo e quando ele foi integrado.

Exercício 3

No arquivo em conflito abaixo, o que representa cada seção? E o que não pode, de jeito nenhum, sobrar no arquivo final?

<<<<<<< HEAD
server { listen 80; server_name localhost; }
=======
server { listen 8080; }
>>>>>>> feature/porta-alternativa
Ver resposta

✓ Resposta: Entre <<<<<<< HEAD e ======= está o conteúdo da branch em que você está. Entre ======= e >>>>>>> feature/porta-alternativa está o conteúdo da branch que está sendo integrada. O Git não escolhe entre as duas: ele marca a divergência e devolve a decisão para você. O que não pode sobrar são os próprios marcadores — <<<<<<<, ======= e >>>>>>>. Esquecê-los é um erro clássico e silencioso no Git, que aceita o commit sem reclamar; a falha só aparece quando o Nginx recusa o arquivo de configuração ou o código quebra em execução.

Exercício 4

Por que a diferença entre git reset --hard e git revert é decisiva quando o commit já foi enviado para o repositório remoto?

Ver resposta

✓ Resposta: reset --hard HEAD~1 remove o commit da história e descarta as mudanças — ele reescreve o passado. Se aquele commit já estava no remoto, seu histórico passa a divergir do de todo mundo, e a única forma de "alinhar" é um force-push, que apaga trabalho de quem já havia baixado. revert não apaga nada: cria um novo commit que desfaz o efeito do anterior, deixando registrado que a mudança existiu e foi revertida. A regra prática é essa: reset para o que ainda é só seu, revert para o que já é de todos.

Exercício 5

Os três comandos abaixo desfazem coisas diferentes. Explique cada um — e diga qual deles é destrutivo.

git reset --soft HEAD~1
git restore --staged arquivo.txt
git checkout -- arquivo.txt
Ver resposta

✓ Resposta: reset --soft HEAD~1 desfaz o commit mas mantém as mudanças no stage — serve para corrigir a mensagem ou acrescentar algo que ficou de fora. restore --staged arquivo.txt tira o arquivo do stage sem tocar no conteúdo: ele volta a ser "modificado, não preparado". checkout -- arquivo.txt descarta as edições não commitadas e devolve o arquivo ao estado do último commit — este é o destrutivo. O que você digitou e não levou nem ao stage nem ao commit nunca esteve sob controle do Git, e não há de onde recuperá-lo.

Exercício 6

Por que mensagens de commit como "atualização" ou "fix" são problemáticas? O que uma boa mensagem precisa responder — e por que isso pesa ainda mais em DevOps?

Ver resposta

✓ Resposta: Porque o histórico é lido no futuro, por alguém que não estava lá — frequentemente você mesmo, meses depois. A mensagem precisa responder o que mudou e por quê; o como já está no diff, e repeti-lo é desperdício. O custo aparece no pior momento: algo quebra em produção, alguém roda git log --oneline procurando o commit responsável e encontra cinquenta linhas de "fix" e "ajustes" — o histórico deixou de ser ferramenta de investigação. Em DevOps o peso é maior porque o commit é a origem do pipeline: a mensagem costuma alimentar changelog, versionamento e nota de release.

Comentários

Mais em DevOps

GitLab Self-Hosted: Soberania Total sobre Código e Pipelines
GitLab Self-Hosted: Soberania Total sobre Código e Pipelines

Quando o SaaS não é opção — setores regulados, residência de dados, custo por…

GitHub Actions: Sua Primeira Automação de CI/CD
GitHub Actions: Sua Primeira Automação de CI/CD

GitHub Actions do zero: os conceitos de workflow, event, job e step, a…

RDS, ElastiCache e Estratégias de Dados na AWS
RDS, ElastiCache e Estratégias de Dados na AWS

A camada mais crítica e mais difícil de recuperar quando algo dá errado: RDS…