Pull to refresh

Git + TrackStudio — автоматизация разработки

Reading time 19 min
Views 11K
Это статья о том, как с помощью TrackStudio и Git можно организовать удобный процесс разработки ПО. В стать рассмотрен вариант для ОC Windows. Но при желании не сложно все сделать и для других OC.
Для этого нам понадобиться:
  1. TrackStudio — универсальная система управления задачами.
  2. Git — VCS.
  3. Blat — Маленькая (70kB) open source консольная программа под Windows, которая позволяет отправлять e-Mail по SMTP протоколу из командной строки, с приложенными файлами.
  4. STunnel — Программа, которая позволяет вам зашифровать произвольные TCP подключения внутри SSL. Инструмент доступен для Unix и Windows.
    Нужен если вы используете gmail.com, как почтовый сервис. Так как blat напрямую работать с gmail не может.
Задача:
Организовать рабочий процесс таким образом, что бы при коммите в git, автоматически в TrackStudio задаче добавлялся комментарии с различными типами сообщения и текстом этого комментария был текст сообщения коммита.

Установка

TrackStudio

Делаем все как написано здесь
Установка всех остальных приложении очень проста, замечу только одно, что blat надо положить в папку с git.exe (у меня С:/Program files/Git/bin), или папку, в которой находится blat добавить в PATH.


Настройка

TrackStudio

TrackStudio умеет забирать письма из email и импортировать их как задачи.
Для настройки этого процесса необходимо в файле TS_HOME\etc\trackstudio.mail.properties (если у вас его нет создайте) прописать параметры сбора почты:
mail.pop3.apop.enable=\
mail.store.forward=отправлять не распознанные письма на email
mail.store.fwdaddress=email куда отправлять не распознанные письма
mail.store.host=pop сервер
mail.store.password=ваш пароль
mail.store.port=порт
mail.store.protocol=протокол
mail.store.user=учетная запись, email, с которого импортировать 
trackstudio.emailSubmission=yes
trackstudio.mailimport.interval=интервал проверки почты
вот пример для gmail.com:
mail.pop3.apop.enable=\
mail.store.forward=yes
mail.store.fwdaddress=mymail@gmail.com
mail.store.host=pop.gmail.com
mail.store.password=password
mail.store.port=995
mail.store.protocol=pop3s
mail.store.user=srvmail@gmail.com
Более подробно все описано в документации.


Скрипт для создания правила импорта

Теперь в TS для предка каждой задачи с типом категории «Изменение» или «Ошибка» надо создать правило импорта и желательно, чтобы они создавались от имени администратора. Но каждый раз делать, это ручками накладно — решаем эту проблему написание плагина. В папке TS_HOME\plugins\scripts\after_create_task (в ней размещаются скрипты, которые выполняются после создания задач) создаем файл createRuleImport.bsh со следующим кодом:
  1. /**
  2. * Скрипт создает правило импорта для задачи предка, при создании дочерней задачи.
  3. */
  4. import com.trackstudio.app.adapter.AdapterManager;
  5. import com.trackstudio.exception.GranException;
  6. import com.trackstudio.secured.*;
  7. import com.trackstudio.securedkernel.SecuredMailImportAdapterManager;
  8. import com.trackstudio.securedkernel.SecuredStepAdapterManager;
  9. import java.util.*;
  10. import com.trackstudio.secured.SecuredUserBean;
  11. import com.trackstudio.app.session.*;
  12. import com.trackstudio.app.csv.CSVImport;
  13.  
  14. try {
  15.     //объек для работы с правилами импорта почтовых сообщений
  16.     SecuredMailImportAdapterManager mam = AdapterManager.getInstance()
  17.         .getSecuredMailImportAdapterManager();
  18.         
  19.     //содержит методы для работы с типами сообщений
  20.     SecuredStepAdapterManager sam = AdapterManager.getInstance()
  21.         .getSecuredStepAdapterManager();
  22.         
  23.     //список доступных типов сообщений для задачи и текущего пользователя
  24.     ArrayList arrMS = sam.getAvailableMstatusList(task.getSecure(), task.getId());
  25.     
  26.     //тип сообщения
  27.     SecuredMstatusBean curMS = null;            
  28.          
  29.     String pefixStr = «new»;    
  30.     /**
  31.      * Проверяем тип категории создаваемой задачи.
  32.      * Если категория задачи равна «Ошибка» = наименование категории в TS
  33.      */
  34.     if(task.getCategory().getName().equals(«Ошибка»))
  35.         pefixStr = «bug»;
  36.     
  37.     /**
  38.      * Формируем ключ для импорта сообщений текущей задачи:
  39.      * префик + номер родительской задачи + # + номер задачи
  40.      * этот ключ должен встречаться в теме письма
  41.      * например:
  42.      * new759#2845 любой текст, все равно он будет проигнорирован при добавлении сообщеиня.
  43.      */
  44.     String mailimKey = pefixStr + task.getParent().getNumber() + "#" + task.getNumber();     
  45.     
  46.     /**
  47.      * Надо ли создавать правило.
  48.      * Так как задача предок может иметь несколько подзадач,
  49.      * то необходимо проверять существование правила импорта,
  50.      * что бы не плодить их.
  51.      */
  52.     boolean found = false;
  53.     
  54.     for (int i = 0; i < arrMS.size(); i++) { 
  55.         curMS = (SecuredMstatusBean) arrMS.get(i);
  56.         
  57.           msn = curMS.getName();
  58.         
  59.         //«Комментрарий» или «Комментировать» это наименования типа сообщения        
  60.         if(msn.compareToIgnoreCase(«Комментарий») == 0 || msn.compareToIgnoreCase(«Комментировать») == 0) {
  61.             //Получаем данные по пользователю root
  62.             SecuredUserBean adminUser = new SecuredUserBean(CSVImport.findUserIdByLogin(«root»), sc);
  63.             //Текущий контекст пользователя
  64.             SessionContext needSc = task.getSecure();
  65.             //проверяем авторизировался ли root
  66.             if(SessionManager.getInstance().existUserSession(adminUser.getUser())) {
  67.                 for(SessionContext curSc: SessionManager.getInstance().getSessions()) {
  68.                     if(curSc.getUser().getLogin().equals(«root»)) {
  69.                         //если да то получаем его контекст
  70.                         needSc = curSc;
  71.                         break;
  72.                     }
  73.                 }
  74.             }
  75.             else {
  76.                 //если нет, то создаем его контекст
  77.                 needSc = SessionManager.getInstance().getSessionContext(
  78.                     SessionManager.getInstance().create(adminUser.getUser()));
  79.             }
  80.             
  81.             if(!found)
  82.                 for (SecuredMailImportBean curMailS: mam.getAllAvailableMailImportList(needSc, task.getParentId())) {            
  83.                     // Ищем правило импорта новых задачь у предка.                    
  84.                     found = curMailS.getKeywords().equals(pefixStr + task.getParent().getNumber());
  85.                     if(found)                        
  86.                         break;
  87.                 }
  88.             
  89.             if(!found) {         
  90.                 /**
  91.                  * если не нашли, то создаем новое правило импорта для предка
  92.                  * от имени root, так как TS поддерживает возможность создания
  93.                  * пользователями индивидуальных правил импорта почты, необходимо
  94.                  * что бы это правило было видно всем.
  95.                  */
  96.                 mam.createMailImport(needSc, pefixStr + task.getParent().getNumber(),
  97.                     task.getParentId(), pefixStr + task.getParent().getNumber(), 1, i,
  98.                     task.getCategoryId(), curMS.getId(), "", true, false);                    
  99.                 found = true;
  100.             }
  101.         }        
  102.      }         
  103.     //Добавляем значение для UDF поля с наименование «Имя ветви в git».
  104.     AdapterManager.getInstance().getSecuredUDFAdapterManager().setTaskUDFValueSimple(
  105.         task.getSecure(),task.getId(),«Имя ветви в git», mailimKey);
  106. }
  107. catch(GranException e) {
  108.     throw new com.trackstudio.exception.UserMessageException(e.getMessage());
  109. }
  110. return task;

Скрипт создали, теперь в TS выбираем в меню «Управление задачами -> Категории»,
щелкаем сначала по категории с наименование «Изменение»,

открываем вкладку «Триггеры» и в списке «AFTER Триггер» отмечаем триггер, который называется createRuleImport.bsh.

Проделываем тоже самое и для категории «Ошибка».

Если все сделано правильно, то при создании новой задачи у ПРЕДКА появится правило импорта newНОМЕР_ПРЕДКА или bugНОМЕР_ПРЕДКА. При отправки письма на email, с которого импортируется почта, с темой «newНОМЕР_ПЕРЕДКА новая задача», TS создаст задачу в ПРЕДКЕ с наименование, которое равно теме письма. А если отправить письмо в теме, которого есть ключ newНОМЕР_ПЕРЕДКА#НОМЕР_ЗАДАЧИ (при этом задача существует), то TS импортирует письмо, как комментарии к задаче и сообщение комментария будет тело письма. При чем если к письму прикрепить файл, то TS прикрепит его к задаче.


Каким образом добавить UDF поле

Для этого в TS верхнем меню выбрать «Управление задачами -> Процессы», открыть интересующий вас процесс и во вкладке «Дополнительные поля» кликнуть по кнопке «Строка».

В названии указать нужное вам имя, в нашем случае «Имя ветви в git», и нажать кнопку Сохранить. Но это все лишь для того, что бы скопировать и быстро вставить в sh при создании новой ветки в git, типа удобно.


Скрипт для обработки сообщении полученных через почту

TS уже умеет из писем создавать сообщения с типом «Комментарии» к задачам.
Теперь нам необходимо сделать, чтобы TS изменил типа сообщения на «Завершить» или «Исправить» для категории «Изменение» или «Ошибка» соответственно («Комментарии», «Завершить», «Изменение» и «Ошибка» — наименования типа сообщения и категорий, они зависят от того как вы настроите свой workflow в TS; если вы используете демонстрационную русскую бд, то они будут именно такими). Нам нужно создать скрипт, который будет искать в теле сообщения, какой нибудь ключ, который будет сообщать TS, что необходимо поменять тип сообщения. Я предлагаю использовать конструкцию [new_state=done], но это уже на ваше усмотрение. Создадим в папке TS_HOME\plugins\scripts\before_add_message, в которой размещаются скрипты, выполняемые до добавления сообщения, файл changeType.bsh с кодом:
  1. /**
  2. * Скритп изменяет тип импортируемого сообщения если находит в теле сообщения
  3. * конструкуцию [new_state=type], где type может быть done или start
  4. */
  5. import java.util.*;
  6. import java.util.regex.*;
  7. import java.io.*;
  8.  
  9. try {
  10.     if(message.description != null)
  11.     { 
  12.         String[] tempArr = null;
  13.         //Ищем нужную нам конструкцию в сообщение        
  14.         String pattern = ".*\\[(\\w*)=(\\w*)\\].*";
  15.         Pattern p = Pattern.compile(pattern, Pattern.MULTILINE);
  16.         Matcher m = p.matcher(message.description);
  17.         if (m.find())
  18.              tempArr = new String[]{m.group(1), m.group(2)};        
  19.         //проеряем ялсяется ли первый параметер директивой к смене типа сообщения
  20.         if(tempArr != null && tempArr[0].toLowerCase().equals(«new_state»)) {
  21.             //определяем на какой тип изменить текущий тип сообщения
  22.             if(tempArr[1].toLowerCase().equals(«done»)) {                
  23.                 //Удаляем из сообщения директиву
  24.                 message.description = message.description.replaceFirst("\\[new_state=done\\]", "");
  25.                 //проверяем задаче какой категории принадлежит
  26.                 if(message.getTask().getCategory().getName().equals(«Изменение»))
  27.                     //устанавливаем новый тип сообщения по его наименованию
  28.                     message.setMstatus(«Завершить»);
  29.                 if(message.getTask().getCategory().getName().equals(«Ошибка»))
  30.                     message.setMstatus(«Исправить»);
  31.             }
  32.             if(tempArr[1].toLowerCase().equals(«start»)) {
  33.                 message.description = message.description.replaceFirst("\\[new_state=start\\]", "");
  34.                 if(message.getTask().getCategory().getName().equals(«Изменение») ||
  35.                   message.getTask().getCategory().getName().equals(«Ошибка»))
  36.                     message.setMstatus(«Начать работу»);
  37.             }
  38.         }
  39.     }
  40. }
  41. catch(Exception e) {
  42.     throw new com.trackstudio.exception.UserMessageException(e.printStackTrace());
  43. }
  44. return message;
Скрипт создали, теперь в TS выбираем в меню «Управление задачами -> Процессы», щелкаем сначала по процессу с наименование «Изменение», затем выбираем вкладку типы операции и кликнем по операции с наименованием «Комментарий»,

открываем вкладку «Триггеры» и в списке «BEFORE Триггер» отмечаем триггер, который называется changeType.bsh.

Проделываем тоже самое и для процесса «Ошибка».

Вроде все, можно пробовать.
Создаем задачу -> копируем из поля «Имя ветви в git» значение ->

создаем письмо с темой, в которую вставляем скопированное значение -> затем в теле письма пишем любой текст и добавляем директиву [new_state=done].

По идее после импорта письма у задачи измениться состояние она должна перейти в состояние завершена и сообщением к этому состоянию будет тело письма.


Итог

Мы расширили способности TS, теперь она умеет:
  • Автоматически создавать правила импорта для создания новых сообщении и задач.
  • Менять статус сообщения в зависимости от найденной в теле письма директивы.
  • Прикреплять файлы к задаче, если они прикреплены к письму.
    Этой способностью TS наделили разработчики, но она нам понадобиться в дальнейшем.

STunnel

Если вы не используете gmail.com как почтовый сервер, то пропустите этот пункт!
В папке куда вы установил stunnel находим файл stunnle.conf (C:\Program Files\stunnel\stunnel.conf). Меняем его содержимое на следующее:
socket = l:TCP_NODELAY=1
socket = r:TCP_NODELAY=1
 
debug = 7
output = stunnel.log
 
client = yes
 
[ssmtp]
accept = 127.0.0.1:465
connect = smtp.gmail.com:465
И для того, чтобы stunnel запускался как служба автоматически выполняем в командной строке:
«C:\Program Files\stunnel\stunnel.exe» –install
источник: Отправка писем из командной строки Windows используя аккаунт GMail


Git

Все что связано с TS производилось на стороне сервера. А теперь все настройки будут на конкретном рабочем месте.
Для того чтобы настроить поведение git при совершении коммита, нам надо изменить 2 хука первый post-commit и prepare-commit-msg, первый выполняется в момент коммита, второй когда формируется сообщение коммита. Файлы хуков лежат в директории DIR_YOUR_REPO/.git/hooks.
  • В файле prepare-commit-msg генерацию шапки сообщения.
    #!/bin/sh
    case "$2,$3" in
      merge,)
        perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/… /#/; print' "$1" ;;
      *) ;;
    esac

    if [ "`git branch | grep -e "^\*.\(new\|bug\)[0-9]\+#[0-9]\+" | cut -f2 -d ' '`" != "" ]; then
        #Помещаем в начало файла с сообщением ключи для отправки письма.
        echo «sendmail» > temp_msg
        echo «to= srvemail@gmail.com» >> temp_msg
        echo "[new_state=done]" >> temp_msg
        cat $1 >> temp_msg
        cat temp_msg > $1
        rm temp_msg
    fi
  • В файле post-commit настроим сбор информации о изменения и отправку почты.
    #!/bin/sh
    # получаем из переменной GIT_AUTHOR_IDENT майл текущего коммитера
    sender=`git var GIT_COMMITTER_IDENT | cut -f1 -d '>' | cut -f 2 -d '<'`

    #проверяем наличие в первой строке сообщения коммита текста sendmail
    if [ `git log -1 | sed «s/[ ]*//g» | grep -e "^sendmail$"` == «sendmail» ]; then

        #формируем тему сообщения, которая берется из названия ветви
        #но это пожеланию, можно сделать чтоб тема была взята из первой строчки основного текста сообщения
        subject=`git branch | grep -e "^\*.\(new\|bug\)[0-9]\+#[0-9]\+" | cut -f2 -d ' '`
        if [ "`git log -1 | sed "s/[]*//g" | gre grep -e "^newroot\=" | cut -f2 -d '='`" != "" ]; then
            subject=`git log -1 | sed «s/[ ]*//g» | grep -e "^newroot\=" | cut -f2 -d '=' | cut -f1 -d '#'`
            subject="$subject#`git branch | grep -e "^\*.\(new\|bug\)[0-9]\+#[0-9]\+" | cut -f2 -d '#'`"

        fi

        #определяем из третей строчки сообщения коммита майл,
        #на который необходимо отправить письмо,
        #по-умолчанию ставиться майл текущего коммитера.
        to=$sender
        if [ "`git log -1 | sed «s/[ ]*//g» | grep -e "^[Tt][Oo]\=" | cut -f2 -d '='`" != "" ]; then
            to=`git log -1 | sed «s/[ ]*//g» | grep -e "^[Tt][Oo]\=" | cut -f2 -d '='"`
        fi

        #формируем имя патчфайла
        patchname="$(date +%j%H%M%s).patch"

        # сохраняем diff соверщеного комита,
        (git diff-index -p -M HEAD^) > $patchname

        #берем из лога git последние сообщение за исключениием управляющего текста
        git log -1 | grep -v "^Date:" | grep -v "^commit " | grep -v "^Author:" | grep -v " *Signed-off-by: " | grep -v "^[ ]*sendmail[ ]*$" | grep -v "^[ ]*newroot\=" | grep -v "^[ ]*[Tt][Oo]\=" | grep -v ".*$subject.*" | sed -e "/^[ ]*$/d" -e "/^$/d" >> temp.tmp
        echo «shortstat» >> temp.tmp
        git diff-index --stat HEAD^ >> temp.tmp

        #отправляем по средствам blat
        blat -charset «utf-8» -f $sender -server localhost -port 465 -u useremail@gmail.com -pw password -to $to -bodyF temp.tmp -subject $subject -base64 -attach $patchname > logSendMail.log

        rm $patchname
        rm temp.tmp
        edit logSendMail.log
        sleep 2s
        rm logSendMail.log
    fi
Эти файлы должны лежать у всех программистов работающих над проектом.


Как это работает

Самое важно, при начале работы над новой задаче, необходимо в оперативном репозитории текущего программиста, создать ветвь с именем, которое создала TS при формировании задачи.
Хук prepare-commit-msg вставляет в начало сообщения коммита параметры отправки письма, такие как:
  • sendmail — отправлять ли письмо. Если в сообщении нет строчки содержащей только это слово, то письмо отправлено не будет.
  • to — на какой email отправить. Если его нет то письмо будет отправлено отправителю.
  • [new_state=done] — директива для TS, что необходимо присвоить новому сообщению тип «Завершен» или «Исправлено».
    Этот параметр можно не указывать, тогда TS будет добавлять новые сообщения с типом «Комментарий».
  • newroot — этот параметр обозначает, надо переделать тему письма. В TS есть возможность перемещать подзадачи из одной задачи в другую, так вот чтоб не создавать новой ветки можно просто указать нового родителя текущей задачи.
    Например, вы создали подзадачу (номер 2084, номер предка 754, ключ new754#2084) и начали над ней работу, а потом поняли, что подзадача должна принадлежать другой родительской задаче (с номером 458, новый ключ new458#2084) и переместили ее к ней. Для того, чтобы в git не заводит новую ветку, можно в сообщении коммита добавляем следующий код
    newroot=new458
    Важно: каждый параметр должен находиться в отдельной строке, количество пробелов или знаков табуляции не влияют на его обнаружение.
Хук post-commit проверяет сообщение коммита на наличие параметров отправки. формирует patch файл с совершенными изменениями и письмо, за тем отправляет письмо от имени коммитера, на указанный в параметре «to» email.Вот пример примениния измениний:
Вводим в командную строку
$ git add --all & git gui


Затем нажимаем Ctrl + Enter и через интервал, который вы установли сборщику почты в TS, провермяем состояние задачи.

Здесь комментарий оформлен html, но это тема отдельного поста.

Заключение

Стандартный рабочий процесс состоит из 4 основных этапов:
  1. Постановка задачи в TS и копировании имени ветви для git.
  2. Создание ветви в git и переход на нее.
    Можно выполнить одной командой:
    $ git checkout -b имя_ветви
  3. Выполнение задачи.
  4. Завершение работы над задачей в TS, совершением коммита в git.
Этап 3 можно расширить промежуточными коммитами в сообщении, которых не будет директивы [new_state=done] — TS будет добавлять комментарии к задаче.


Почему используется именно такой механизм

Причина в том, что у TS пока нет интеграции с SCM GIT.
Да и когда появиться, не понятно каким образом будет реализована работа с распределенными репозиториями.
А данное решение позволяет работать над задачей нескольким программистам, у которых свой зоопарк репозиториях. А потом объединять их в один центральный, в котором порядок и чистота.
К тому же, очень удобно работать не имея прямого доступа до TS.
Создавать задачи может руководитель проекта и назначать на них ответственными конкретных людей. К ним на почту будут приходить письма о том, что создана задача или, что его назначили ответственным. Он в сообщении письма видит какая поставлена задача и имя ветви для git, начинает работу над задачей. По окончанию работы, спокойно делает коммит в репозитории и может уже не волноваться о том, что задачу надо завершить. Причем он также может удаленно создавать подзадачи зная ключ задачи предка, отправляя письма на email, с которого импортируется почта в TS с темой, в которой присутствует нужный ключ. В ответ также будут приходить письма о том, что он создал задачу.

Источники

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+26
Comments 49
Comments Comments 49

Articles