Коммит изменения в GitLab — фоновый и рутинный процесс, на который никто не закладывает рабочего времени. Но в нем есть действия, которые съедают 18 секунд при каждом коммите. 10 коммитов — уже 3 минуты за день и 15 — за неделю. Да, немного, но на это тратится внимание. К тому же, за эти 15 минут можно сделать что-то полезное или просто выпить кофе и дать мозгу отдохнуть.
Мы в Selectel нашли способ, как упростить коммиты в GitLab и добавить им информативности — описания прямиком из Jira. Любите автоматизировать рутинные задачки? Тогда добро пожаловать под кат.
Привет, Хабр! Меня зовут Анатолий, с недавнего времени я фронтенд-разработчик в Selectel. Во время адаптационного периода у меня не было серьезных проектов, поэтому я мог придумывать задачи себе сам. Это отличный шанс посмотреть на процессы в команде и попробовать сделать их лучше. И, как позже выяснилось, это очень благодарный труд.
В команде принято использовать ID задач из Jira в качестве названий для веток и Conventional Commits. Формат:
type(component_from_Jira): subject TASK_ID
.Удобство от такого подхода я ощутил сразу, в GitLab намного проще ориентироваться. Ветки, коммиты, мердж реквесты — все становится очевидным. Но есть и обратная сторона медали — приходится писать сообщения о коммите.
Процедура коммита выглядит так.
- Копируешь тип задачи в Jira, вставляешь в сообщение коммита.
- Копируешь компонент задачи в Jira, вставляешь в сообщение коммита.
- Копируешь название задачи в Jira, вставляешь в сообщение коммита.
- Копируешь ID задачи, вставляешь в сообщение коммита.
- Коммитишь.
Получаем что-то вроде feat(common): «Сделать кнопку чуть краснее и чуть больше WEB-1234». Именно исправлению этого малого неудобства я и решил посвятить большую часть своего внимания.
Очевидное и простое решение
Зачем заставлять пользователя (разработчика) копировать эти поля и вручную форматировать сообщение коммита, когда можно
ТЗ: кнопка на странице Issue в Jira, которая будет класть в клипборд отформатированное сообщение.
Попытка №1
Используя crxjs, создаем расширение на Vite и Preact. Расширение состоит из манифеста, content script, popup и service worker. Если не вдаваться в подробности, то popup — это окошко, которое появляется при клике на иконку расширения. Там мы размещаем пользовательские настройки, а content script вставляется в страницу и позволяет манипулировать DOM-ом.
Внутри
content script
создаем кнопку в нужном месте:const issueHeader = document.getElementById("jira-issue-header")
const root = document.createElement('div')
root.id = "jira-helper-entry"
issueHeader.appendChild(root)
render(
<div class="sch-buttons">
<CopyCommitMessageButton />
</div>,
root
)
// Добавим на страницу тостер, в нем будут
// спавниться тосты библиотеки react-hot-toast.
render(
<Toaster position='bottom-right'/>,
document.querySelector('#page')
)
По клику на нее соберем поля таски и составим из них сообщение:
const name = document.querySelector("#summary-val").textContent.trim()
const component = document.querySelector("#components-val").textContent.trim()
const key = document.querySelector("#key-val").textContent.trim()
const type = document.querySelector("#type-val").textContent.trim()
const commitMessage = `${type}(${component}): ${name} ${key}`
Полученное сообщение кладем в клипборд и показываем тост, чтобы пользователь не сомневался, что ему в клипборд что-то попало и это не каша:
function listener(event: ClipboardEvent) {
event.clipboardData?.setData("text/plain", commitMessage)
event.preventDefault()
}
document.addEventListener("copy", listener)
document.execCommand("copy")
document.removeEventListener("copy", listener)
// react-hot-toast
toast.success(
() => (
<div>
<b>Скопировано!</b></b>
{commitMessage}
</div>
),
{
id: commitMessage
}
)
Вуаля! Теперь можно наслаждаться новой кнопкой в Jira и экономить 18 секунд каждый раз, когда надо составить сообщение коммита.
Осознание
Через неделю использования меня стала тревожить мысль о том, что я заменил несколько глупых шагов одним глупым шагом. Конвенция коммитов не стала абсолютным плюсом, а так и осталась компромиссом. Мы получали читаемый GitLab, но все еще платили за это своим временем, хотя и не так много как раньше. Мне теперь приходилось открывать браузер с открытой Issue в Jira и нажимать на кнопку. Это лучше, чем копировать четыре поля, но все равно глупо.
А самое обидное, что бескомпромиссное решение было все это время под носом, а если точнее над промптом. Мы же и в консоли, и в IDE знаем, в какой ветке мы находимся, а значит знаем, над какой таской работаем!
ТЗ: написать расширение для WebStorm, которое будет ходить в Jira, находить Issue, исходя из названия текущей ветки в IDE, составлять сообщение о коммите и сразу вставлять его в соответствующее окошко в IDE.
Попытка №2
Для основы я нашел существующий open source проект, который добавляет окно для создания сообщений коммита по шаблону.
Изучаем Java, тратим день, чтобы понять, как ставить зависимости, билдить проект и как, черт побери, фиксить LinkageError, и приступаем к работе!
Это совсем не то, что нам надо, но это намного лучше, чем начинать с нуля!
Например, в этом плагине уже добавлена кнопка в интерфейс, по нажатию на которую появляется окно выше:
<idea-plugin>
<actions>
<action id="Commit.Button" class="com.kopyl.commit.CreateCommitAction"
text="Create Commit Message"
description="Create commit message"
icon="/icons/generate.svg">
<add-to-group group-id="Vcs.MessageActionGroup" anchor="first"/>
</action>
</actions>
</idea-plugin>
И реализована функция получения названия ветки для использования в качестве Jira ID:
private String getJiraIdFromBranchName(Project project) {
RepositoryManager<GitRepository> repositoryManager = GitUtil.getRepositoryManager(project);
GitLocalBranch branch = repositoryManager.getRepositories().get(0).getCurrentBranch();
if (branch == null) return "";
return branch.getName();
}
Напишем класс для взаимодействия с API Jira и будем передавать туда этот ID.
// src/main/java/com/kopyl/commit/JiraClient.java
public class JiraClient {
private final String token;
private final String jiraUrl;
private final JiraRestClient restClient;
public JiraClient(String token, String jiraUrl) {
this.token = token;
this.jiraUrl = jiraUrl;
this.restClient = getJiraRestClient();
}
private JiraRestClient getJiraRestClient() {
BearerHttpAuthenticationHandler handler = new BearerHttpAuthenticationHandler(this.token);
JiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();
return factory.create(getJiraUri(), handler);
}
private URI getJiraUri() {
return URI.create(this.jiraUrl);
}
public Issue getIssue(String issueKey) {
return restClient.getIssueClient()
.getIssue(issueKey)
.claim();
}
}
Напишем класс CommitMessage, который будет принимать Issue и иметь метод для превращения этого Issue в строку подходящую под наш формат. Везде для взаимодействия с Jira используется пакет Atlassian Jira REST Client API.
// src/main/java/com/kopyl/commit/CommitMessage.java
public class CommitMessage {
private ChangeType changeType = ChangeType.FEAT;
private String changeScope = "";
private String shortDescription = "";
private String jiraId = null;
public CommitMessage(Issue issue) {
if (Objects.equals(issue.getIssueType().getName(), "Bug")) {
this.changeType = ChangeType.FIX;
}
this.changeScope = issue.getComponents().iterator().next().getName();
this.shortDescription = issue.getSummary();
this.jiraId = issue.getKey();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(changeType.label());
if (isNotBlank(changeScope)) {
builder.append('(').append(changeScope).append(')');
}
builder.append(": ").append(shortDescription);
if (isNotBlank(jiraId)) {
builder.append(' ').append(jiraId);
}
return builder.toString();
}
}
В плагине уже существовал класс CreateCommitAction, который соответствовал из plugin.xml, он обрабатывает нажатие на добавленную в интерфейс кнопку.
Перепишем его, чтобы он не открывал окно, а обращался в API и создавал сообщение о коммите из полей таски:
// src/main/java/com/kopyl/commit/CreateCommitAction.java
public class CreateCommitAction extends AnAction {
// ...
@Override
public void actionPerformed(@NotNull AnActionEvent actionEvent) {
CommitMessageI commitPanel = getCommitPanel(actionEvent);
if (commitPanel == null) return;
String personalAccessToken = AppSettingsState.getInstance().personalAccessToken;
String jiraUrl = AppSettingsState.getInstance().jiraUrl;
JiraClient jiraClient = new JiraClient(personalAccessToken, jiraUrl);
String jiraId = getJiraIdFromBranchName(actionEvent.getProject());
Issue issue = jiraClient.getIssue(jiraId);
CommitMessage commitMessage = new CommitMessage(issue);
commitPanel.setCommitMessage(commitMessage.toString());
}
}
Код выше использует класс AppSettingsState. С помощью него мы получаем параметры, заданные пользователем в настройках. Взаимодействие с настройками реализованы в соответствии с официальным туториалом.
Билдим проект через Tasks → intellij → buildPlugin.
Ставим его с диска и получаем желаемый результат!
Не уверен, что конечный продукт стоил потраченных усилий, но полученные навыки определенно того стоили. Я наконец получил решение, которое фундаментально лучше уже не сделать. Или сделать? Поделитесь в комментариях опытом, если зашли еще дальше в решении этой или похожей задачи.
Другие тексты про автоматизацию
→ Как сделать бота для заказа шавермы и оставить голодными лишь 1,1% коллег
→ Как мы хакнули систему Хабра, ускорив верстку статей в несколько раз
→ Как мы автоматизировали верстку статей на vc.ru
Комментарии (14)
slonopotamus
19.10.2023 14:36+7Вредная штука. Заголовки задач (а особенно багов) далеко не всегда сформулированы в пригодном для коммит-мессаджа стиле.
Doctor_IT
19.10.2023 14:36+1Не работаю в отделе разработки, откуда Анатолий, но вообще у нас в Selectel команды, как правило, делают шаблоны для постановки задач. Ну и, конечно, если совсем все плохо, заголовок можно отредактировать в самой задаче.
slonopotamus
19.10.2023 14:36+2Заголовок бага обычно описывает что не так. Ну там не знаю, "Бесконечно копятся логи приложения". Вы в коммит мессадж так и напишете? Или всё-таки в нём будет "Настроено удаление логов старше недели"?
akopyl Автор
19.10.2023 14:36Почти так и напишем :)
Если это баг, то тип изменения по конвеншну — fix, что отражается в сообщении. Получается «fix(компонент): Бесконечно копятся логи приложения»
slonopotamus
19.10.2023 14:36+3Но такой коммит мессадж ничего не говорит о том а что собсна говоря сделано. Может вы их вообще писать перестали.
akopyl Автор
19.10.2023 14:36Подумайте, куда вы, через неопределенный промежуток времени, пойдете смотреть отчет о проделанной работе? Шерстить репозитории, ветки и коммиты или в Jira?
Если вы (и ваш менеджер) используете сообщения коммитов в гите, как основной источник для получения информации о проделанной работе, то скорее всего этот подход не для вас.Безусловно, не каждая конвенция подходит каждой команде)
buldo
19.10.2023 14:36+3Раза 3 перечитал сообщение и так и не понял.
В jira бизнес история изменений - какие таски сделаны, какие баги исправлены.
В git сообщениях - что именно сделано.
Если у вас реализация таски состоит из нескольких коммитов, то у них будут одинаковые сообщения?
slonopotamus
19.10.2023 14:36+1"Через неопределенный промежуток времени" все эти джиры теряются/мигрируют/остаются в другой компании. И у вас на руках есть только репозиторий с кодом.
Andreyika
19.10.2023 14:36+2Что-то похоже на обрезанный функционал Tools - Tasks..
Там можно и к jira подцепиться и к redmine и еще десятку сервисов.. видеть список открытых (и не только) задач, создавать на их основе ветку, делать название коммита, передвигать (в небольших рамках) задачи автоматическиTyVik
19.10.2023 14:36Вот да. А ещё одновременно делать небольшие фиксы, раскидывая их по разным листам изменений (changelist).
Andrey_Solomatin
19.10.2023 14:36+1Я бы смотрел в сторону хуков гита или автоматизации в гитлабе.
Хуки могут сами добавть нужный текст в сообщение перед созданием коммита.
В гитхабе, можно настроить так, чтобы при мердже все коммиты сквашились в один и заголовок мердже реквеста и описание становилось сообщением этого коммита. Вроде в гитлабе можно настроить похожее. Тогда можно просто сделать CI, которые будет обновлять заголовок и описание.
Fodin
А можно TL; DR со ссылкой на даунлоад?
akopyl Автор
Конечно!
Писать сообщение для коммита – трата времени. Генерировать сообщение для коммита автоматически – экономия времени.
Плагин для Intellij: https://plugins.jetbrains.com/plugin/22770-auto-commit-message
Исходный код: https://github.com/anatolykopyl/auto-commit-message-plugin
Расширением для хрома не делюсь, потому что Jira у всех разная, а расширение сделано именно под наш интерфейс. Да и для IDE плагин лучше.