Javascript

Git avançado e fluxo de trabalho em equipe Já leu

14 min de leitura

Git avançado e fluxo de trabalho em equipe
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 q

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
Comentários

Mais em Javascript

Funções: declaração, expressão e arrow functions
Funções: declaração, expressão e arrow functions

Se os la&ccedil;os evitam a repeti&ccedil;&atilde;o de a&ccedil;&otilde;es, a...

Revisão  + Projeto Final: Produção Real
Revisão + Projeto Final: Produção Real

Chegamos ao fim de mais um módulo. Em três artigos cobrimos deploy, segurança...

APIs públicas: exemplos práticos
APIs públicas: exemplos práticos

Teoria sem prática é esquecida rápido. Neste artigo vamos consumir APIs públi...