DevOps

Git

Guia completo de Git — configuração, staging, commits convencionais, branches, rebase, stash, bisect, reflog, hooks e fluxos de trabalho

Git é um sistema de controle de versão distribuído. Cada desenvolvedor tem uma cópia completa do repositório, incluindo todo o histórico. Commits são snapshots, não diffs — o Git calcula diffs sob demanda.


Configuração Inicial

# Identidade — obrigatório antes do primeiro commit
git config --global user.name "Rafael Marques"
git config --global user.email "rafael@empresa.com"

# Editor padrão
git config --global core.editor "nvim"
git config --global core.editor "code --wait"   # VS Code

# Branch padrão
git config --global init.defaultBranch main

# Comportamento de push padrão
git config --global push.default current        # push para branch de mesmo nome

# Rebase por padrão no pull
git config --global pull.rebase true

# Aliases úteis
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage "restore --staged"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.aliases "config --get-regexp alias"

# Cores no output
git config --global color.ui auto

# Auto-correção de comandos com typo (após 1s)
git config --global help.autocorrect 10

# Mostrar configuração
git config --list
git config --list --show-origin   # mostra qual arquivo define cada config

Staging Area — Entendendo o Index

A staging area (index) é um espaço intermediário entre o working directory e o repositório. Permite compor commits cirurgicamente.

# Visualizar estado
git status                          # estado resumido
git status -s                       # short format: ?? = untracked, M = modified, A = staged

# Adicionar ao stage
git add arquivo.ts                  # arquivo específico
git add src/                        # diretório inteiro
git add .                           # tudo no diretório atual
git add -p                          # interativo: escolhe hunks (recomendado!)
git add -p arquivo.ts               # interativo para arquivo específico
# No modo -p: y = adiciona hunk, n = ignora, s = divide, e = edita manualmente

# Ver diferenças
git diff                            # working directory vs staged
git diff --staged                   # staged vs último commit (o que vai no commit)
git diff HEAD                       # working directory vs último commit (tudo)
git diff branch-a..branch-b         # diff entre branches
git diff HEAD~3 -- arquivo.ts       # diff de arquivo há 3 commits atrás

# Remover do stage (sem perder mudanças)
git restore --staged arquivo.ts     # remove do stage, mantém no working directory
git restore --staged .              # remove tudo do stage

# Descartar mudanças no working directory (irreversível!)
git restore arquivo.ts              # descarta mudanças não-staged
git restore .                       # descarta tudo (cuidado!)

# Ver o que está staged em detalhe
git diff --staged --stat            # estatística de mudanças staged
git diff --cached                   # mesmo que --staged (alias)

Commits — Conventional Commits

Conventional Commits é uma especificação para mensagens de commit que permite automação de changelogs e versionamento semântico.

Formato:
<tipo>[escopo opcional]: <descrição>

[corpo opcional]

[rodapé opcional]
# Tipos de commit
git commit -m "feat: adicionar endpoint de cancelamento de pedido"
git commit -m "fix: corrigir cálculo de desconto para clientes VIP"
git commit -m "docs: atualizar README com instruções de setup"
git commit -m "refactor: extrair PricingService de OrderService"
git commit -m "test: adicionar testes para CartService"
git commit -m "chore: atualizar dependências para versões LTS"
git commit -m "perf: otimizar query de listagem de pedidos com índice"
git commit -m "ci: configurar pipeline de deploy para produção"
git commit -m "style: formatar código com Prettier"
git commit -m "build: migrar de Maven para Gradle"

# Com escopo (módulo ou área afetada)
git commit -m "feat(payment): integrar Stripe como gateway de pagamento"
git commit -m "fix(cart): resolver problema de quantidade negativa"
git commit -m "refactor(auth): simplificar validação de token JWT"

# Breaking change — MAJOR version bump
git commit -m "feat!: alterar formato de resposta do endpoint /api/orders"
# ou via rodapé:
git commit -m "feat(api): alterar paginação para cursor-based

BREAKING CHANGE: o campo 'page' foi removido. Use 'cursor' para paginação."

# Commit com corpo detalhado
git commit -m "fix(order): prevenir criação de pedido duplicado

O endpoint POST /api/orders não verificava pedidos em andamento
para o mesmo cliente, permitindo duplicatas em condições de race condition.

Adiciona verificação de pedido pendente antes de criar um novo e
implementa idempotency key via header X-Idempotency-Key.

Closes #123"

# Amend — modifica o ÚLTIMO commit (apenas antes de push!)
git commit --amend                          # edita mensagem + staged
git commit --amend -m "nova mensagem"       # apenas mensagem
git commit --amend --no-edit               # adiciona staged sem mudar mensagem

Branches — Criação, Merge e Estratégias

# Criação e navegação
git switch -c feature/checkout-flow         # cria e muda para a branch
git switch main                             # volta para main
git switch -                                # volta para branch anterior
git branch                                  # lista branches locais
git branch -a                               # lista locais + remotas
git branch -vv                              # com informação de tracking e ahead/behind
git branch -d feature/checkout-flow        # deleta branch (seguro)
git branch -D feature/checkout-flow        # força delete mesmo sem merge
git branch -m feature/old feature/new      # renomeia branch

# Merge — Fast-Forward vs 3-Way
# Fast-Forward: quando main não avançou desde a criação da branch
# Resultado: histórico linear, sem merge commit
git merge feature/my-feature               # FF quando possível

# Forçar merge commit mesmo em FF (mantém histórico de branch)
git merge --no-ff feature/my-feature -m "feat: adicionar checkout flow"

# 3-Way merge: main avançou, Git cria merge commit
git merge feature/my-feature
# Se houver conflitos:
git status                                  # ver arquivos em conflito
# Editar arquivos com conflito manualmente
git add arquivo-conflitado.ts              # marcar como resolvido
git merge --continue                       # finalizar merge
git merge --abort                          # cancelar e voltar ao estado anterior

# Rebase vs Merge — quando usar cada
# REBASE: histórico linear, ideal para feature branches antes de abrir PR
#   - Reescreve histórico — nunca em branches compartilhadas/públicas
# MERGE: preserva histórico real, ideal para integrar main em branches longas
#   - Seguro em qualquer branch

Git Flow vs Trunk-Based Development

GIT FLOW — adequado para releases programadas, versionamento semântico

├── main         → produção, protegida, tags de release
├── develop      → integração, base para feature branches
├── feature/*    → uma por funcionalidade, saem de develop
├── release/*    → preparação de release (bug fixes + versão)
└── hotfix/*     → correções urgentes em produção, mergem em main E develop

Vantagens: processo claro, suporte a múltiplas versões em paralelo
Desvantagens: complexo, branches de longa duração, merge hell

TRUNK-BASED DEVELOPMENT — adequado para CD, times ágeis, microserviços

├── main         → sempre deployável, integração contínua
└── feature/*    → curtas (1-2 dias máx), mergeadas com frequência

Vantagens: integração contínua real, menos conflitos, CD natural
Desvantagens: requer feature flags para features incompletas, disciplina maior

Na prática: a maioria dos times usa algo no meio:
- main (protegida) + feature branches curtas + PRs obrigatórios

Rebase — Interativo e Avançado

# Rebase simples — atualiza a branch com base na main
git rebase main                             # rebases feature branch em main

# Rebase interativo — editar, reordenar, combinar commits
git rebase -i HEAD~3                        # editar últimos 3 commits
git rebase -i origin/main                  # editar desde que divergiu de main

# No editor (commits aparecem do mais antigo para o mais recente):
# pick abc1234 feat: adicionar CartService
# pick def5678 fix: corrigir bug no CartService
# pick ghi9012 test: adicionar testes

# Comandos disponíveis:
# pick    → mantém o commit como está
# reword  → mantém o commit, edita a mensagem
# edit    → para no commit para fazer alterações
# squash  → combina com o commit anterior (mantém todas as mensagens)
# fixup   → combina com o commit anterior (descarta a mensagem)
# drop    → remove o commit completamente
# reorder → mova as linhas para reordenar commits

# Squash de commits — combinar vários commits em um
# Antes:
# abc1234 feat: início do CartService
# def5678 wip: continuando CartService
# ghi9012 fix: corrigir teste
# jkl0123 refactor: limpar código

# Depois do squash:
# abc1234 feat: implementar CartService com testes

# Rebase --onto — mover branch para outra base
git rebase --onto main feature-base feature-sub
# Pega commits exclusivos de feature-sub (em relação a feature-base)
# e os aplica em cima de main

# Resolver conflito durante rebase
git status                # ver conflitos
# Editar arquivos com conflito
git add arquivo.ts        # marcar como resolvido
git rebase --continue     # continuar
git rebase --abort        # cancelar e voltar ao ponto inicial

# REGRA DE OURO: nunca rebase em branches remotas compartilhadas
# Rebase reescreve histórico — force push é perigoso e antisocial

Cherry-Pick

# Aplica um commit específico na branch atual
git cherry-pick abc1234             # aplica o commit abc1234
git cherry-pick abc1234..def5678    # aplica intervalo de commits
git cherry-pick abc1234 def5678 ghi9012  # múltiplos commits específicos

# Cherry-pick sem commit automático (permite editar antes)
git cherry-pick abc1234 --no-commit
# Edite o que precisar, então:
git commit -m "feat: adaptado de abc1234"

# Implicações:
# Cherry-pick DUPLICA o commit — cria um novo commit com o mesmo conteúdo
# O commit original e o cherry-picked são objetos diferentes (SHA diferente)
# Use com moderação — pode complicar o histórico
# Cenário ideal: copiar hotfix para uma release branch

Reset vs Revert vs Checkout

# RESET — move o ponteiro HEAD (modifica histórico!)
git reset --soft HEAD~1    # desfaz commit, mantém mudanças STAGED
                            # como se você nunca tivesse feito o commit
git reset --mixed HEAD~1   # desfaz commit, mantém mudanças no working dir (padrão)
git reset --hard HEAD~1    # desfaz commit E descarta mudanças (irreversível!)

# QUANDO USAR RESET:
# --soft:  "quero refazer o commit com um nome melhor" (antes de push)
# --mixed: "quero dividir este commit em dois" (antes de push)
# --hard:  "quero descartar completamente o que fiz" (CUIDADO!)
# NUNCA reset em commits já publicados (shared history)

# REVERT — cria um novo commit que desfaz outro (seguro, não modifica histórico)
git revert abc1234          # cria commit que desfaz abc1234
git revert HEAD~3..HEAD     # reverte últimos 3 commits
git revert HEAD~3..HEAD --no-commit  # prepara reverts sem commitar (para commitar junto)

# QUANDO USAR REVERT:
# Para desfazer commits já publicados/compartilhados
# Em branches de produção — registro auditável de desfazer mudança

# CHECKOUT (restore) — restaura arquivos ou muda de branch
git checkout -- arquivo.ts          # descarta mudanças no arquivo (old syntax)
git restore arquivo.ts              # mesmo que acima (new syntax, preferida)
git restore --source abc1234 arq.ts # restaura arquivo para versão de um commit

# Comparativo:
# reset:    afeta HEAD + index + working dir (dependendo da flag)
# revert:   cria novo commit para desfazer
# restore:  afeta apenas working dir e/ou index, não move HEAD

Stash — Salvar Trabalho em Andamento

# Salvar
git stash                              # salva working dir + index
git stash push -m "wip: feature X"    # com mensagem descritiva
git stash push --include-untracked    # inclui arquivos não-rastreados
git stash push --all                  # inclui arquivos ignorados também
git stash push -p                     # interativo: escolhe o que guardar

# Listar e inspecionar
git stash list                         # lista todos os stashes
git stash show                         # resumo do último stash
git stash show -p                      # diff completo do último stash
git stash show stash@{2}               # stash específico

# Aplicar
git stash pop                          # aplica e remove o último stash
git stash apply                        # aplica mas MANTÉM no stash
git stash apply stash@{2}             # aplica stash específico

# Remover
git stash drop stash@{0}              # remove stash específico
git stash clear                        # remove todos os stashes

# Criar branch a partir de stash
git stash branch feature/nova-branch stash@{0}
# Útil quando o stash não aplica mais limpo na branch atual

Remote — Fetch, Pull, Push e Upstream

# Gerenciar remotes
git remote -v                          # listar remotes
git remote add origin git@github.com:usuario/repo.git
git remote add upstream git@github.com:original/repo.git  # fork workflow
git remote remove origin
git remote rename origin backup

# Fetch — baixa mudanças SEM fazer merge/rebase
git fetch origin                       # fetch de todos os branches do origin
git fetch --all                        # fetch de todos os remotes
git fetch origin main                  # apenas o branch main

# Pull — fetch + merge (ou rebase se configurado)
git pull                               # pull do remote tracking
git pull --rebase                      # pull + rebase (histórico mais limpo)
git pull origin main                   # pull de branch específico

# Push
git push origin main                   # push da branch main
git push -u origin feature/my-feature  # push + configura upstream tracking
git push --force-with-lease            # ✅ force push SEGURO (falha se houver commits remotos que você não tem)
git push --force                       # ❌ PERIGOSO — sobrescreve sem verificar
git push origin --delete feature/old  # deleta branch remota
git push origin :feature/old          # mesmo que acima (sintaxe alternativa)

# Upstream tracking
git branch --set-upstream-to=origin/main main  # configura tracking manualmente
git branch -u origin/main              # idem, forma curta

# Ver status de ahead/behind
git fetch origin
git status                             # "Your branch is 2 commits ahead of 'origin/main'"
git log origin/main..main             # commits locais não publicados
git log main..origin/main             # commits remotos não integrados

Tags

# LIGHTWEIGHT TAG — apenas um ponteiro para um commit
git tag v1.0.0
git tag v1.0.0 abc1234   # tag em commit específico

# ANNOTATED TAG — objeto completo com mensagem, data, autor
# Preferida para releases — tem metadados completos
git tag -a v1.0.0 -m "Release 1.0.0 — checkout flow e novo sistema de pagamento"
git tag -a v1.0.0 abc1234 -m "Release 1.0.0"

# Listar
git tag                                # todas as tags
git tag -l "v1.*"                      # tags que combinam com padrão
git show v1.0.0                        # detalhes da tag

# Push de tags
git push origin v1.0.0                # push de tag específica
git push origin --tags                 # push de todas as tags
git push --follow-tags                 # push do commit + tags anotadas associadas

# Deletar
git tag -d v1.0.0                      # deleta tag local
git push origin --delete v1.0.0       # deleta tag remota
git push origin :refs/tags/v1.0.0     # mesmo que acima

Bisect — Encontrando o Commit que Introduziu um Bug

# Início do bisect
git bisect start

# Marcar commits
git bisect bad                         # commit atual está bugado
git bisect good v1.0.0                # v1.0.0 estava OK

# Git vai fazer checkout de um commit no meio
# Teste o comportamento, então:
git bisect bad                         # este commit ainda tem o bug
git bisect good                        # este commit está OK

# Git continua o binário search — em ~10 iterações em 1000 commits
# Ao final, Git mostra o commit exato que introduziu o bug

# Automatizar — com script de teste
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect run ./test.sh               # executa script automaticamente
# 0 = OK, 1+ = bug presente

# Sair do bisect
git bisect reset                       # volta ao HEAD original
git bisect log                         # histórico das marcações

Reflog — Recuperar Commits “Perdidos”

O reflog é o histórico de todos os movimentos do HEAD — incluindo commits “descartados” com reset —hard, branches deletadas, etc.

# Ver reflog
git reflog                             # histórico do HEAD
git reflog show feature/branch        # reflog de branch específica
git reflog --date=relative             # com timestamps relativos

# Recuperar commit após reset --hard
git reset --hard HEAD~3               # "perdeu" 3 commits
git reflog                            # veja os commits antes do reset
# HEAD@{3} abc1234 commit: feat: funcionalidade X  ← ainda existe!
git reset --hard abc1234              # recupera o estado anterior

# Recuperar branch deletada
git branch -D feature/deletada        # deletou a branch
git reflog                            # encontre o último commit da branch
# HEAD@{5} def5678 commit: último commit da feature/deletada
git checkout -b feature/deletada def5678  # recria a branch

# Reflog tem expiração padrão de 90 dias
# git config gc.reflogExpire         → padrão: 90 dias
# git config gc.reflogExpireUnreachable → padrão: 30 dias

Hooks — Automação no Git

Hooks são scripts executados automaticamente em eventos do Git. Ficam em .git/hooks/ (local) ou em ferramentas como Husky (compartilhado via repositório).

# pre-commit — valida antes de commitar
# .git/hooks/pre-commit (ou via Husky: .husky/pre-commit)
#!/bin/sh
set -e

echo "Executando validações pre-commit..."

# Linting
npm run lint || { echo "❌ Lint falhou. Corrija antes de commitar."; exit 1; }

# Testes unitários rápidos
npm run test:unit || { echo "❌ Testes falharam."; exit 1; }

echo "✅ Validações ok."
# commit-msg — valida formato da mensagem de commit
# .git/hooks/commit-msg
#!/bin/sh

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Valida Conventional Commits
PATTERN="^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?(!)?: .{1,100}"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
    echo "❌ Mensagem de commit inválida!"
    echo "   Use: tipo(escopo): descrição"
    echo "   Exemplo: feat(cart): adicionar validação de quantidade"
    exit 1
fi
# pre-push — validações mais pesadas antes do push
# .git/hooks/pre-push
#!/bin/sh
set -e

echo "Executando validações pre-push..."

# Testes completos (mais lentos, mas antes de ir para o servidor)
./gradlew test || { echo "❌ Testes falharam. Corrija antes de fazer push."; exit 1; }

# Verificar por TODOs críticos
if git diff origin/main..HEAD | grep -q "TODO(CRITICAL)"; then
    echo "❌ TODOs críticos encontrados. Resolva antes do push."
    exit 1
fi

echo "✅ Pronto para push."
# Configuração com Husky (Node.js) — hooks compartilhados no repositório
npm install --save-dev husky
npx husky init

# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

# .lintstagedrc.json — aplica apenas nos arquivos staged
{
  "*.ts": ["eslint --fix", "prettier --write"],
  "*.java": ["./gradlew spotlessApply"],
  "*.{json,yml,yaml}": ["prettier --write"]
}

.gitignore — Padrões e Boas Práticas

# .gitignore do projeto

# Build outputs
target/
dist/
build/
out/
*.class
*.jar
*.war

# Dependências
node_modules/
.gradle/
.mvn/wrapper/maven-wrapper.jar

# IDE — estes devem estar no gitignore GLOBAL, não do projeto
# Mas incluir se o time usa o mesmo IDE
.idea/
*.iml
.vscode/settings.json   # mas NÃO .vscode/extensions.json (útil compartilhar)
.classpath
.project

# Ambiente e secrets — NUNCA commitar
.env
.env.local
.env.production
*.key
*.pem
secrets/

# Logs
*.log
logs/

# Temporários
*.tmp
*.temp
.cache/
# .gitignore global — para sua máquina, não compartilhado
git config --global core.excludesfile ~/.gitignore_global

# ~/.gitignore_global
.DS_Store
Thumbs.db
*.swp
*.swo
.idea/
.vscode/
# .gitkeep — manter diretório vazio no repositório
# Git não rastreia diretórios vazios — use .gitkeep para preservar a estrutura
mkdir -p src/main/resources/uploads
touch src/main/resources/uploads/.gitkeep

# No .gitignore do projeto:
# src/main/resources/uploads/*
# !src/main/resources/uploads/.gitkeep

# Ignorar arquivo já rastreado (após adicionar ao .gitignore)
git rm --cached arquivo.env           # remove do índice, mantém no disco
git rm --cached -r node_modules/      # remove diretório do índice