- PVSM.RU - https://www.pvsm.ru -
Если ты здесь, ты наверняка знаешь, что такое git. И да, не спорю - это офигенная штука. Деды знали, что писали.
Но я долгое время работал над небольшими проектами и был там единственным разработчиком. Когда перешёл в большую команду, пришлось глубже вникнуть в git.
И тут началось: я стал тратить кучу ресурсов на постоянные вопросы:
Нужна ли отдельная ветка или нет?
Merge или Rebase?
Какой revert использовать?
В каком статусе сейчас файл?
Где вообще находится header?
А ещё каждый коммитил как хотел. В итоге история проекта - это каша: понять, что, когда и зачем сделал человек, просто невозможно.
Я начал гуглить best practices, читать про git flow, пытался навести порядок. Но всё равно слишком много времени уходило не на код, а на борьбу с системой контроля версий.
И вот я наткнулся на Jujutsu (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. Какая версионность? Какие правильные коммиты? Какой качество код? Вы вообще о чем?
Пишешь код, коммит за коммитом.
# Никаких веток
# Первый коммит
# ... код ...
# никаких `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.
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, если нужно откатить изменения.
Получает изменения из удалённого репозитория.
Несовместимые закладки создают конфликт закладок (аналог 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.
Отправляет изменения в удалённый репозиторий.
Изменённые изменения создаются заново (аналог --force).
Основная ветка защищена - изменения нужно явно пушить с --ignore-immutable.
jj git push -c @ создает новую временную закладку (git автоматически предложит создать MR)
Про закладки:
Локальные закладки копируются в удалённый репозиторий.
Если закладка не синхронизирована - push выдаёт ошибку, нужно сначала сделать jj git fetch.
Связывает локальную закладку с удалённой веткой.
Отслеживать изменения из удалённого репозитория при 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) |
|
|
Восстановить файлы из другого изменения |
|
|
Создать обратное изменение |
|
|
Отказаться от изменения |
|
|
Показать разницу между изменениями |
|
|
Объединить изменения |
Более подробно читаем в документации [1]
Давайте посмотрим, как jj справляется с типичными задачами, используя только свои команды и концепции.
Тут я решил показать возможности revsets [2]
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, когда я пишу код, быстро сохраняю свои наработки и пишу дальше. В моей голове это "чек-поинты", к которым я в любой момент могу вернуться.

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

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

И вновь jj удивляет и сразу предлагает ссылку на MR
Jujutsu - это не замена Git, а его улучшенная оболочка. Он решает многие проблемы, которые так раздражают в классическом Git:
Грязная история и мучительный rebase -i.
Сложности с git add и git stash.
Страх "сломать" репозиторий, ведь у jj есть jj undo.
Если вы хотите освободить свой для написания кода, а не для борьбы с системой контроля версий, попробуйте jj. Просто попробуйте, если что, вы всегда сможете вернуться на Git.
Автор: Ads_2s
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/git/428439
Ссылки в тексте:
[1] документации: https://jj-vcs.github.io/jj/latest/
[2] revsets: https://jj-vcs.github.io/jj/latest/revsets/
[3] мозг: http://www.braintools.ru
[4] Источник: https://habr.com/ru/articles/938220/?utm_source=habrahabr&utm_medium=rss&utm_campaign=938220
Нажмите здесь для печати.