Если тебе не нравится Jujutsu - ты не прав.
Как и все разработчики, я пользуюсь git с начала времён - с тех пор, как его команды были непостижимым набором плохо сочетающихся заклинаний. И таким, по большей части, он остаётся по сей день. Не нужно и говорить, что я просто не понимаю git. Никогда не понимал, хотя прочитал кучу материалов о том, как он всё это внутри представляет. Годами я пользовался им, зная, что делают несколько команд, и каждый раз, когда git входил в какое-то странное состояние из-за того, что я промахнулся по клавише, у меня был мой надёжный алиас fuckgit, который удаляет директорию .git, клонирует репозиторий заново во временную папку, переносит оттуда .git в мою директорию - и так я как-то ухитрялся зарабатывать на жизнь и кормить семью.
За последние несколько лет я всё чаще видел, как люди восторгаются Jujutsu, и всегда хотел попробовать, но мне казалось, что возни слишком много - даже несмотря на то, что я ненавижу git. Я лениво читал несколько туториалов, пытаясь понять, как это работает, но в итоге решил, что это не для меня.
Однажды я случайно решил попробовать снова, но на этот раз попросил Claude объяснить, как сделать в Jujutsu то, что я хотел сделать в git. И вот тогда в моей голове наконец сложилась ментальная модель jj - и я понял всё. Даже то, как работает git. Никогда бы не подумал, что система контроля версий может приносить радость, но вот мы здесь. И я решил, что, может быть, смогу написать что-то, что заставит jj «щёлкнуть» и у тебя.
Кроме того, не помешает, что Jujutsu полностью совместим с git (а значит - и с GitHub), и я могу использовать всю мощь Jujutsu локально в своих git-репозиториях, не выдавая никому, что я вообще-то не использую git.
Постановка вопроса
Проблема, которая у меня была с другими туториалами (и которую я тогда не осознавал), заключалась в том, что между двумя базовыми вещами существует фундаментальное противоречие: лучший способ объяснить jj человеку, который уже знает git, - это использовать все знакомые ему git-термины (так ведь проще), но при этом нужно, чтобы он начал думать об этих знакомых терминах по-другому (иначе он сформирует неправильную ментальную модель).
Ты не можешь по-настоящему объяснить что-то, говоря: «Коммит в jj - это как коммит в git, только не совсем», - поэтому я попробую поступить немного иначе.
Этот пост будет коротким (или, по крайней мере, не таким длинным, как другие туториалы по jj). Я объясню тебе общую ментальную модель, которую стоит иметь в голове, а потом приведу раздел ЧАВО - как делать разные git-вещи в jj.
Предупреждения
Просто небольшое замечание перед началом: это далеко не исчерпывающая справка. Я не эксперт ни в git, ни в Jujutsu, но знаю достаточно, чтобы, возможно, заставить jj «щёлкнуть» у тебя в голове настолько, чтобы ты потом смог изучить остальное сам. Так что не злись, если я что-то опущу.
Кроме того, ты сейчас прочтёшь кое-что о том, как Jujutsu предпочитает делать вещи - и это, возможно, оскорбит тебя до глубины души. Твоя первая реакция будет: «Безумие! Это не может работать!» Когда ты это подумаешь, я хочу, чтобы ты расслабился - всё в порядке, оно действительно работает. Просто это значит, что я пока не сумел заставить всю картину сложиться у тебя в голове. Просто читай дальше.
Я не собираюсь показывать тебе никаких команд Jujutsu. Я, возможно, буду упоминать их по именам, но хочу, чтобы ты понял модель мышления достаточно хорошо, чтобы сам мог всё найти. У Jujutsu вообще-то всего три команды, которыми ты будешь пользоваться для всего подряд (да, с их помощью можно делать всё то же, что и с git).
(Кстати, если ты собираешься пробовать команды по ходу чтения этого поста, обязательно поставь jjui - она позволяет визуально работать с репозиторием, и тогда всё станет гораздо проще понять.)
Общая ментальная модель, которую тебе стоит иметь
Прежде всего: все базовые git-вещи, с которыми ты уже знаком, есть и в jj. Коммиты, ветки, операции над ними - всё это сохраняется, с некоторыми небольшими отличиями.
Главное различие - в том, как эти две системы вообще устроены. jj сильно упрощает модель git, избавляясь от некоторых несоответствий, и делает её намного понятнее «под капотом». А всё потому, что «под капотом» теперь настолько мало и просто, что это может буквально лежать над капотом.
git
Ментальная модель, которую, вероятно, ты имеешь с git, выглядит примерно как конвейер.
Ты берёшь кучу деталей, собираешь из них виджет, кладёшь этот виджет в коробку, пишешь на коробке «Общие исправления багов», заклеиваешь и отправляешь - навсегда, чтобы никто больше никогда не видел его.
Это то, что git считает коммитом. У тебя есть некая работа - «то, над чем ты сейчас работаешь», - и в какой-то момент она вроде как закончена: ты выбираешь, какие куски этой работы хочешь увековечить, и делаешь коммит, навсегда застывший во времени.
(Я знаю, что в git можно редактировать коммиты, но в целом его ментальная модель такова: коммиты неизменны.)
Jujutsu
Jujutsu, напротив, больше похож на игру с пластилином.
Ты берёшь кусок, разрезаешь его пополам, формируешь из одной части что-то, даёшь этому имя, передумываешь, даёшь другое имя, отрываешь кусочек от второй половины и прилепляешь к первой - и вообще, ходишь туда-сюда по всей площадке, меняя всё как хочешь.
Jujutsu хочет, чтобы ты мог вернуться к старому коммиту, изменить его (о, ужас!), перейти на другую ветку (три коммита назад от текущего HEAD), изменить и тот коммит, переместить целые ветки дерева в другие его части - в общем, делать, что угодно. Твоя рабочая директория в Jujutsu - это свободная зона, где можно переставлять всё как душе угодно.
Безумие, это не может работать
Да-да, никто не хочет, чтобы коммиты менялись у него под ногами - именно поэтому Jujutsu не позволяет легко менять коммиты, уже отправленные на удалённый репозиторий. Так что можешь расслабиться.
Однако, если ты на секунду подумаешь о том, что я сказал выше, то, вероятно, осознаешь: чтобы всё это работало, нужно изменить несколько вещей (и они действительно изменены).
Коммиты должны быть изменяемыми
И правда - коммиты в Jujutsu изменяемы (до тех пор, пока ты их не запушил). Сейчас ты, вероятно, думаешь о коммитах как о чём-то, что нельзя трогать, но это одна из вещей, которые нужно принять.
Ты можешь (и будешь) возвращаться к предыдущему коммиту (который ты ещё не запушил), чтобы исправить в нём баг, на который только что наткнулся, - и это так же просто, как «чекаутнуть» (в jj это называется редактировать) этот коммит и внести изменения. Тебе не нужно делать новый коммит! Jujutsu делает всё необходимое под капотом, когда ты вызываешь команду jj, - а тебе просто кажется, что твои изменения автоматически сохраняются в коммите в реальном времени.
Чтобы прояснить: Jujutsu не создаёт новых коммитов во время этой работы - ты просто видишь один «открытый» коммит, в котором продолжаешь менять код.
Если я могу просто войти в коммит, отредактировать его, и jj всё авто-сохраняет, з��ачит, staging area (области подготовленных изменений) не существует?
И действительно, staging area, такой как в git, не существует. В git код делится на то, что уже в репозитории (в коммите), и то, что снаружи - staged/unstaged.
В Jujutsu этого нет: ты всегда находишься внутри коммита. Это важно: в git ты находишься вне коммита, пока его не создашь. Еще раз повторимся, в Jujutsu ты всегда внутри коммита. Ничего никогда не существует «вне» коммита - «вне коммита» как понятия просто не существует, понял?.
Даже сама команда commit в Jujutsu - это просто алиас, который добавляет сообщение к текущему коммиту и затем создаёт новый (пустой) коммит, в котором ты будешь дальше работать. Даже когда ты создаёшь новый репозиторий, ты начинаешь уже внутри коммита.
Это - самое важное отличие между jj и git, и единственная вещь, о которой тебе стоит всерьёз подумать, потому что она открывает возможность для множества интересных рабочих процессов.
Постоянно находиться в коммите означает, что да - у тебя будут коммиты с «недоделанной» работой. Возможно, много таких. Обычно я просто помечаю это в сообщении коммита, чтобы самому помнить.
Так значит, у коммитов может не быть сообщения коммита?
Ты поразительно проницателен, мой юный падаван. Все именно так - у коммитов может не быть сообщения.
Они начинаются с пустого сообщения, и ты можешь добавить его в любой момент - когда только появится идея, что вообще делает этот коммит. Это может быть в самом начале работы, где-то посередине или уже в конце. Лично я обычно добавляю сообщение в конце, но это вопрос вкуса.
То есть стэша тоже нет?
Да, ведь всё всегда находится внутри коммита - значит, стэшить нечего (git stash позволяет на время архивировать (или «отложить») изменения, сделанные в рабочей копии).
В git, если у тебя есть какие-то незафиксированные изменения и ты хочешь перейти на старый коммит, тебе нужно сначала их застэшить. В Jujutsu, поскольку все твои изменения автоматически сохраняются в коммите постоянно, ты можешь иметь какие-то новые изменения (которые, если бы это был git, считались бы незафиксированными), перейти (или, как говорит jj, отредактировать) старый коммит, потом вернуться к своим новым изменениям в последнем коммите - и всё там будет.
Но тогда ветки должны быть лёгкими?
Если ты постоянно прыгаешь по дереву, делаешь коммиты и ветки, то они не могут требовать обязательных имён. Jujutsu позвол��ет создавать ветки просто создавая коммит - тебе не нужно давать ветке имя.
В Jujutsu (и в git тоже!) ветки - это просто два или больше коммитов с одним и тем же родителем. Просто git искусственно заставляет тебя думать, будто ветки - это что-то особенное, потому что требует их именовать.
В Jujutsu создать ветку - это просто взять нужный коммит, из которого ты хочешь ответвиться, и сделать на его основе новый коммит. Это один из примеров, где Jujutsu упрощает git.
В git ветки - довольно тяжёлая сущность: их нужно именовать, у тебя есть ментальная модель «нахождения» на ветке, и весь твой рабочий процесс вокруг этого крутится. В Jujutsu ты просто добавляешь новый коммит, и если у этого коммита есть «соседи», то - вот тебе и ветка.
Конфликты
Я пока мало говорил о конфликтах, потому что - в отличие от git - на практике в Jujutsu с ними вообще нет ничего особенного. Jujutsu не «останавливает мир» - она даже особенно не жалуется. Она просто помечает коммит как конфликтный, но ты можешь спокойно продолжать работать в других местах дерева, а потом, когда будет удобно, вернуться и решить конфликты в том коммите.
В то время как git заставляет тебя всё бросить и немедленно разрешать конфликты, jj говорит примерно так:
«Кстати, когда будет минутка - дай знать, как должен выглядеть этот коммит».
Изменения при этом каскадно распространяются на все последующие коммиты, и это великолепно: тебе нужно решать конфликты только один раз, а дальше jj сама разбирается со всем остальным.
Снапшоты
Под капотом jj автоматически и прозрачно делает коммит всего, над чем ты работаешь, каждый раз, когда ты вызываешь любую команду jj. (Можно даже настроить, чтобы она делала это сама - каждый раз, когда в репозитории меняется файл.)
Это безопасно, потому что эти промежуточные изменения никуда не пушатся. Но это значит, что ты получаешь снапшоты бесплатно!
Если у тебя когда-нибудь бывало, что Claude дошёл до рабочего решения, а потом всё испортил, jj может помочь: ты можешь использовать oplog (журнал операций), чтобы вернуть репозиторий в то состояние, в каком он был несколько минут назад - даже если ты явно ничего не коммитил!
Даже просто вызов status или log, чтобы посмотреть, что происходит, создаёт снапшот твоего репозитория - и ты можешь вернуться к нему, если что-то пошло не так. Больше никаких потерянных unstaged-изменений, никогда!
Это уже несколько раз спасало мою задницу.
ЧАВО (частые вопросы, FAQ)
К этому моменту у тебя, вероятно, есть уже куча других вопросов - я попробую ответить хотя бы на некоторые из них здесь. Если у тебя появятся ещё, просто пришли их мне - я добавлю их сюда вместе с ответом.
Как мне создать ветку от main?
Ты, строго говоря, не создаёшь ветку от main - в том смысле, что обычно тебе не нужно делать два коммита от main; ты просто делаешь один.
git
Вот как git думает о ветках: ты создаёшь ветку - и теперь ты как бы находишься в особом «веточном» измерении.

В git мы говорим «ответвиться от main», и теперь наша ментальная модель такова: мы на этой ветке. На самом же деле, если посмотреть на граф справа, всё всё ещё остаётся одной прямой линией - мы просто мысленно «согнули» её, чтобы убедить себя, будто теперь мы «в другой ветке».
Но для самого графа ничего особенного не произошло: мы просто добавили больше коммитов. Единственная реальная разница в том, что main останавливается на третьем коммите, а «моя ветка» - на шестом. Помимо этого, вся история остаётся одной линией.
Jujutsu

Вот как о ветках думает jj. Оба графа эквивалентны, но в Jujutsu ты нигде не находишься «особо»: main просто немного дальше назад. Jujutsu, в отличие от git, не заботится о том, что ты себе там думаешь. Её волнует только одно - какие коммиты являются родителями, потомками и «соседями».
Есть две причины, по которым ты можешь захотеть создать ветку:
История действительно расходится в разные направления, или
Ты хочешь дать понять другим людям (или самому себе), что эта часть истории - другая (например, содержит новую фичу). Это также тот случай, когда ты создаёшь ветку, чтобы потом открыть для неё PR (запрос на слияние).
Для Jujutsu история этого репозитория - просто прямая линия, никакого настоящего «ветвления» нет. Единственная причина, по которой нужны ветки, - это коммуникация, поэтому Jujutsu просит тебя самостоятельно пометить коммиты, которые ты хочешь видеть на ветках. Ты можешь видеть эти метки на примере справа - и это то же самое, что и в git-примере выше: всё ещё есть три коммита в main и ещё три в «моей ветке».
Jujutsu называет такие метки bookmarks (закладки), и они соответствуют тому, что git использует для пометки веток. Закладки - это то, чем ты помечаешь свои коммиты, чтобы сказать git, где находятся твои ветки. Если граф действительно разветвился - например, потому что у main появился ещё один коммит - то это уже настоящее ответвление.

main появился один дополнительный коммит.Продолжая предыдущий пример: если мы создадим второй коммит от main, даже если это будет merge-коммит (то есть коммит с двумя родителями), - вот тогда дерево действительно расходится. На графе справа коммит, от которого мы ответвились, теперь имеет двух потомков, и история перестаёт быть линейной. Это не что-то особенное - просто так устроено дерево. Но именно это Jujutsu и считает настоящим ветвлением.
Способ, которым git создаёт ветки (т.е. без реального разветвления истории), существует лишь ради нас, людей, и наших потребностей в коммуникации.
Jujutsu не требует, чтобы ветки имели имена. Ты можешь спокойно работать вообще без названий веток, и при этом легко понимать, что где находится - просто глядя на описания коммитов. Если хочешь, можешь именовать ветки, но не обязан.
Сейчас это звучит немного странно, но на деле - это очень удобный способ работы.
Если ты ментально немного потерялся к этому месту в статье, то это не страшно. Ты быстро всё поймёшь, когда немного поиграешь с деревом в jjui.
Как добавить сообщение коммита?
Ты можешь добавить сообщение коммита в любой момент, используя команду describe.
Это можно сделать когда угодно - даже вернуться к старым коммитам и изменить их сообщения (опять же, с помощью describe).
Как выбрать, какие изменения закоммитить?
Никак! Все изменения уже находятся в коммите! То, что ты делаешь, - это интерактивно выбираешь некоторые изменения из текущего коммита (неважно, это новый пустой коммит или старый), и разделяешь этот коммит на два.
Jujutsu может даже сделать это автоматически! Если у тебя есть коммит с кучей мелких изменений в разных файлах, jj может «впитать» эти изменения в ближайший коммит-предок, где каждая строка когда-то менялась. Это выглядит почти магически: ты можешь накидать по одному-двум фиксам багов в разных местах - и jj сама разнесёт их по соответствующим коммитам, где эти строки впервые были тронуты.
Как «чекаутнуть» коммит?
Не вдаваясь в подробности, просто редактируй нужный коммит. Так ты его и «чекаутишь»: можешь вносить в него изменения. Но имей в виду - если этот коммит уже был запушен на удалённый репозиторий, jj предупредит тебя, что не стоит редактировать коммиты, которые уже отправлены.
jjui делает навигацию по репозиторию очень простой, так что используй её и для переключения между коммитами.
Как сделать выборочно перекинуть коммит на другую ветку?
Ты просто перемещаешь его. В jjui всё просто: находишь коммит, который хочешь переместить, нажимаешь r (от слова rebase), идёшь к коммиту, после которого хочешь его поместить, нажимаешь Enter - и всё.
Как сделать мягкий/жесткий сброс?
На самом деле, мягкого сброса (soft reset) в Jujutsu как такового нет - ведь здесь нет staging area, куда можно было бы «сбросить» изменения. Чтобы сделать что-то вроде мягкой перезагрузки, просто отредактируй нужный коммит. Вот и весь «мягкий сброс» в Jujutsu.
Для жёсткого сброса (то есть чтобы полностью выкинуть коммит) - откажись от него (abandon). Опять же, в jjui это делается очень легко.
Что, если я что-то испорчу?
Неважно, что ты сделал - ты можешь всё отменить. Не только изменения, но вообще любую операцию в jj: можно отменить rebase, pull - вообще всё.
Ты также можешь использовать oplog (и снова, да - в jjui это очень просто), чтобы вернуть репозиторий в то состояние, в каком он был в любой момент времени. Так что не бойся экспериментировать - в Jujutsu крайне легко отменить любую ошибку.
Как исправить коммит?
Просто отредактируй его и внеси нужные изменения. Всё.
Как переместить «unstaged»-изменения с одной ветки на другую?
В Jujutsu нет unstaged-изменений. Все изменения всегда находятся внутри коммита. Если ты хочешь перенести изменения из текущего коммита на другую ветку - просто перемести сам коммит на целевую ветку при помощи rebase. Я никогда не запоминаю, что именно значит «rebase X onto Y», поэтому просто перемещаю коммит с изменениями так, чтобы он стал потомком вершины нужной ветки. (И снова - используй jjui, там это делается парой нажатий.)
Как открыть запрос на изменение (pull request) на GitHub?
Чтобы это сделать, тебе нужно запушить новую ветку. Перейди к коммиту, который хочешь запушить, затем, возможно, создай новый коммит поверх него (я обычно создаю новый коммит, когда заканчиваю работу над старым - просто чтобы самому понимать, что закончил,
но это личное предпочтение). После этого добавь bookmark (закладку) на этот коммит с именем ветки, которую хочешь дать своему PR, и запушь коммит вместе с этой закладкой.
И всё - теперь ты можешь открыть PR.
Здесь jj показывает тебе низкоуровневые операции гораздо прозрачнее, чем git:
тебе нужно самому переместить bookmark на нужный коммит (git делает это автоматически),
и самому запушить bookmark. Это помогает лучше понять, как всё работает «под капотом»,
но обычно ты просто создаёшь себе алиас в jj, чтобы делать это одной командой.
Лично у меня есть такой алиас (ниже я его покажу), который находит имя закладки, перемещает её на последний коммит и делает push.
Мои алиасы
Вот мой конфиг с алиасами:
[aliases]
init = ["git", "init", "--colocate"]
ps = ["util", "exec", "--", "bash", "-c", """
set -e
# Проверяем, есть ли у текущего коммита и описание, и изменения
has_description=$(jj log -r @ --no-graph --color never -T 'description' | grep -q . && echo "yes" || echo "no")
# Используем ключевое слово 'empty', чтобы проверить, есть ли изменения
has_changes=$(jj log -r @ --no-graph --color never -T 'empty' | grep -q "false" && echo "yes" || echo "no")
if [ "$has_description" = "yes" ] && [ "$has_changes" = "yes" ]; then
echo "Текущий коммит имеет описание и изменения, создаю новый коммит..."
jj new
fi
# Получаем bookmark напрямую из родительского коммита
bookmark=$(jj log -r 'ancestors(@) & bookmarks()' -n 1 --no-graph --color never -T 'bookmarks' | sed 's/\\*$//' | tr -d ' ')
if [ -z "$bookmark" ]; then
echo "Не найден bookmark у родительского коммита"
exit 1
fi
echo "Перемещаю bookmark '$bookmark' на родительский коммит и пушу..."
jj bookmark set "$bookmark" -r @-
jj git fetch
jj git push --bookmark "$bookmark" --allow-new
"""]
cma = ["commit", "-m"]
Это значит, что я могу запустить jj init, чтобы добавить Jujutsu в git-репозиторий,
и jj cma "сообщение", чтобы описать текущий коммит и создать новый поверх него
(именно это делает команда commit под капотом).
jj ps - это удобный алиас, который:
Идёт назад по истории;
Находит последний bookmark (если бы это был git, это была бы моя ветка);
Проверяет, есть ли изменения в текущем коммите;
Если есть - создаёт новый коммит;
Перемещает bookmark на родительский коммит (тот, на котором я был до запуска команды);
Достаёт изменения из апстрима (
fetch), чтобы обновить дерево;И пушит изменения на удалённый репозиторий.
Я пользуюсь этим постоянно!
Эпилог
Jujutsu не делает ничего такого, чего git не может, но она убирает столько трения, что ты начинаешь делать вещи, которые git тоже умел, но которые ты никогда не делал - просто потому, что это было слишком муторно.
Создавать ветку на минуту, чтобы проверить идею, даже если ты посреди других изменений; возвращаться к старому коммиту, чтобы добавить забытый кусочек; передвигать коммиты по дереву - всё это становится настолько простым, что превращается в часть твоего обычного рабочего процесса.
С git я никогда не переключался на другую ветку посреди работы - слишком боялся, что стэш съест мои изменения. Никогда не возвращался к старому коммиту, чтобы его поправить - слишком опасно, «там драконы». Ужасно боялся rebase, потому что каждый раз получал по конфликту на каждый коммит и должен был разрешать одно и то же по пятьдесят раз.
Jujutsu даёт тебе уверенность и понимание, которые позволяют делать всё это спокойно -
и даже если ты что-то сломаешь (что, чудом, со мной ещё не случалось!), oplog всегда рядом, чтобы вернуть всё ровно в то состояние, в котором оно было тридцать секунд назад.
Так что, давай уже, переключайся на jujutsu!
