Já passamos muito tempo usando o Git… Mas outra ferramenta está tentando oferecer vantagens suficientes para justificar a mudança. Vamos nos aprofundar.
Table of Contents
Este post é apenas a parte 1 de uma série de duas partes. Esta primeira parte foca nas diferenças entre Jujutsu e Git com conceitos e exemplos práticos, além de alguns truques interessantes que podemos fazer com JJ para começar a usá-lo em nossos repositórios!
No segundo post da série, lidaremos com conflitos, merges, pull requests e workflows mais complexos. Veremos como o JJ torna essas ações mais simples, mesmo que sejam consideradas demoradas no Git.
Mas como surgiu o Jujutsu?
O Jujutsu é uma ferramenta de controle de versão que visa tornar os workflows mais flexíveis. Ele repensa algumas das frustrações encontradas ao usar o Git.
Para refrescar a memória:
Controle de versão é um sistema que registra alterações em um arquivo ou conjunto de arquivos ao longo do tempo para que você possa recuperar versões específicas posteriormente. – # Getting Started – About Version Control
É isso que o Jujutsu visa fazer, embora redefina a forma como você lida com alterações e com o diretório de trabalho de maneira diferente do Git.
O foco é ser fácil de usar. O JJ também usa repositórios Git como sua camada de armazenamento, o que permite que desenvolvedores usem Jujutsu com Git. Isso significa que você pode usá-lo com repositórios existentes!
Além disso, o Jujutsu é uma ferramenta usada internamente no Google. Martin von Zweigbergk é o criador do projeto. Ele e um grupo de colaboradores quiseram experimentar uma nova ideia para melhorar suas ferramentas internas. Esse experimento visava lidar com alterações sem o stash e explorar outros conceitos diferentes, como mover-se livremente entre commits, mesmo quando há conflitos!
Além disso, Martin trabalhou anteriormente com Mercurial antes de mudar o foco para o JJ, mas continua trabalhando com controle de código fonte. A conexão é clara quando vemos o README do projeto mencionando como vários recursos do Mercurial inspiraram o design do JJ, junto com ideias do Sapling e do Darcs (como tratar conflitos como objetos de primeira classe). Ajudando a ferramenta a se tornar o que é hoje.
Finalmente, o JJ foi lançado oficialmente ao público em 2023. Portanto, sim, é uma ferramenta relativamente recente. No entanto, já foi bastante elogiada!
Mas por que dar uma chance?
A maioria dos desenvolvedores já está acostumada com o Git, não é? Por que testar uma nova ferramenta? Como diz o velho ditado: “se não está quebrado, não conserte”, certo?
Mas o JJ tenta resolver os principais problemas que encontramos com o Git. Alguns dos recursos que o JJ oferece e que mais me chamam a atenção são merges mais simples e menos dores de cabeça com conflitos. Embora funcione de forma diferente e exija um tempo de adaptação, os benefícios são interessantes.
Tudo isso é possível devido a alguns princípios fundamentais:
- Todo o repositório está sob controle de versão: toda vez que você executa um comando jj, seja
jj statusoujj evolog, ele tira um snapshot da sua cópia de trabalho. Isso concede um histórico completo do seu repositório. Você pode conferir isso com o comandojj op log.
Conflitos podem ser registrados em commits: as operações têm sucesso mesmo quando há conflitos! Esses conflitos são armazenados, para que você possa resolvê-los mais tarde.
Possui rebase automático: sempre que um commit é modificado, seus descendentes são automaticamente rebasados.
Suporte abrangente para reescrever o histórico: diferentes comandos permitem mover partes de uma alteração de um commit para outro. Além disso,
jj diffeditpermite editar as alterações de um commit sem precisar fazer o checkout dele.
Esses benefícios podem ajudar bastante o seu workflow, dependendo do tamanho da sua equipe e de como você lida com PRs e branches.
Mas o que muitos gostariam de saber são as diferenças práticas entre o Git e o JJ. Afinal, as pessoas precisam descobrir se o processo de adaptação vale o esforço.
Bem, vamos conferir algumas delas!
JJ vs Git
Como mencionado, podemos usar o JJ com o Git. Martin von Zweigbergk garantiu que o JJ seja compatível com ele. De fato, podemos usar jj git clone para pegar um repositório do GitHub, por exemplo.
Mas ainda existem algumas diferenças que podem definir sua decisão ao testar a ferramenta em seu próximo projeto de estudos.
Vamos dar uma olhada em uma comparação rápida:
Git: você precisa adicionar alterações ao stage para criar um commit, que pode ter no máximo dois pais. Além disso, qualquer commit feito fora de uma branch não é considerado pelo Git. É visto como lixo. Seu workflow é fortemente baseado em diferentes branches para várias funcionalidades.
JJ: não há área de stage ou index. Além disso, um commit no JJ pode ter mais de dois pais, o que aumenta a flexibilidade. Adicionalmente, o JJ não tem a etapa de staging. Cada ação dispara um commit. Isso significa que, ao executar
jj git init, as alterações fazem parte automaticamente da cópia de trabalho. Não há necessidade de um comando comogit add some-file.js, como mostrado abaixo:
❯ jj git init Initialized repo in "."
Hint: Running git clean -xdf will remove .jj/!
❯ jj status
Working copy changes:
A .gitignore
A index.html
A script.js
Working copy (@) : nntrkurr e56acbdd (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)Mesmo que eu tenha acabado de começar meu projeto, meus três arquivos principais já fazem parte automaticamente da cópia de trabalho. Legal, né? Se eu usasse o Git, precisaria adicionar essas alterações ao meu commit atual.
No repositório abaixo, por exemplo, eu teria que adicionar minhas modificações antes de fazer um push:
Também é importante notar que git commit e jj commit são comandos diferentes. O comando do Git cria um commit, enquanto o comando do Jujutsu permite editar a descrição do seu commit atual que já possui conteúdo e criar um novo commit vazio para trabalhar.
Veja o exemplo abaixo mostrando minha cópia de trabalho que adicionou algumas alterações ao HTML do projeto. Após executar jj commit, o editor de texto padrão do meu sistema abriu (no meu caso, o nano), e escrevi a descrição “Semantic HTML”:
Note que o comando JJ se comportou de forma diferente. Ele apenas me permitiu avançar para o próximo commit, finalizando o atual. O commit pai não é mais “Footer” porque o commit com as alterações de HTML que eu fiz já foi salvo.
Agora, qualquer nova alteração que eu fizer fará parte do commit com o change id wuoplktq, que está atualmente vazio e não possui descrição.
Falaremos sobre os outros conceitos mostrados acima mais adiante neste post, não se preocupe.
Além disso, o Jujutsu foca mais em alterações do que na ideia de branches. É por isso que o conceito de change id é tão importante. Logo veremos isso na prática.
Com essas diferenças, nosso workflow muda. No entanto, isso traz vantagens. Vamos vê-las na prática.
JJ & VSCode
Antes de começarmos, quero recomendar uma ferramenta caso você use o VSCode como eu. (Também funciona com o Cursor).
O VisualJJ é muito interessante e pode nos ajudar a visualizar a estrutura de nossas modificações, especialmente se você vem do Git.
Ele fornece uma aba no VSCode que rastreia as alterações que você fez. Com esse suporte visual, você pode lidar melhor com a ideia de múltiplas alterações apontando para o mesmo pai.
Veja o exemplo abaixo em um pequeno projeto:
Além disso, ele também é capaz de executar alguns comandos para você. No entanto, nosso foco será usá-lo principalmente como suporte visual, já que é importante criar o hábito de escrever esses novos comandos para que eles façam sentido para você.
Consequentemente, durante nosso tutorial abaixo, mencionarei o VisualJJ algumas vezes para que possamos visualizar nosso progresso. Agora, vamos seguir em frente!
JJ na prática
Agora que você entende alguns dos conceitos básicos, vamos usá-lo em um mini projeto que nos ajudará a explorar a ferramenta.
Vimos que podemos iniciar um projeto usando JJ e Git como armazenamento com o comando jj git init ou usar jj git clone. Neste post, para manter nosso aprendizado direto, vamos começar com uma pasta simples que ainda não é um repositório!
Nomeie sua pasta como mini-jj-project ou hello-world, o que preferir. Depois, crie alguns arquivos simples apenas para testar o versionamento. Você poderia criar um arquivo script.js e um index.html, por exemplo.
Nota: O Jujutsu reconhece o arquivo
.gitignore. Se o seu pequeno projeto precisar de um arquivo de ignore, sinta-se à vontade para criá-lo. Ele funcionará da mesma maneira que no Git.
Finalmente, execute jj git init dentro da pasta. Você também pode usar sua IDE preferida. Neste post, usarei o VSCode, como mencionado no subtópico anterior.
Após executar o comando, você verá algo assim:
Legal! Vamos checar o status do nosso repositório?
Note que um atalho para jj status é jj st. Ambos funcionam bem! Além disso, observe as cores diferentes no terminal. Dependendo das suas configurações, essas cores serão ligeiramente diferentes, mas são importantes para vermos o change id e o commit id da nossa cópia de trabalho e do commit pai mais facilmente.
Em nossa cópia de trabalho, nntrkurr é o change id. Enquanto e56acbdd é o commit id. A cor de destaque que vemos na imagem acima mostra que podemos nos referir a esses ids apenas pela letra colorida. Isso significa que ainda não há outras alterações com ids semelhantes, então por enquanto você pode se referir a esses ids apenas pelo primeiro dígito. Útil, né?
Agora, vamos olhar alguns conceitos iniciais. Preste atenção ao commit pai:
Este é o commit root. Ele sempre terá os ids: zzzzzzzz 00000000. Além disso, veja que a cópia de trabalho foi criada acima dele. Ela já contém alterações, os arquivos que criamos, e é por isso que não aparece: (empty).
Para definir o usuário e o e-mail que serão vinculados às suas alterações, você pode executar os seguintes comandos:
> jj config set --user user.name "Cool developer"
> jj config set --user user.email "cooldev@dev.com"Agora, quer adicionar uma descrição à nossa cópia de trabalho? O comando jj describe abrirá o editor de texto padrão do seu sistema. No meu caso, o nano abriu:
Vemos informações relacionadas à nossa alteração aqui. E o texto "Hello world description" foi a descrição que adicionei.
Após escrever nossa descrição, podemos salvar as alterações. Agora, note que algo mais também mudou. Toda vez que atualizamos a descrição, nosso commit id mudará.
Nota: lembre-se de que o
change idé o que aparece primeiro, e o segundo id mostrado é ocommit id.
Veja o caso abaixo:
Agora nosso commit id é b67419cc. No entanto, nosso change id não foi modificado. Isso significa que mesmo que sua alteração evolua com o tempo, recebendo vários commit ids após mudanças, você ainda terá um change id estável para se referir a essa versão do seu código. Não apenas describe, mas muitos outros comandos disparam uma alteração no commit id, então tenha isso em mente se notar mudanças neles enquanto testa os comandos do JJ!
Agora, execute jj new para que possamos iniciar uma nova cópia de trabalho. Consequentemente, ainda não há alterações. Para confirmar isso, podemos executar jj st novamente e checar:
Vamos também usar a extensão Visual JJ para nos ajudar a visualizar nossas alterações. Veja que agora temos um fluxo simples:
Vamos fazer mais algumas alterações simples, como comentários, por exemplo. Minha descrição será "Documentation & comments addition."
Depois disso, podemos fazer o mesmo fluxo de antes e criar mais uma alteração com jj new.
Neste caso, adicione alterações a mais de um arquivo, como correções no texto da função JavaScript e alterações no arquivo HTML. Depois disso, escreva uma descrição de suas alterações. Contudo, desta vez não quero usar o editor de texto padrão. Vamos escrevê-la diretamente no comando. Algo como:
❯ jj describe -m "Fix incorrect naming"Agora, após essas duas alterações, cheque o VisualJJ novamente.
Até agora, temos várias semelhanças com o Git. Mas é interessante notar que não precisamos de nada equivalente ao git add. As alterações simplesmente já estão sendo rastreadas. Com esta alteração finalizada, vamos começar outra com mais um jj new.
Outro detalhe interessante é que quando executamos jj log, vemos que nosso commit root tem "root()":
❯ jj log
@ vpnnnrsl user@local.com 2026-02-24 23:12:11 d1f8dbb2
│ (empty) (no description set)
○ xlvvuzyl user@local.com 2026-02-24 23:11:17 d8cb31cf
│ Fix incorrect naming.
○ pspqyylr user@local.com 2026-02-24 23:09:00 c1cb04b0
│ Documentation & comments addition.
○ nntrkurr user@local.com 2026-02-24 22:54:46 b67419cc
│ Hello world description
◆ zzzzzzzz root() 00000000Isso é um revset. A documentação do Jujutsu explica detalhadamente:
O Jujutsu suporta uma linguagem funcional para selecionar um conjunto de revisões. Expressões nesta linguagem são chamadas de “revsets” (a ideia vem do Mercurial). A linguagem consiste em símbolos, operadores e funções. – Jujutsu docs
root() é uma função nessa linguagem e retorna o commit root que mencionamos anteriormente. O uso de revsets é como o Jujutsu faz listagens e vários outros comandos úteis. Exploraremos algumas dessas funções nas próximas seções e na parte 2.
Além disso, note que @ representa a cópia de trabalho atual. Ainda não temos alterações finalizadas da cópia de trabalho, e seu commit pai é xlvvuzyl (que é um change id). Você pode alternar para qualquer uma dessas alterações anteriores com o comando jj edit change-id-here:
❯ jj edit nntrkurr
Working copy (@) now at: nntrkurr e17a9a1c Hello world description
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 2 files, removed 0 filesO JJ forneceu a diferença entre as versões através da mensagem: "Added 0 files, modified 2 files, removed 0 files". No meu caso, eu tinha alterado apenas o index.html e o script.js.
Legal, com o básico entendido, já podemos usar o JJ em repositórios pequenos. Também praticamos o uso de change ids seguindo a ideia de estar sempre em uma cópia de trabalho. Mas existem alguns outros comandos úteis que podem ser proveitosos durante nossos estudos, vamos ver?
Comandos úteis e workflow do JJ
Ok, está claro que o JJ é semelhante ao Git, mas também possui seus comandos e conceitos específicos. Vamos ver alguns deles enquanto aprendemos mais sobre seu fluxo, como em qualquer outro sistema de controle de versão. Para ver essa ideia na prática, vamos primeiro testar alguns comandos abaixo:
jj duplicate change-id: autoexplicativo. Podemos testá-lo com nossa alteração nntrkurr:
O log agora mostra a duplicação:
Ele mostra em qual estamos atualmente com o “@”. Legal, né?
jj diff -r (a range here): Este comando permite ver a diferença entre um grupo de alterações usando seus change ids. Teste em seu terminal e veja o diff:
jj config set --user operation.hostname "anything"ejj config list operation, que permitem alterar o hostname e listar sua configuração, respectivamente. Lembre-se de que a alteração do hostname terá efeito a partir do momento em que for feita em diante. Alterações passadas ainda terão o hostname antigo, por exemplo. Podemos checar o hostname exibido após executarjj op log, que por sua vez nos mostra o log de operações feitas com o JJ:
Agora, se você quiser atualizar o autor em um intervalo de alterações, pode executar:
❯ jj metaedit --update-author -r 'id..@'E, no caso acima, atualizamos todas as alterações começando pela cópia de trabalho. Se quiser atualizar tudo: jj metaedit --update-author -r 'all()' (Note o uso de uma função revset aqui, all()). Lembre-se de que o hostname antigo ainda estará acessível no log de operações, por exemplo, já que o JJ armazena essa informação mesmo após mudanças de config.
Vamos explorar outro comando agora: jj squash.
Primeiro, vamos descrever uma alteração vazia, vou chamá-la de "Log goodbye and hello".. Depois, vamos criar outra alteração com jj new. Em resumo, descrevemos uma alteração vazia e seguimos para começar a trabalhar em outra.
Então, faça a modificação descrita na alteração anterior que cria uma função que loga “goodbye” e “hello” em um de seus arquivos, como o arquivo script.js, por exemplo.
Agora vamos fazer squash. Faremos isso pelo terminal com o comando:
Note que descrevemos o que íamos fazer e então, depois de criar outra alteração e finalizar a feature, usamos o squash para passar nossas modificações para a alteração anterior. Esse fluxo é chamado de The Squash Workflow. É um workflow mais semelhante ao do Git, já que emulamos a ideia de ter um tipo de “staging” antes de finalizarmente alteração.
Assim, usamos o commit pai como o commit que realmente receberá as alterações que fizemos na cópia de trabalho.
Além disso, há outra maneira de fazer squash se você estiver acompanhando seu workflow no VisualJJ também. Abra a aba da extensão e veja que há um botão que nos permite fazer o squash pela interface:
Você também pode escolher arquivos específicos para fazer o squash. Então jj squash script.js funciona. O comando jj squash -i permitirá que você use seu editor de texto padrão para escolher quais arquivos incluir no squash! Tente modificar mais de um arquivo e teste em seu terminal!
Mas e se eu quiser me livrar do que fiz até agora na minha cópia de trabalho? O comando para isso é jj abandon. Ele removerá todas as alterações feitas, então tome cuidado.
Agora, vamos explorar como criar alterações antes da atual, já que o workflow não linear acontece constantemente quando lidamos com versionamento. Vamos ver como o JJ lida com esses casos:
Primeiro, verifique se você está em uma alteração vazia, adicione a ela uma descrição relacionada a logs, como "Hello, world!".. Quando estiver satisfeito com o código que fez, crie uma nova alteração com jj new.
Abaixo, crio uma nova alteração após ter criado uma pequena função que loga a frase desejada:
Agora, "Printing hello world" tornou-se o commit pai.
Se checarmos nosso VisualJJ, vemos:
Mas e se quisermos adicionar alguns comentários ao código antes da alteração atual? É simples! Podemos executar: jj new -B @ -m "comments description here!".
Veja abaixo:
❯ jj new -B @ -m "Even more comments"
Rebased 1 descendant commits
Working copy (@) now at: pyrpxqlz 238e34f4 (empty) Even more comments
Parent commit (@-) : lowkruxs aa3e6872 Printing hello world
❯ jj new -m "Documentation - comments"
Working copy (@) now at: wqylpmwv bef37a76 (empty) Documentation - comments
Parent commit (@-) : pyrpxqlz f1d8cba6 Even more commentsAcima, vemos que um rebase acontece automaticamente quando criamos uma alteração antes da cópia de trabalho: "Rebased 1 descendant commits".
Essa ação sempre terá sucesso devido à forma como o Jujutsu lida com conflitos e rebases. No JJ, os conflitos fazem parte do histórico, são armazenados e podem ser resolvidos posteriormente, como mencionado em parágrafos anteriores. No entanto, em nosso caso simples, não houve conflitos. Veremos mais sobre conflitos e rebases na parte 2!
Além disso, na imagem acima, iniciei uma nova alteração para documentation agora. Mas e se precisarmos fazer alguma refatoração antes de escrever nossa documentação? Por exemplo, talvez queiramos remover algumas funções não utilizadas. Vamos usar a flag before "-B" novamente e criar uma alteração antes da cópia de trabalho:
❯ jj new -B @ -m "Removal of old functions"
Rebased 1 descendant commits
Working copy (@) now at: ylmyvtts eb701ce9 (empty) Removal of old functions
Parent commit (@-) : pyrpxqlz f1d8cba6 Even more commentsVamos ver como nosso workflow fica no VisualJJ:
Legal! O que fizemos é geralmente chamado de "The Edit Workflow", que foca em criar novas alterações antes da principal quando uma tarefa grande precisa ser dividida em funcionalidades menores, por exemplo.
Você pode escolher o fluxo que melhor se adapta ao seu projeto, claro. Tanto o workflow Edit quanto o workflow Squash são úteis.
Agora, vamos analisar como criamos bookmarks no Jujutsu. E lembre-se que bookmarks são basicamente commits diferentes com o mesmo pai. Observe o pequeno workflow abaixo:
Você deve ter notado que mais dígitos começaram a ter a cor de destaque nos commit e change ids. Isso serve apenas para nos ajudar a entender que há mais de um com o mesmo dígito inicial, por exemplo. No caso acima, criamos uma nova alteração a partir de wqy; se tivéssemos digitado apenas wq, haveria duas opções de id semelhantes, já que temos wqlpytzw e wqylpmwv, veja abaixo:
❯ jj new wq
Error: Change ID prefix wq is ambiguousOk, vamos checar nosso workflow agora:
Legal! Criamos nosso primeiro bookmark. Isso nos permite trabalhar em uma funcionalidade enquanto o projeto pode continuar crescendo. Você pode saltar para diferentes commits com jj edit id e explorar a criação de mais e mais bookmarks! Lembre-se de que você não conseguirá executar jj edit zzz, pois ele é imutável e é o resultado de um revset:
❯ jj edit zzz
Error: The root commit 000000000000 is immutableContudo, com esses comandos iniciais, você já é capaz de trabalhar com o JJ em pequenos projetos de estudo e experimentar seus comandos!
Próximos passos
Vimos que o JJ é, no mínimo, uma ferramenta que vale a pena estudar e experimentar. Mesmo que os projetos reais de seus clientes não o usem oficialmente, você pode testá-lo! Dessa forma, a facilidade de usar o Jujutsu pode se tornar mais clara à medida que você o utiliza em mais repositórios.
Como esta ferramenta está em seus estágios iniciais, novos recursos interessantes ainda serão adicionados, tornando a curva de aprendizado mais orgânica ao começar cedo.
Mas vimos apenas os conceitos iniciais do JJ aqui! Eles são ótimos comandos para iniciar seus estudos e criar pequenos repositórios. No próximo post, analisaremos alguns comandos mais complexos. Dessa forma, poderemos aprimorar nosso uso do JJ e expandir nosso conhecimento sobre o comportamento geral das ferramentas de controle de versão.
Referências
- Jujutsu Version Control Explained
- Solving Git’s Pain Points with Jujutsu (with Martin von Zweigbergk)
- Introduction – Steve’s Jujutsu Tutorial
- Introduction – Jujutsu for everyone
We want to work with you. Check out our Services page!

