Voltar para artigos
CategoriaDemo

Cancelei o GitKraken e construí meu próprio cliente Git em dois dias

11 de agosto de 2026
Tags
Open SourceFrontendProdutividadeArquiteturaAutomação
Cancelei o GitKraken e construí meu próprio cliente Git em dois dias

O GitKraken tava pesado demais pra minha máquina e pro meu bolso

Chegou o e-mail da renovação anual do GitKraken e eu olhei praquilo com um carinho especial: o carinho de quem olha pra conta de luz. Pago todo ano. Por um cliente Git. Que demora uma eternidade pra abrir e engole RAM como se fosse o Chrome com 40 abas.

O pior nem era o dinheiro, era a espera. Clicar no ícone, ir buscar café, voltar, e o bicho ainda carregando. Minha máquina aguenta build de projeto grande o dia inteiro, mas sofria pra renderizar um grafo de commits em Electron.

Aí eu pensei: e se em vez de trocar por outra ferramenta paga (ou gratuita e capada), eu simplesmente construísse a minha? Do meu jeito, enxuta, sem conta anual.

Nasceu o GitCraque. Este post conta como.

Tempo estimado de leitura: 5 min

Grafo do GitCraque aberto num repositório real

Dois dias de vibe coding e um trocadilho que eu não vou abandonar

Aí eu construí o meu. Chama GitCraque, e foi basicamente dois dias de vibe coding do zero até virar meu cliente Git do dia a dia. Sem Electron, sem conta anual, sem esperar splash screen.

O nome? Olha o GitKraken: um polvo gigante, oito braços, mora no fundo do mar e nunca ganhou uma Copa. Oito braços e zero troféus é um aproveitamento ruim pra qualquer elenco. Do outro lado tinha um cara com o corte de cabelo mais indefensável da história do futebol, dois gols na final e a alcunha de O Fenômeno. A escolha se fez sozinha. Troquei o K pelo C, o cefalópode pelo camisa 9.

Yes, it's a tribute. Yes, it's a pun. No, we will not stop.

Pra testar é um comando, dentro de qualquer repositório:

bash
npx gitcraque                     # dentro do diretório do repositório
npx gitcraque ~/code/project      # ou aponta um path, tipo `git -C`
npx gitcraque --repo ~ --port 5271
npx gitcraque --no-open           # não abre o browser

O repo tá em https://github.com/frederico-kluser/GitCraque. E aviso logo: o projeto tá crescendo, evoluindo, não tá finalizado. Tá jogando bem, mas ainda tá em temporada.

A escalação: um backend com uma dependência só

Time enxuto, sem elenco inchado e sem folha salarial em node_modules. A escalação ficou assim:

  • Goleiro: Node puro com node:http. Segura a API sem framework na frente. Nada de Express, nada de Fastify, só o que vem na caixa.
  • Zagueiro: child_process, marcando o binário do git em cima, sempre com argv em array. Sem shell interpola no meu time.
  • Lateral: ws. A única dependência do backend. Uma. Total.
  • Meio-campo: React 19 + Vite, distribuindo o jogo pra SPA.
  • Camisa 10: Tailwind + Motion UI, o primeiro toque e a beleza.
  • Ponta: @dnd-kit/core, arrasta o commit e deixa o marcador pra trás.
  • Camisa 9: SVG escrito à mão. O grafo inteiro é <circle> e <path> com Bézier cúbica. Nenhuma lib de gitgraph. O drible é todo nosso.

E o Electron? Não foi convocado. Cliente Git empacotado em Chromium é aquele jogador que chega pesado, come metade da RAM e demora pra aquecer. Aqui não.

Requisitos pra escalar: Node >= 22.13 (o node:sqlite só larga a flag experimental a partir dessa versão) e git no PATH. Chuteira é opcional.

As funcionalidades que me fizeram esquecer o GitKraken

Time escalado, agora o que ele faz em campo. Seis coisas, na ordem em que me conquistaram:

  1. O grafo de histórico. O backend roda git log --all --topo-order e entrega os dados crus; o front calcula o (X, Y) de cada commit com algoritmo próprio, separando filhos de branch de filhos de merge pra traçar rotas que nunca se cruzam. O desenho é SVG escrito à mão, <circle> pros commits e <path> com Béziers pras branches, com virtualização de janela. Repo com dezenas de milhares de commits e o scroll continua liso. Nenhuma lib de gitgraph envolvida. O drible é nosso.

  2. Worktrees sem checkout. Clicou num worktree na lateral, o servidor faz process.chdir() pro path absoluto e avisa por WebSocket. Nenhum git checkout acontece, a working tree de ninguém é tocada. É trocar de posição sem pedir substituição pro quarto árbitro.

  3. Minha favorita, e olha que eu testei todas: deletar worktree, branch local e branch na origin num comando só. No GitKraken eu fazia isso em dois comandos separados, toda vez, e toda vez me irritava. Agora é um clique. Parece bobo, mas é o tipo de atrito diário que acumula até você cancelar uma assinatura.

  4. Drag-and-drop com intenção. Arrastar um commit em cima de uma branch propõe cherry-pick; branch em cima de branch abre a escolha entre merge e rebase. O comando cru aparece antes de rodar, e qualquer coisa que reescreve histórico exige press-and-hold: o botão só cede se você segurar. Tempo de pensar antes de chutar.

  5. Push que nunca trava. Modelo trampolim com GIT_ASKPASS apontando pro próprio script do Node, que responde o git via stdout. O token viaja por unix socket com nonce de uso único. Nunca no argv, nunca em disco.

  6. Picker quando você abre fora de um repo. Tela com recents, varredura das pastas usuais e até git init pra onde você tá parado. Começou fora de um repositório? Não é erro, é scout.

O squash gráfico sem abrir vim na sua cara

Todo cliente Git que tenta fazer rebase interativo "bonito" cai numa das duas armadilhas: ou emula um terminal inteiro dentro da UI (pesado, frágil, horroroso), ou desiste e abre o vim na sua cara mesmo. Eu odeio as duas opções. O git já sabe fazer rebase interativo sozinho, o problema é que ele insiste em abrir um editor pra você editar o git-rebase-todo na mão.

Aí veio a sacada: e se o "editor" fosse um script meu?

O git respeita a variável GIT_SEQUENCE_EDITOR. Quando ela tá setada, ele gera o git-rebase-todo, chama o programa apontado passando o caminho do arquivo, e espera um exit 0 pra continuar. Eu injeto GIT_SEQUENCE_EDITOR="node proxy-editor.mjs" e o script faz o trabalho sujo:

javascript
// server/proxy-editor.mjs
import { readFileSync, writeFileSync } from 'node:fs';

const todoPath = process.argv[2];
const targets = new Set(process.env.GITCRaque_SQUASH.split('\n'));

const lines = readFileSync(todoPath, 'utf8').split('\n');
const edited = lines.map((line, i) => {
  const [action, hash] = line.split(' ');
  if (action === 'pick' && i > 0 && targets.has(hash)) {
    return line.replace(/^pick/, 'squash');
  }
  return line;
});

writeFileSync(todoPath, edited.join('\n'));
process.exit(0);

Lê o todo que o próprio git gerou, troca pick por squash nas linhas marcadas na UI (mantendo o primeiro commit como pick, senão o rebase inteiro desanda), grava e sai com exit 0. O git aplica o rewrite como se um humano tivesse editado o arquivo com todo cuidado do mundo.

Zero emulação de terminal. Zero vim.

Três commits entram, um sai. E ninguém viu como.

A volta por cima: sobreviver ao Chrome descartando sua aba

O Chrome tem dois jeitos de economizar recurso com aba em segundo plano, e os dois doem diferente.

O primeiro é o freezing: as filas de task param e o WebSocket volta half-open. O readyState jura que tá OPEN, mas a conexão morreu do outro lado. É o jogador em pé no campo, fora da jogada.

O segundo é pior. Discarding: o Chrome apaga a página da memória inteira e ela volta do zero, como se fosse a primeira visita.

O GitCraque cobre os três caminhos de retorno: visibilitychange, resume e pageshow com persisted. E o snapshot do estado da view acontece quando a aba esconde, nunca na saída. Por quê? Porque beforeunload e unload simplesmente não disparam quando o browser descarta a aba. Você programa o handler, confia nele, e ele nunca roda. Descobri do jeito divertido.

Tem ainda um root boundary pra segurar render que explode, e o reload automático tem budget. Loop de reload é pior que tela quebrada: você nem consegue abrir o devtools pra investigar.

Joelho reconstruído, artilharia da Copa. It works.

O que eu aprendi testando tudo com TDD

Me perguntaram qual foi a parte difícil. Honestamente? Nenhuma foi muito complicada, e o motivo é chato de tão simples: eu fui testando com qualidade tudo que era feito. TDD do começo ao fim, mais teste end-to-end por cima. Hoje o npm test roda 472 testes: 319 de server, 51 do grafo, 20 de drag-and-drop, 82 do viewer. E ainda tem 39 checks de e2e fora desse bolo.

Tem um trade-off real aqui, e eu aprendi na marra: roda um comando de teste por vez. A suíte do grafo mede proporção de wall-clock, então se você rodar ela junto com outro processo pesado ela marca uma falta que nunca aconteceu. E não tem VAR aqui.

Segurança foi decisão de projeto, não detalhe: o servidor escuta só em 127.0.0.1, recusa request com Host/Origin de fora, e nunca, jamais, deve ser exposto à rede.

E sejamos claros: o projeto tá crescendo. Não tá finalizado, vai melhorando com o tempo. Igual ao Fenômeno depois do joelho, aliás.

Testa aí e me conta o que quebrou

Chega de ler. Entra num projeto seu e roda:

bash
npx gitcraque

npm install -g gitcraque
cd ~/code/project && gitcraque

A view abre sozinha no navegador, já escalada no repositório. Sem instalar nada permanente, sem cadastro, sem cartão de crédito.

Um aviso antes que você xingue o projeto: npm i github:frederico-kluser/GitCraque não funciona. O pacote publicado já traz a SPA buildada, e o build só roda no npm pack. Do source é clone + npm run build.

Gostou? Estrela no repo, abre issue, manda PR. Ou nem usa: se inspire e construa a sua. E manda esse post no Slack ou no WhatsApp praquela pessoa que ainda paga cliente Git todo ano. Você conhece uma.

Boots optional.