Как потратить дни, чтобы сэкономить секунды: продвинутые коммиты в GitLab

от автора

Коммит изменения в GitLab — фоновый и рутинный процесс, на который никто не закладывает рабочего времени. Но в нем есть действия, которые съедают 18 секунд при каждом коммите. 10 коммитов — уже 3 минуты за день и 15 — за неделю. Да, немного, но на это тратится внимание. К тому же, за эти 15 минут можно сделать что-то полезное или просто выпить кофе и дать мозгу отдохнуть.

Мы в Selectel нашли способ, как упростить коммиты в GitLab и добавить им информативности — описания прямиком из Jira. Любите автоматизировать рутинные задачки? Тогда добро пожаловать под кат.


Привет, Хабр! Меня зовут Анатолий, с недавнего времени я фронтенд-разработчик в Selectel. Во время адаптационного периода у меня не было серьезных проектов, поэтому я мог придумывать задачи себе сам. Это отличный шанс посмотреть на процессы в команде и попробовать сделать их лучше. И, как позже выяснилось, это очень благодарный труд.

В команде принято использовать ID задач из Jira в качестве названий для веток и Conventional Commits. Формат: type(component_from_Jira): subject TASK_ID.

Удобство от такого подхода я ощутил сразу, в GitLab намного проще ориентироваться. Ветки, коммиты, мердж реквесты — все становится очевидным. Но есть и обратная сторона медали — приходится писать сообщения о коммите.

Процедура коммита выглядит так.

  1. Копируешь тип задачи в Jira, вставляешь в сообщение коммита.
  2. Копируешь компонент задачи в Jira, вставляешь в сообщение коммита.
  3. Копируешь название задачи в Jira, вставляешь в сообщение коммита.
  4. Копируешь ID задачи, вставляешь в сообщение коммита.
  5. Коммитишь.

Получаем что-то вроде 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. С помощью него мы получаем параметры, заданные пользователем в настройках. Взаимодействие с настройками реализованы в соответствии с официальным туториалом.

Билдим проект через TasksintellijbuildPlugin.

Ставим его с диска и получаем желаемый результат!

Не уверен, что конечный продукт стоил потраченных усилий, но полученные навыки определенно того стоили. Я наконец получил решение, которое фундаментально лучше уже не сделать. Или сделать? Поделитесь в комментариях опытом, если зашли еще дальше в решении этой или похожей задачи.

Другие тексты про автоматизацию

Как сделать бота для заказа шавермы и оставить голодными лишь 1,1% коллег
Как мы хакнули систему Хабра, ускорив верстку статей в несколько раз
Как мы автоматизировали верстку статей на vc.ru


ссылка на оригинал статьи https://habr.com/ru/articles/763086/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *