Работать с кучей параллельных изменений в git — сплошная головная боль. Постоянно жонглируешь ветками и коммитами, запускаешь страшные rebase -i, которые одним чихом ломают всё дерево. Альтернатива jj рекламируется как решение, но её подход не заходит каждому. Автор статьи пробовал jj раз в три месяца полтора года — и каждый раз возвращался к git. И тут на помощь приходит git history — экспериментальная команда, появившаяся в двух релизах: 2.54 (подкоманды reword и split) и 2.55 (подкоманда fixup). О ней много говорили в дни релизов, а потом забыли. А зря — она уже приносит часть тех плюшек, за которые хвалят jj, и при этом не требует переучиваться. Работает из коробки, без установки дополнительного софта.
Три подкоманды: fixup, reword и split.
git history fixup правит старый коммит, в котором закралась ошибка, и автоматически перебазирует все ветки, которые от него зависят. Делаешь git add, затем git history fixup <commit> — и фикс вклеивается в целевой коммит. Похоже на git commit --fixup с autosquash, но с бонусом: переписываются ветки, которые содержали этот коммит. git rebase --update-refs двигает только ссылки внутри перебазируемого диапазона, а git history находит и переписывает каждую локальную ветку, произошедшую от изменяемого коммита (можно ограничить только текущей веткой). Правда, не работает при наличии merge-коммитов — для некоторых это станет препятствием. Важное свойство всех трёх команд — атомарность: они никогда не оставляют дерево в полусломанном состоянии, отказываясь выполнять операцию, если может возникнуть конфликт. Это слабее, чем в jj, который умеет протаскивать конфликт через rebase и даёт разобраться позже. Но в документации сказано: ограничение снято, когда (если) git научится first-class конфликтам.
git history reword меняет сообщение старого коммита и автоматически пересобирает всё поверх. Открывает редактор, ты правишь сообщение, сохраняешь — и остальной стек перестраивается, ветки едут следом. Поскольку меняется только сообщение (не содержимое), команда не трогает ни индекс, ни рабочую директорию — можно переписывать коммит на ветке, которую не checkoutнул, не мешая текущей работе.
git history split разбивает один коммит на два. Он закидывает тебя в пофрагментный выбор (как git add -p), но без акробатики с rebase. Фрагменты, которые ты сохраняешь, образуют первый коммит, остальные — второй. Незаменимая вещь, когда нужно разлепить два unrelated изменения, попавшие в один коммит.
Конечно, git history не закрывает весь разрыв с jj: у jj есть operation log с откатом, модель рабочей копии как коммита и умение тащить конфликты через rebase. Но уже сейчас git history — большой шаг вперёд, встроенный в инструмент, который ты используешь каждый день. А документация намекает, что улучшения ещё будут.