Вступление
Если ты здесь, ты наверняка знаешь, что такое git. И да, не спорю - это офигенная штука. Деды знали, что писали.
Но я долгое время работал над небольшими проектами и был там единственным разработчиком. Когда перешёл в большую команду, пришлось глубже вникнуть в git.
И тут началось: я стал тратить кучу ресурсов на постоянные вопросы:
Нужна ли отдельная ветка или нет?
Merge или Rebase?
Какой
revert
использовать?В каком статусе сейчас файл?
Где вообще находится header?
А ещё каждый коммитил как хотел. В итоге история проекта - это каша: понять, что, когда и зачем сделал человек, просто невозможно.
Я начал гуглить best practices, читать про git flow, пытался навести порядок. Но всё равно слишком много времени уходило не на код, а на борьбу с системой контроля версий.
И вот я наткнулся на Jujutsu (jj). И хочу рассказать тебе, чем он меня зацепил.
О сути jj
Основная идея jj - "У нас нет веток. У нас есть изменения".
Погоди, сейчас поясню.
Сразу скажу: всё это полностью совместимо с git. Так что можно просто взять и начать использовать jj прямо сейчас.
Пример боли
Предположим, мы работаем по классике: feature-branch.
Надо её как-то назвать. Начинаю в ней работать.
После каждого осмысленного шага - коммит. Ещё один. И ещё.
# Работаем над фичей 'new-feature'
git checkout -b new-feature
# Первый коммит
# ... код ...
git add .
git commit -m "feat: Add initial user authentication"
# Второй коммит
# ... код ...
git add .
git commit -m "refactor: Improve auth validation"
# Третий коммит
# ... код ...
git add .
git commit -m "fix: Fix minor typo in error message"
А потом замечаем: ошибка была в самом первом коммите. В feat: Add initial user authentication
.
Что делать?
Создавать новый? Делать rebase -i
?
Переключаться, править, переносить руками.
# Как исправить ошибку в первом коммите, не затрагивая последующие?
# Вариант 1: rebase -i
git rebase -i HEAD~3 # Ищем коммит, меняем 'pick' на 'edit', правим, continue
# Вариант 2: revert первого коммита, потом новый коммит с исправлением
git revert <hash_первого_коммита> # Создает новый коммит, отменяющий изменения
# ... потом fix ...
git commit -m "fix: Actually fix initial user auth"
# Вариант 3: reset до первого коммита, теряя все последующие
git reset --soft <hash_первого_коммита>
# ... потом все изменения из второго и третьего коммита в staging ...
# ... а потом перекоммичивать все заново ...
О, а тут ещё начальник прибегает с криком: "hotfix срочно!"
А я не могу у меня лапки конфликт.
Не знаю, как у вас, у меня такое регулярно.
Вследствие чего ты тратишь кучу сил на то, чтобы угомонить git. Какая версионность? Какие правильные коммиты? Какой качество код? Вы вообще о чем?
А теперь jj
Пишешь код, коммит за коммитом.
# Никаких веток
# Первый коммит
# ... код ...
# никаких `add`
jj commit -m "feat: Add initial user authentication"
# Второй коммит
# ... код ...
jj commit -m "refactor: Improve auth validation"
# Третий коммит
# ... код ...
jj commit -m "fix: Fix minor typo in error message"
Ой, ошибка в первом? Просто переходишь к нему, правишь, возвращаешься - готово.
jj edit <rev> # перходим на нужный commit
# ... правим ...
jj edit <rev> # возвращаемся к работе обратно
История переписалась, текущие изменения не потерялись.
Всё.
Я реально сижу и думаю: "А точно всё? Я что-то, слишком мало команд ввёл?"
Ты не думаешь:
"А какую merge-стратегию выбрать?.."
Ты думаешь:
"Как сделать код лучше?"
Конфликт? Hotfix? - без паники
Окей, допустим, случился конфликт.
Начальник снова кричит: "Фикс срочно!"
Без проблем - просто переключаюсь на старую версию, делаю hotfix.
jj edit <rev> # перходим на нужный commit
# ... hotfix ...
jj edit <rev> # возвращаемся к работе обратно
Конфликты? Потом разберёмся. jj позволяет не тормозить разработку.
Никаких stash
, rebase
, checkout -b
, "а где HEAD?".
Где же ветки?
В jj вместо веток - закладки (bookmarks).
Представь, что ты ведёшь дневник.
Вот ты пишешь, тебе нравится, как получилось - ставишь закладку prod
.
Пишешь дальше. Получилось неплохо, но не уверен - ставишь dev
.
Думаю, суть вы уловили.
# Создаем закладку на текущем коммите
jj bookmark prod
# Работаем дальше, создаем новые коммиты
jj commit -m "feat: More features"
# Создаем еще одну закладку
jj bookmark dev
# Смотрим закладки
jj bookmark list
# Переносим закладку на последний commit
jj bookmark move prod --to=@-
Основные понятия
Модель репозитория
Репозиторий Jujutsu - это направленный ацикличный граф (DAG), в котором каждый узел - это изменение(change), содержащее:
Снимок файловой системы в директории репозитория.
Конфликты файлов (локальны, не блокируют работу, в отличие от Git).
Один или несколько родительских изменений (корневое не имеет родителей).
Описание изменения (commit message).
Дополнительно:
"Изменение" в JJ - это аналог "коммита" в Git (но с более стабильным ID).
Одно из изменений - рабочее (
@
), аналогичноHEAD
в Git.Закладки (bookmarks) - уникальные строки, ссылающиеся на изменения, для Git это
branch
.Поддержка удалённого репозитория - закладки синхронизируются как
BOOKMARK@REMOTE
.
Основные правила
При перемещении
@
рабочая директория обновляется.Если удалить изменение, на которое указывает
@
, создаётся новое пустое изменение.Изменения без файлов, описания и ссылок исчезают.
Изменение - это diff. Перемещение может вызвать конфликты.
Почти все команды действуют на
@
по умолчанию, но могут принимать--revision
.
Конфликты файлов
Чтобы разрешить конфликт, достаточно отредактировать файл и убрать маркеры (
<<<<<<<
,=======
,>>>>>>>
).Для бинарных файлов - замените файл нужной версией.
Используйте
jj restore
, если нужно откатить изменения.
Работа с удалённым репозиторием
jj git fetch
Получает изменения из удалённого репозитория.
Несовместимые закладки создают конфликт закладок (аналог merge conflict).
Как разрешить:
Слить изменения:
jj new CHANGE-ID-1 CHANGE-ID-2
, затемjj bookmark move BOOKMARK-NAME
.Выбрать одно:
jj bookmark move BOOKMARK -r CHANGE-ID
.Сделать rebase:
jj rebase -b CHANGE-ID-2 -d CHANGE-ID-1
, затемjj bookmark move
.
jj git push
Отправляет изменения в удалённый репозиторий.
Изменённые изменения создаются заново (аналог
--force
).Основная ветка защищена - изменения нужно явно пушить с
--ignore-immutable
.jj git push -c @
создает новую временную закладку (git автоматически предложит создать MR)
Про закладки:
Локальные закладки копируются в удалённый репозиторий.
Если закладка не синхронизирована -
push
выдаёт ошибку, нужно сначала сделатьjj git fetch
.
jj bookmark track
Связывает локальную закладку с удалённой веткой.
Отслеживать изменения из удалённого репозитория при
jj git fetch
, упрощатьpush
и автоматически разрешать конфликты закладок.-
Синтаксис:
jj bookmark track <локальная_закладка> <удалённая_ветка@удалённый_репозиторий>
jj bookmark track <имя_ветки>
(если локальная и удалённая ветки совпадают)
-
Пример:
jj bookmark track develop develop@origin
-
Просмотр:
jj bookmark list --tracked
(показать только отслеживаемые)
Команды настройки
jj config set --user user.name МОЁ_ИМЯ
jj config set --user user.email МОЙ_EMAIL
jj config set --user ui.editor МОЙ_РЕДАКТОР
jj config edit --user # Открыть конфиг
Вместо
--user
можно использовать--repo
для конфигурации внутри конкретного репозитория.
Команды репозитория
jj git init # Инициализация репозитория
jj git clone URL [DEST] # Клонирование репозитория
jj git init --colocate # Добавление JJ в существующий git-репозиторий
Редактирование локального репозитория
Команда |
Описание |
---|---|
|
Показать важные изменения |
|
Статус рабочего изменения, родитель, изменённые файлы |
|
Отменить последнюю команду |
|
Создать новое изменение |
|
Задать описание |
|
Показать описание изменения |
|
Показать все закладки |
|
Подключиться к удаленной ветке |
|
Создать закладку |
|
Удалить закладку |
|
Переместить закладку |
|
Переместить закладк на указанное изменение |
|
Переименовать закладку |
|
Отредактировать указанное изменение (перенсти @ на q) |
|
Восстановить файлы из другого изменения |
|
Создать обратное изменение |
|
Отказаться от изменения |
|
Показать разницу между изменениями |
|
Объединить изменения |
Более подробно читаем в документации
Примеры живой работы с jj
Давайте посмотрим, как jj
справляется с типичными задачами, используя только свои команды и концепции.
Тут я решил показать возможности revsets
jj log -r "@ | bookmarks() & author('Ads')"
:

jj log
показывает историю изменений в репозитории. Каждый блок соответствует одному изменению (коммиту) и содержит несколько ключевых элементов:
Рабочая копия - на нее указывает
@
○
(локальное изменение) - это то, что вы можете свободно менять.◆
(неизменяемое) - это коммит, который вы уже отправили на удалённый сервер и трогать его не стоит (хотяjj
позволяет и это с флагом-ignore-immutable
).ID изменения - уникальный короткий идентификатор, например
szqumyoy
,szq
- alias для данного изменения.Автор и email - кто сделал изменение.
Дата и время - когда было сделано изменение.
Закладки (
bookmarks
) и/или ветки - метки, указывающие на изменение, напримерmaster
илиmaster@origin
.Локальная закладка - те, что ещё не синхронизирована с удалённым репозиторием имеют символ
*
ID Git-коммита - хэш коммита в Git (для совместимости). Например,
59d9790f
.Сообщение коммита - описание сделанных изменений.
~ (elided revisions)
- Это пропущенные изменения.jj
иногда скрывает их в данном случае из-за "фильтров".
jj status
(alias st
):

Здесь мы видим какие изменения сейчас есть в нашей
рабочей копии
.Буквы
A
,M
означают тип изменения файла.Тут же мы видим ссылку на:
@
- это изменение и@-
- родителя.
Из скриншота видно, что тут явно 2 вида изменений. Я хочу чтобы было красиво.
jj split
:

Открывается diff-editor
- это мощный инструмент, который позволяет работать с изменениями, а не с файлами. Он помогает поддерживать чистую и понятную историю, не прибегая к сложным манипуляциям.

Выбираем нужные нам изменения и жмем c
(Это тут такое управление, если что можно все делать мышкой)

И попадаем в интерфейс jj commit
, для ввода description

jj split
автоматически разбил изменения на 2 коммита и выстроил их в линейку, посмотрим на jj
.

Перенесем закладку на новые изменения. Переносить на @ нельзя там пусто, так что --to=@-

Workflow: "Черновики" и их "уборка"
Лично мне очень нравится workflow, когда я пишу код, быстро сохраняю свои наработки и пишу дальше. В моей голове это "чек-поинты", к которым я в любой момент могу вернуться.

Затем, когда функционал реализован, я привожу историю в порядок:
jj squash
- объединяю все "черновые" коммиты в один.jj split
- разделяю одно большое изменение на несколько логичных. В итоге история становится чистой и понятной.

Можно заметить что id vxzxpzxm
, ktvwvoqs
не поменялись, а их git-hash изменился.
Push и автоматический MR
jj
умеет работать с удалёнными репозиториями очень элегантно. Например, jj git push -c @
не просто отправляет изменения, но создает отдельную новую ветку и сразу предлагает ссылку на создание Merge Request, если ваша система это поддерживает. Но можно и просто:

И вновь jj
удивляет и сразу предлагает ссылку на MR
Финал
Jujutsu
- это не замена Git, а его улучшенная оболочка. Он решает многие проблемы, которые так раздражают в классическом Git:
Грязная история и мучительный
rebase -i
.Сложности с
git add
иgit stash
.Страх "сломать" репозиторий, ведь у
jj
естьjj undo
.
Если вы хотите освободить свой мозг для написания кода, а не для борьбы с системой контроля версий, попробуйте jj
. Просто попробуйте, если что, вы всегда сможете вернуться на Git.
Комментарии (9)
artptr86
18.08.2025 11:26Для начала JJ работает поверх бекенда Git, а это уже опасно в плане инкапсуляции особенностей поведения JJ, поскольку другие сотрудники и утилиты ничего о JJ не знают.
Без проблем - просто переключаюсь на старую версию, делаю hotfix.
Изменение старого коммита перепишет вам историю в Git. Под капотом там скорее всего что-то вроде
git checkout <commit> git reset --soft <ожидание коммита> git rebase --onto ...
Даже если JJ позволяет вам забить на конфликты, они неизбежно возникнут у других сотрудников и на серверах контроля версий просто из-за изменения истории.
jj git push -c @
не просто отправляет изменения, но создает отдельную новую веткуgit push -u origin <название>
сделает то же самое.и сразу предлагает ссылку на создание Merge Request, если ваша система это поддерживает
Это фича сервера контроля версий, а не JJ.
AnthonyAxenov
18.08.2025 11:26Свистоперделка для поиграться. Она даже не всё умеет, что умеет гит, но уже преподносится как что-то крутое и революционное, что уже можно использовать в работе.
Гит стал стандартом спустя много-много лет и много-много боли (перешедшим с других vcs). За это время накопились тонны информации по его косточкам. Может быть, на нашем веку эта зумерская погремушка ещё успеет стать рабочим инструментом, но её ждёт трудный путь.
mynameco
18.08.2025 11:26когда нибудь, гит сделают с встроенной поддержкой бинарных файлов больших размеров. со встроенной поддержкой работы со срезами, как в свн и поддержкой папок. и жизнь наладится.
PetyaUmniy
18.08.2025 11:26Весьма маловероятно.
Это крайне плохо ложится на концепцию локального репозитория. В котором все участники разработки будут хранить одновременно все версии всех блобов.
А вот добавить поддержку сбоку, этакий lfs на максималках, наверное могли бы.
maxim_ge
18.08.2025 11:26А потом замечаем: ошибка была в самом первом коммите. В feat: Add initial user authentication.
Что делать?
Не знаю, как у вас, у меня такое регулярно.Коммитить fix в ветку new-feature а потом pull request + squash.
Вот тут так делали регулярно.
О, а тут ещё начальник прибегает с криком: "hotfix срочно!"
Ну в новую ветку переключаешься да и всё. Возможно, я чего-то не понял...
Mayurifag
Спасибо за статью и отдельное за понятный пример, зачем читателю эта штука. А то когда узнал впервые про jj и зашёл на сайт, сходу не понял, да и закрыл за ненадобностью кажущейся. Но тут дело скорее во мне, уже с гитом привык работать, в зубодробительных ситуациях переключаюсь на GUI какой-нибудь, опыта достаточно набил за годы.
Мне ещё понравился их log, — чуть репрезентативнее сравнение можно прямо в репозитории проекта посмотреть, — в сравнении с тем, что стандартно в git.
Ну и чтобы пользу комментарий какую-никакую нёс, вот что спрошу. Тем кто гитхуками пользуется, тяжело будет пересаживаться? Из коробки есть поддержка, не проверяли на своих задачах? Надеюсь бесшовно работают. Без гитхуков очень многим ненужно будет (да и с ними, хе-хе).