Saber usar git add, git commit e git push é o mínimo. Em um projeto real com múltiplos desenvolvedores, você vai lidar com conflitos de merge, branches paralelas, histórico bagunçado, releases mal organizadas e deploys que quebraram tudo.
Git é uma ferramenta poderosa — e como toda ferramenta poderosa, pode fazer muito bem ou muito mal dependendo de como é usada. Este artigo ensina o Git que você realmente vai usar no dia a dia profissional.
Revisão — os comandos fundamentais
# Configuração inicial (uma vez por máquina)
git config --global user.name "Seu Nome"
git config --global user.email "seu@email.com"
git config --global core.editor "code --wait" # VS Code como editor padrão
git config --global init.defaultBranch main
# Iniciando
git init # novo repositório
git clone URL # clona repositório existente
git clone URL nome-da-pasta # clona com nome customizado
# Ciclo básico
git status # estado atual
git add arquivo.js # adiciona arquivo específico
git add . # adiciona tudo
git add -p # adiciona interativamente (por partes)
git commit -m "mensagem" # commita
git commit --amend # edita o último commit
# Remoto
git remote add origin URL # conecta ao repositório remoto
git push origin main # envia para o remoto
git pull origin main # busca e mescla
git fetch origin # busca sem mesclar
Branches — trabalhando em paralelo
# Criar e navegar
git branch # lista branches locais
git branch -a # lista branches locais e remotas
git branch nome-da-branch # cria branch
git checkout nome-da-branch # muda para branch
git checkout -b nome-da-branch # cria e muda em um comando
git switch nome-da-branch # alternativa moderna ao checkout
git switch -c nome-da-branch # cria e muda (moderno)
# Remover
git branch -d nome # remove branch local (seguro)
git branch -D nome # remove branch local (forçado)
git push origin --delete nome # remove branch remota
# Renomear
git branch -m nome-antigo nome-novo
# Ver branches e seus últimos commits
git branch -v
git branch --merged # branches já mergeadas em main
git branch --no-merged # branches ainda não mergeadas
Merge vs Rebase — entendendo a diferença
Esta é uma das discussões mais importantes do Git:
# ── Situação: você está em feature e quer integrar main ──
# MERGE — preserva o histórico exato
# Cria um "merge commit" que une os dois históricos
git checkout feature
git merge main
# Histórico após merge:
# A---B---C---M (main)
# /
# D---E (feature mergeada)
# M = merge commit
# REBASE — reescreve o histórico de forma linear
# Aplica seus commits em cima da branch de destino
git checkout feature
git rebase main
# Histórico após rebase:
# A---B---C (main)
#
# D'--E' (feature rebaseada — novos commits)
# D' e E' são os mesmos que D e E mas com novos hashes
Quando usar cada um:
Merge:
✅ Branches públicas e compartilhadas
✅ Quando o histórico exato importa
✅ Feature branches no merge final (PR)
✅ Mais seguro para iniciantes
Rebase:
✅ Atualizar branch local antes de abrir PR
✅ Manter histórico limpo e linear
✅ Branches privadas ainda não compartilhadas
❌ Nunca em branches públicas já compartilhadas
("golden rule of rebasing")
Estratégias de branching
Git Flow — para projetos com releases programadas
main ──────●────────────────────────●──────
| |
release/1.0 ──────●──────────● |
| | |
develop ──●───●───●───●───●───●───●───●──
| | |
feature/A ────●───●───┘ |
|
feature/B ────────────●───●───┘
|
hotfix/bug ────────────────────●──●──────────
# Branches no Git Flow:
# main → código em produção
# develop → integração de features
# feature/* → novas funcionalidades
# release/* → preparação de release
# hotfix/* → correções urgentes em produção
# Fluxo típico de feature:
git switch develop
git switch -c feature/autenticacao-jwt
# ... trabalho ...
git switch develop
git merge --no-ff feature/autenticacao-jwt # --no-ff preserva contexto
git branch -d feature/autenticacao-jwt
GitHub Flow — para deploys contínuos (mais simples)
main ──●──────────────────────●──────────────●──
/
feature ●──●──●──(PR)────── fix ●──●──(PR)
# Apenas duas regras:
# 1. main sempre está pronta para deploy
# 2. Todo trabalho acontece em branches criadas de main
git switch main
git pull origin main
git switch -c feature/nova-funcionalidade
# ... trabalho e commits ...
git push origin feature/nova-funcionalidade
# Abre Pull Request no GitHub
# Revisão → aprovação → merge → delete branch
Para a maioria dos projetos modernos, GitHub Flow é suficiente e mais simples.
Commits semânticos — mensagens que comunicam
Uma das práticas mais valiosas e menos adotadas:
# Formato: tipo(escopo): descrição curta
#
# tipo: o que foi feito
# escopo: onde foi feito (opcional)
# descrição: o quê, não o como
# Tipos principais:
feat → nova funcionalidade
fix → correção de bug
docs → apenas documentação
style → formatação (sem mudança de lógica)
refactor → refatoração (sem bug fix nem feature)
test → adicionando/corrigindo testes
chore → tarefas de manutenção (build, deps, config)
perf → melhoria de performance
ci → mudanças em CI/CD
# Exemplos reais:
git commit -m "feat(auth): adiciona autenticação JWT"
git commit -m "fix(usuarios): corrige validação de email duplicado"
git commit -m "docs: atualiza README com instruções de instalação"
git commit -m "refactor(tarefas): extrai lógica de paginação para service"
git commit -m "test(auth): adiciona testes de integração para login"
git commit -m "chore: atualiza dependências para versões mais recentes"
git commit -m "perf(consultas): adiciona índice ao campo email no MongoDB"
Com commits semânticos, o histórico vira uma documentação legível:
git log --oneline
# a3f8c21 feat(auth): adiciona autenticação JWT
# b2e1d90 test(auth): adiciona testes de integração para login
# c4d7f12 fix(usuarios): corrige validação de email duplicado
# d9a1b34 refactor(tarefas): extrai lógica de paginação
# e5f2c67 chore: atualiza mongoose para 7.6.0
# f8a3d45 feat(tarefas): adiciona filtro por prioridade
Commitizen — commits semânticos com assistente
npm install -D commitizen cz-conventional-changelog
# package.json
{
"scripts": {
"commit": "cz"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
}
# Em vez de git commit -m "..."
npm run commit
# ? Select the type of change:
# ❯ feat: A new feature
# fix: A bug fix
# docs: Documentation only
# style: Formatting changes
# ...
# ? What is the scope? (optional): auth
# ? Short description: adiciona autenticação JWT
# ? Longer description: (optional)
# ? Breaking change? No
# ? Issues closed: #42
Comandos avançados essenciais
git stash — guardar trabalho temporariamente
# Você está no meio de algo e precisa trocar de branch urgente
git stash # guarda mudanças não commitadas
git stash save "descrição" # com descrição
git stash list # lista o que está guardado
# stash@{0}: On feature/auth: trabalho em progresso
# stash@{1}: On main: fix urgente
git stash pop # aplica o mais recente e remove da lista
git stash apply stash@{1} # aplica específico (mantém na lista)
git stash drop stash@{0} # remove da lista sem aplicar
git stash clear # limpa tudo
git stash branch feature/nova stash@{0} # cria branch do stash
git cherry-pick — aplicar commit específico
# Você quer trazer apenas um commit de outra branch
git log feature/pagamento --oneline
# a1b2c3d fix: corrige cálculo de desconto ← quer este
# d4e5f6g feat: adiciona parcelamento
git switch main
git cherry-pick a1b2c3d
# Aplica apenas esse commit na branch atual
git revert — desfazer commit de forma segura
# Desfaz um commit criando um NOVO commit que reverte as mudanças
# Seguro para branches públicas — não reescreve histórico
git revert a1b2c3d
# Revert de merge commit
git revert -m 1 a1b2c3d # -m 1 = volta ao primeiro parent
git reset — voltar no tempo
# Três modos — do mais seguro ao mais destrutivo:
# --soft: desfaz commit, mantém mudanças no staging
git reset --soft HEAD~1
# --mixed (padrão): desfaz commit, mantém mudanças no working directory
git reset HEAD~1
git reset --mixed HEAD~1
# --hard: desfaz commit, DESCARTA todas as mudanças
git reset --hard HEAD~1 # ⚠️ cuidado — mudanças perdidas
# HEAD~1 = último commit
# HEAD~3 = três commits atrás
# a1b2c3d = commit específico
# ⚠️ Nunca use --hard em branches públicas compartilhadas
git reflog — o histórico de tudo
# O reflog guarda TUDO que aconteceu — mesmo após reset --hard
git reflog
# HEAD@{0}: commit: feat: adiciona nova rota
# HEAD@{1}: reset: moving to HEAD~1
# HEAD@{2}: commit: fix: corrige bug crítico ← quero recuperar este
# HEAD@{3}: commit: feat: adiciona autenticação
# Recuperar commit perdido após reset --hard
git reset --hard HEAD@{2}
# ou
git checkout -b recuperado HEAD@{2}
Resolvendo conflitos de merge
# Situação: você fez merge e há conflito
git merge feature/nova-rota
# CONFLICT (content): Merge conflict in src/routes/index.js
# Automatic merge failed; fix conflicts and then commit the result.
# Abrindo o arquivo com conflito:
// src/routes/index.js com conflito
const express = require('express');
const router = express.Router();
<<<<<<< HEAD
// Sua versão (branch atual)
router.use('/usuarios', require('./usuarios'));
router.use('/auth', require('./auth'));
=======
// Versão da feature/nova-rota
router.use('/usuarios', require('./usuarios'));
router.use('/produtos', require('./produtos'));
>>>>>>> feature/nova-rota
module.exports = router;
// Resolvendo manualmente — combina as duas versões
const express = require('express');
const router = express.Router();
router.use('/usuarios', require('./usuarios'));
router.use('/auth', require('./auth')); // da nossa branch
router.use('/produtos', require('./produtos')); // da feature
module.exports = router;
# Após resolver todos os conflitos:
git add src/routes/index.js
git commit -m "merge: integra feature/nova-rota com resolução de conflito"
# Ou abortar o merge e voltar ao estado anterior:
git merge --abort
Git hooks com Husky — integrado ao projeto
# Já instalamos no artigo anterior — expandindo os hooks:
# .husky/commit-msg — valida o formato da mensagem de commit
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
npm install -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style',
'refactor', 'test', 'chore', 'perf', 'ci'
]],
'subject-max-length': [2, 'always', 100],
'subject-case': [2, 'always', 'lower-case'],
},
};
# Agora commits com mensagens ruins são bloqueados:
git commit -m "arrumei umas coisas"
# ✖ subject may not be empty [subject-empty]
# ✖ type may not be empty [type-empty]
git commit -m "fix: corrige validação de email"
# ✔ Running commitlint...
# ✔ Commit aceito!
.gitignore — o que nunca commitar
# .gitignore para projetos Node.js
# Dependências
node_modules/
.pnp
.pnp.js
# Variáveis de ambiente — NUNCA commite!
.env
.env.local
.env.*.local
.env.production
# Build e dist
dist/
build/
out/
# Cobertura de testes
coverage/
.nyc_output/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
logs/
# Cache
.cache/
.eslintcache
.prettierignore
# Editores
.vscode/
!.vscode/settings.json # exceto settings compartilhadas
!.vscode/extensions.json
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db
# Uploads e assets gerados
uploads/
public/uploads/
Pull Requests — revisão de código profissional
Um bom PR (Pull Request / Merge Request) é uma forma de comunicação:
<!-- Template de PR: .github/pull_request_template.md -->
## O que este PR faz?
Breve descrição do que foi implementado ou corrigido.
## Por que esta mudança é necessária?
Contexto do problema que resolve ou feature que adiciona.
## Como foi testado?
- [ ] Testes unitários adicionados/atualizados
- [ ] Testes de integração passando
- [ ] Testado manualmente em desenvolvimento
## Checklist
- [ ] Código segue os padrões do projeto (ESLint/Prettier)
- [ ] Documentação atualizada se necessário
- [ ] Sem `console.log` esquecidos
- [ ] Variáveis de ambiente documentadas no .env.example
## Screenshots (se aplicável)
<!-- Para mudanças visuais -->
## Issues relacionadas
Closes #42
Aliases úteis — produtividade no terminal
# ~/.gitconfig — aliases que aceleram o trabalho
git config --global alias.st "status -sb"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.undo "reset --soft HEAD~1"
git config --global alias.unstage "restore --staged"
git config --global alias.branches "branch -v --sort=-committerdate"
git config --global alias.aliases "config --get-regexp alias"
# Uso:
git st # status compacto
git lg # log visual com árvore de branches
git undo # desfaz último commit mantendo mudanças
git unstage . # remove tudo do staging
git branches # lista branches ordenadas pela mais recente
Fluxo completo de trabalho em equipe
# 1. Sempre começar de main atualizado
git switch main
git pull origin main
# 2. Criar branch com nome descritivo
git switch -c feature/autenticacao-jwt
# 3. Trabalhar em pequenos commits semânticos
git add src/middlewares/auth.js
git commit -m "feat(auth): cria middleware de verificação JWT"
git add src/routes/auth.js
git commit -m "feat(auth): adiciona rotas de login e registro"
git add tests/auth.test.js
git commit -m "test(auth): adiciona testes de integração para autenticação"
# 4. Antes de abrir PR, atualizar com main
git fetch origin
git rebase origin/main # histórico linear, sem merge commit extra
# 5. Resolver conflitos se houver, então:
git rebase --continue
# 6. Push para o remoto
git push origin feature/autenticacao-jwt
# 7. Abrir Pull Request no GitHub
# 8. Aguardar revisão, fazer ajustes se solicitado
# 9. Após aprovação: merge e deletar branch
git switch main
git pull origin main
git branch -d feature/autenticacao-jwt
Tarefa para você
Pratique o fluxo completo em um projeto pessoal:
# 1. Crie um repositório no GitHub com o projeto da API REST
# 2. Configure todos os hooks do Husky:
# - pre-commit: lint-staged
# - commit-msg: commitlint
# 3. Crie as seguintes branches e faça trabalho real em cada uma:
# feature/paginacao → adiciona paginação às listagens
# feature/busca → adiciona busca por texto
# fix/validacao-email → melhora regex de validação
# 4. Simule um conflito:
# Edite o mesmo arquivo nas duas feature branches
# Mergeje a primeira em main
# Tente mergear a segunda e resolva o conflito
# 5. Use git log --oneline --graph --decorate --all
# e veja a árvore de branches que você criou
# 6. Tente fazer um commit com mensagem errada
# e veja o commitlint bloqueando
# 7. Use git stash para simular uma interrupção:
# - Você está no meio de um trabalho
# - Precisa corrigir um bug urgente em main
# - Faça stash, vá para main, corrija, volte e aplique o stash
Conclusão
Neste artigo você aprendeu:
- Os comandos fundamentais do Git revisitados com profundidade
- Como trabalhar com branches — criar, navegar, remover e renomear
- A diferença entre merge e rebase — e quando usar cada um
- Git Flow e GitHub Flow — estratégias para times reais
- Commits semânticos com Conventional Commits e Commitizen
- Comandos avançados: stash, cherry-pick, revert, reset e reflog
- Como resolver conflitos de merge com calma e confiança
- Git hooks com Husky e commitlint para validação automática
- O .gitignore completo para projetos Node.js
- Como escrever Pull Requests que comunicam bem
- Aliases que aceleram o trabalho no terminal
- O fluxo completo de trabalho em equipe do início ao fim
No próximo artigo vamos aprender sobre TypeScript — como adicionar tipagem estática ao JavaScript e o que isso muda na forma de desenvolver.
📌 Próximo artigo: Aula 36 — Introdução ao TypeScript
📚 Fontes e Referências
- Git — Documentação oficial: https://git-scm.com/doc
- Conventional Commits: https://www.conventionalcommits.org/pt-br
- Commitizen: https://commitizen-tools.github.io/commitizen
- Commitlint: https://commitlint.js.org
- Husky: https://typicode.github.io/husky
- Atlassian — Git Tutorials: https://www.atlassian.com/git/tutorials
- GitHub — Pull Request templates: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests
- Pro Git — Scott Chacon e Ben Straub (gratuito): https://git-scm.com/book/pt-br/v2
- Oh Shit, Git!?: https://ohshitgit.com/pt_BR