Вступление
Если ты здесь, ты наверняка знаешь, что такое 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.
ссылка на оригинал статьи https://habr.com/ru/articles/938220/
Добавить комментарий