Предыстория
Как-то пару месяцев назад пришел ко мне в гости в коворкинг поработать удаленно мой давний приятель. Он пишет на Java и использует в своей работе IntelliJ IDEA. Помню, он долго восхищался новой на тот момент фичей встроенного AI Assistant - умением генерировать commit message.
На тот момент я как-то не сильно проникся идеей автогенерации сообщения, потому что я, как человек, который ответственен за процесс code-review в своей команде, с трепетом отношусь к описанию коммита. Прошло немного времени, у меня по работе прилетела задача рефакторинга довольно объемного куска кодовой базы. Причем, эта задача была разбита на подзадачи, связанные с микросервисами. Поэтому, мне надо было писать довольно объемные коммит-сообщения по завершении каждой итерации. И тут я вспомнил про своего приятеля, когда он за минуту редактировал сгенерированное сообщение от AI ассистента и экономил немало времени.
Типа ТЗ
Я тоже иногда периодически пользуюсь IDE от JetBrains, но в тот момент я плотно сидел на NeoVim и кроме эмулятора терминала никакими GUI инструментами не пользовался. Для работы с Git у меня в арсенале консольных инструментов есть Lazygit - прекрасное TUI приложение для оперативной работы с этой системой контроля версий. Поэтому, надо было придумать, каким образом прикрутить ИИ к lazygit
и еще очень важным обстоятельством было то, что сообщения должны соответствовать принятым у нас в команде стандартам: содержать в заголовке идентификатор задачи из Jira, списки изменений в секциях: added, deleted, changed, moved.
Поиски подходящего решения
Немного погуглив, я наткнулся на CLI тулзу Aichat с довольно мощной поддержкой разных LLM из коробки. Еще немного погодя, нашел на страничках Github историю о том, как кто-то уже успешно скрещивал эти два инструмента - вот тут, вдруг, кому интересно будет.
Ну все, база для допиливания под себя есть, с этим я двинулся дальше.
Работа напильником
Для начала я создал шаблон сообщения будущих коммитов. Выглядел он в моем случае так:
{summary}
added:
-
changed:
-
moved:
-
deleted:
-
Далее, надо было определиться с LLM, которую надо будет вызывать через aichat
. Для начала я решил попробовать бесплатную облачную модель от CloudFlare. Почему именно ее? Да не почему, просто так, не пробовал, не трогал, стало интересно :) Зарегистрировался, получил токен. Весь этот процесс тут описывать не буду, так как он простой и не заслуживает отдельного внимания. Настройки aichat
хранятся в файле config.yaml
. В MacOS файл находится в ~/Library/Application Support/aichat
, в Linux - ~/.config/aichat
. Открываем или создаем этот файл в любимом редакторе и добавляем следующее:
clients:
- type: openai-compatible
name: cloudflare
api_base: https://api.cloudflare.com/client/v4/accounts/{client_id}/ai/v1
{client_id} - это ваш идентификатор клиента в CloudFlare.
Токен доступа я добавляю в переменные среды следующим образом:
export CLOUDFLARE_API_KEY=$(keyring get cloudflare CLOUDFLARE_API_KEY)
Эта строчка у меня добавлена в .zshrc
. Сам токен я храню в keyring. Почему я так делаю, а не сразу прописываю токен при экспорте переменной? Ответ прост - я храню копии конфигурационных файлов в открытом репозитории dotfiles, поэтому, очевидно, что там секреты храниться не должны.
Следующим шагом надо “натравить” lazygit
на aichat
, чтобы сгенерировать текст коммита. Открываем конфиг lazygit
: для MacOS он находится в ~/library/Application Support/lazygit/config.yml
, для Linux - ~/.config/lazygit/config.yml
и добавляем туда вот такое содержимое:
customCommands:
- key: <c-a>
description: Pick AI commit
command: |
aichat "Пожалуйста, напишите commit message для следующего коммита, используя результат команды git diff:
\`\`\`diff
$(git diff --staged)
\`\`\`
**Пример сообщения коммита:**
Краткое описание изменений.
added:
- какие сущности, методы, классы или логика добавлены и т.д.
removed:
- какие сущности, методы, классы или логика удалены и т.д.
modified:
- какие сущности, методы, классы или логика изменены и т.д.
moved:
- какие сущности, методы, классы или логика перемещены и т.д.
**Выходной шаблон**
Обязательно используй этот шаблон, вывод которого ниже. Не используй переносы строк, лишние пробелы, лишние символы. Не украшай текст.
\`\`\`
$(cat .git/.template)
\`\`\`
**Используй предыдущие коммиты для примера:**
\`\`\`
$(git log -n 5 --pretty=format:'%h %s')
\`\`\`
Напиши свой commit message строго соблюдая шаблон. Пиши на английском. В шаблоне обязательно должны быть заполнены все секции.
Не углубляйся в детали, описывай только суть того, что поменялось или добавилось."\
| fzf --height 40% --border --ansi --read0 --preview "echo {}" --preview-window=up:wrap \
| xargs -0 -J {} bash -c '
COMMIT_MSG_FILE=$(mktemp)
printf "%s" "$1" > "$COMMIT_MSG_FILE"
${EDITOR:-nvim} "$COMMIT_MSG_FILE"
if [ -s "$COMMIT_MSG_FILE" ]; then
git commit -F "$COMMIT_MSG_FILE"
else
echo "Commit message was not saved, commit aborted."
fi
rm -f "$COMMIT_MSG_FILE"' _ {}
context: files
output: terminal
Примечание: у вас должна быть установлена утилита fzf
Что тут происходит, я, думаю, понятно: при нажатии ctrl+a
мы вызываем aichat
и передаем промпт модели, используя вывод команд git
. Далее, то, что нагенерирует модель попадает в окно fzf и мы можем либо принять то, что она нагенерировала с дальнейшим редактированием (откроется редактор, который у вас определен в переменных среды в переменной EDITOR
, либо тот, что по умолчанию).
Ну вот, собственно говоря, и все, это работает. Работает, но с небольшими оговорками:
ваш вывод
git diff
вы передаете на удаленный сервер (это может быть проблемой про NDA);если
git diff
выводит много чего, то этот вывод может не влезть в кол-во токенов модели.
Со вторым пунктом решение на основе локальной LLM вам, скорее всего, не поможет, а вот с первым - вполне.
Итак, нам потребуется LM Studio. Устанавливаем сие чудесное ПО. Открываем его. Скриншоты с настройками приводить не буду, так как мне кажется, что интерфейс очень понятный, можно сказать, интуитивный :)
Скачиваем модель, которую вы хотите, в моем случае я решил попробовать Deepseek R1 Qwen 3 8B
(кто-то мне ее советовал). Загружаем ее. Слева в меню выбираем пункт Developer (с пиктограммой консоли). Там нам надо включить сервер для обработки запросов к модели по HTTP. Включаем рубильничек, проверяем curl
http://127.0.0.1:1234
или просто открываем этот адрес в браузере. Там по дефолту будет ошибка {"error":"Unexpected endpoint or method. (GET /)"}
, но, тем не менее, статус ответа - 200. Тут нам больше и не надо.
Возвращаемся к aichat
, точнее, к его конфигурационному файлу. Добавляем туда:
- type: openai-compatible
name: deepseek
api_base: http://127.0.0.1:1234/v1
Соответственно, весь конфиг у нас выглядит теперь так:
clients:
- type: openai-compatible
name: cloudflare
api_base: https://api.cloudflare.com/client/v4/accounts/01fdd480aaf4e9d99a9ec1609fc00cba/ai/v1
- type: openai-compatible
name: deepseek
api_base: http://127.0.0.1:1234/v1
Подробная документация по возможным конфигурациям тут.
Теперь осталось чуть изменить конфиг lazygit
, а именно дописать сюда ключ для выбора локальной модели:
aichat -m deepseek "Пожалуйста, напишите commit message для следующего коммита, используя результат команды git diff:
Готово! Теперь у нас идет генерация commit message при помощи локальной модели.
Вот пример сообщения, который был сгенерирован в репозитории с моими статьями, когда я написал этот текст:
Added two new methods for generating commit messages:
1. Using online Cloudflare LLM (requires internet)
2. Using offline local models like Deepseek via LM Studio
Updated the template documentation to include these new approaches.
Verified that both methods follow our commit message format requirements.
На этом все, надеюсь, было интересно и, возможно, кому-то даже полезно :)
P.S.
Шаблон для сообщения в репозитории с текстами статей такой:
{summary}
{changes description}