Git + TrackStudio - development automation

  • Tutorial
This article is about how to use TrackStudio and Git to organize a convenient software development process. The article discusses an option for Windows OS. But if you wish, it’s not difficult to do everything for other OCs.
For this we need:
  1. TrackStudio is a universal task management system.
  2. Git - VCS.
  3. Blat - A small (70kB) open source console program for Windows that allows you to send e-mail via SMTP from the command line with the attached files.
  4. STunnel - A program that allows you to encrypt arbitrary TCP connections inside SSL. The tool is available for Unix and Windows.
    Needed if you use gmail.com as a mail service. Since blat cannot work directly with gmail.
Task:
Organize the workflow in such a way that when committing in git, comments with various message types were automatically added to TrackStudio task and the text of the comment was the text of the commit message.

Installation

Trackstudio

We do everything as it is written here.
Installing the rest of the application is very simple, I note only one thing that blat should be put in the folder with git.exe (I have C: / Program files / Git / bin), or add the folder in which blat is located to PATH .


Customization

Trackstudio

TrackStudio can pick up emails from email and import them as tasks.
To configure this process, you must specify the mail collection parameters in the TS_HOME \ etc \ trackstudio.mail.properties file (if you do not have one):
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=интервал проверки почты
here is an example for 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
Everything is described in more detail in the documentation .


Script to create an import rule

Now in TS, for the ancestor of each task with the category category “Change” or “Error”, you need to create an import rule and it is desirable that they be created on behalf of the administrator. But every time you do it, it’s unprofitable with pens - we solve this problem by writing a plugin. In the TS_HOME \ plugins \ scripts \ after_create_task folder (it contains the scripts that are executed after the tasks are created), create the createRuleImport.bsh file with the following code:
  1. / **
  2. * The script creates an import rule for an ancestor task when creating a child task.
  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.     // object for working with mail import rules
  16.     SecuredMailImportAdapterManager mam = AdapterManager.getInstance ()
  17.         .getSecuredMailImportAdapterManager ();
  18.         
  19.     // contains methods for working with message types
  20.     SecuredStepAdapterManager sam = AdapterManager.getInstance ()
  21.         .getSecuredStepAdapterManager ();
  22.         
  23.     // list of available message types for the task and current user
  24.     ArrayList arrMS = sam.getAvailableMstatusList (task.getSecure (), task.getId ());
  25.     
  26.     // message type
  27.     SecuredMstatusBean curMS = null;            
  28.          
  29.     String pefixStr = "new";    
  30.     / **
  31.      * Check the category type of the task being created.
  32.      * If the task category is “Error” = category name in TS
  33.      * /
  34.     if (task.getCategory (). getName (). equals ("Error"))
  35.         pefixStr = "bug";
  36.     
  37.     / **
  38.      * We generate a key for importing messages of the current task:
  39.      * prefix + parent task number + # + task number
  40.      * this key should appear in the subject line
  41.      * eg:
  42.      * new759 # 2845 any text, anyway it will be ignored when adding a community.
  43.      * /
  44.     String mailimKey = pefixStr + task.getParent (). GetNumber () + "#" + task.getNumber ();     
  45.     
  46.     / **
  47.      * Do I need to create a rule.
  48.      * Since an ancestor task can have several subtasks,
  49.      * then it is necessary to check the existence of the import rule,
  50.      * that would not produce them.
  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.         // “Comment” or “Comment” is the name of the message type        
  60.         if (msn.compareToIgnoreCase ("Comment") == 0 || msn.compareToIgnoreCase ("Comment") == 0) {
  61.             // Get data for root user
  62.             SecuredUserBean adminUser = new SecuredUserBean (CSVImport.findUserIdByLogin ("root"), sc);
  63.             // Current user context
  64.             SessionContext needSc = task.getSecure ();
  65.             // check if root is authorized
  66.             if (SessionManager.getInstance (). existUserSession (adminUser.getUser ())) {
  67.                 for (SessionContext curSc: SessionManager.getInstance (). getSessions ()) {
  68.                     if (curSc.getUser (). getLogin (). equals ("root")) {
  69.                         // if so, get its context
  70.                         needSc = curSc;
  71.                         break;
  72.                     }
  73.                 }
  74.             }
  75.             else {
  76.                 // if not, then create its context
  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.                     // We are looking for the rule to import new tasks from the ancestor.                    
  84.                     found = curMailS.getKeywords (). equals (pefixStr + task.getParent (). getNumber ());
  85.                     if (found)                        
  86.                         break;
  87.                 }
  88.             
  89.             if (! found) {         
  90.                 / **
  91.                  * if not found, then create a new import rule for the ancestor
  92.                  * as root, as TS supports the ability to create
  93.                  * users of individual mail import rules need
  94.                  * That this rule would be visible to all.
  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.     // Add a value for the UDF field with the name "Branch name in git".
  104.     AdapterManager.getInstance (). GetSecuredUDFAdapterManager (). SetTaskUDFValueSimple (
  105.         task.getSecure (), task.getId (), "Branch name in git", mailimKey);
  106. }
  107. catch (GranException e) {
  108.     throw new com.trackstudio.exception.UserMessageException (e.getMessage ());
  109. }
  110. return task;

We created the script, now in TS select “Task Management -> Categories” in the menu, first click on the category with the name “Change”, open the “Triggers” tab and select the trigger called createRuleImport.bsh in the “AFTER Trigger” list . We are doing the same for the “Error” category.






If everything is done correctly, then when creating a new task, the ancestor will have the import rule newNUMBER_EXT or bugNUMBER_EXT. When sending a letter to the email address from which the mail is imported, with the subject line “new_TRAN_NEW new task”, TS will create a task in the ANCESTOR with a name that is equal to the subject of the letter. And if you send a letter in the subject, which has the key newNUMBER_TRAN # TASK NUMBER (the task exists), TS imports the letter as comments on the task and the comment message will be the body of the letter. Moreover, if you attach a file to the letter, TS will attach it to the task.


How to add a UDF field

To do this, in the TS top menu, select "Task Management -> Processes", open the process of interest to you and click on the "String" button in the "Additional Fields" tab. Indicate the name you need in the name, in our case, “Branch name in git”, and click the Save button. But this is just to copy and paste quickly into sh when creating a new branch in git, such as convenient.




Script for processing a message received via mail

TS already knows how to create messages with the type "Comments" on tasks from letters.
Now we need to make TS change the message type to “Complete” or “Fix” for the category “Change” or “Error”, respectively (“Comments”, “Finish”, “Change” and “Error” - names of the message type and categories , they depend on how you configure your workflow in TS; if you use a demo Russian database, then they will be just that). We need to create a script that will look in the message body for some key that will tell TS that it is necessary to change the type of message. I suggest using the [new_state = done] construct, but that's up to you. Create in the folder TS_HOME \ plugins \ scripts \ before_add_message, which hosts the scripts executed before the message is added, the changeType.bsh file with the code:
  1. / **
  2. * The script changes the type of the imported message if it is found in the message body
  3. * construction [new_state = type], where type can be done or 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.         // Looking for the construction we need in the message        
  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.         // check if the first parameter is a directive to change the type of message
  20.         if (tempArr! = null && tempArr [0] .toLowerCase (). equals ("new_state")) {
  21.             // determine which type to change the current message type
  22.             if (tempArr [1] .toLowerCase (). equals ("done")) {                
  23.                 // Remove the directive from the message
  24.                 message.description = message.description.replaceFirst ("\\ [new_state = done \\]", "");
  25.                 // check the task of which category belongs
  26.                 if (message.getTask (). getCategory (). getName (). equals ("Change"))
  27.                     // set a new message type by its name
  28.                     message.setMstatus ("Complete");
  29.                 if (message.getTask (). getCategory (). getName (). equals ("Error"))
  30.                     message.setMstatus (“Fix”);
  31.             }
  32.             if (tempArr [1] .toLowerCase (). equals ("start")) {
  33.                 message.description = message.description.replaceFirst ("\\ [new_state = start \\]", "");
  34.                 if (message.getTask (). getCategory (). getName (). equals ("Change") ||
  35.                   message.getTask (). getCategory (). getName (). equals ("Error"))
  36.                     message.setMstatus ("Get started");
  37.             }
  38.         }
  39.     }
  40. }
  41. catch (Exception e) {
  42.     throw new com.trackstudio.exception.UserMessageException (e.printStackTrace ());
  43. }
  44. return message;
We created the script, now in TS select “Task Management -> Processes” in the menu, first click on the process with the name “Change”, then select the operation types tab and click on the operation with the name “Comment”, open the “Triggers” tab and in the list "BEFORE Trigger" marks the trigger called changeType.bsh . We are doing the same for the “Error” process. Like everything, you can try. Create a task -> copy the value from the "Branch name in git" field -> create a message with a subject into which we paste the copied value -> then in the body of the message we write any text and add the directive [new_state = done].










In theory, after the letter is imported, the task has a state change, it should go to the completed state and the message body will be a message to this state.


Total

We have expanded the capabilities of TS, now she can:
  • Automatically create import rules to create new messages and tasks.
  • Change the message status depending on the directive found in the message body.
  • Attach files to the task, if they are attached to the letter.
    This ability was given to TS by developers, but we will need it in the future.

Stunnel

If you are not using gmail.com as a mail server, skip this step!
In the folder where you installed stunnel find file stunnle.conf (the C: \ Program Files \ stunnel \ stunnel.conf). We change its contents to the following:
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
And so that stunnel starts as a service, we automatically execute it on the command line:
"C: \ Program Files \ stunnel \ stunnel.exe" –install
source: Sending letters from the Windows command line using a GMail account


Git

Everything related to TS was done on the server side. And now all the settings will be at a specific workplace.
In order to configure the behavior of git when committing, we need to change 2 hooks, the first post-commit and prepare-commit-msg , the first is executed at the time of the commit, the second when the commit message is generated. Hook files are in the directory DIR_YOUR_REPO / .git / hooks .
  • In the prepare-commit-msg file, generate a message header.
    #! / 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
        # Put at the beginning of the file with the message the keys to send the letter.
        echo  "sendmail"  >  temp_msg
        echo  "to = srvemail@gmail.com"  >>  temp_msg
        echo  "[new_state = done]"  >>
           temp_msg
        cat  temp_msg  >  $ 1
        rm  temp_msg
    fi
  • In the post-commit file, we’ll configure the collection of information about the change and sending mail.
    #! / bin / sh
    # we get from the variable GIT_AUTHOR_IDENT the mail of the current committer
    sender = `git var GIT_COMMITTER_IDENT | cut -f1 -d '>' | cut -f 2 -d '<' `

    # check for sendmail
    if  [  ` git log -1 | sed "s / [] * // g" | grep -e "^ sendmail $" `  ==" sendmail "  ];  then

        # we form the subject of the message, which is taken from the name of the branch
        # but this is optional, you can make the topic be taken from the first line of the main body of the message
        subject = `git branch | grep -e "^ \ *. \ (new \ | bug \) [0-9] \ + # [0-9] \ +" | cut -f2 -d '' `
        if  [  " `git log -1 | sed"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

        # we determine from the third line of the commit message the mail,
        # to which you want to send an email,
        # by default, the mail of the current committer is set.
        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

        # form the name of the patch file
        patchname = " $ (date +% j% H% M% s) .patch "

        # save the diff of the committed commit,
        ( git diff-index -p -M HEAD ^)  >  $ patchname

        # take the last message from the
        git log except for the control text 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

        # we send by means of 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
These files should be kept by all programmers working on the project.


How it works

Most importantly, when starting work on a new task, it is necessary in the operational repository of the current programmer to create a branch with the name that TS created when the task was created.
The prepare-commit-msg hook inserts message sending options at the top of the commit message, such as:
  • sendmail - whether to send the letter. If the message does not contain a line containing only this word, then the letter will not be sent.
  • to - to which email to send. If it is not, the letter will be sent to the sender.
  • [new_state = done] - a directive for TS that you need to set the message "Completed" or "Fixed" to the new message.
    This parameter can be omitted, then TS will add new messages with the type “Comment”.
  • newroot - this parameter indicates whether the subject of the message should be redone. In TS, it is possible to move subtasks from one task to another, so just to not create a new branch, you can simply specify the new parent of the current task.
    For example, you created a subtask (number 2084, ancestor number 754, key new754 # 2084) and started working on it, and then realized that the subtask should belong to another parent task (with number 458, new key new458 # 2084) and moved it to her. So that git does not start a new branch, you can add the following code in the commit message
    newroot=new458
    Important: each parameter must be on a separate line, the number of spaces or tabs does not affect its detection.
The post-commit hook checks the commit message for send parameters. forms a patch file with the changes made and a letter, then sends a letter on behalf of the committer to the email specified in the “to” parameter. Here is an example of applying the changes: Enter the
command line
$ git add --all & git gui


Then press Ctrl + Enter and through the interval that you set the mail collector in TS, check the status of the task. Here the comment is framed in html, but this is the topic of a separate post.



Conclusion

A standard workflow consists of 4 main steps:
  1. Setting the task in TS and copying the branch name for git.
  2. Creating a branch in git and switching to it.
    You can execute it with one command:
    $ git checkout -b имя_ветви
  3. Task completion.
  4. Completing a task in TS, committing to git.
Stage 3 can be extended with intermediate commits in the message, which will not be directive [new_state = done] - TS will add comments to the task.


Why is this mechanism used?

The reason is that TS does not yet have integration with SCM GIT.
And when it appears, it is not clear how the work with distributed repositories will be implemented.
And this solution allows several programmers who have their own zoo repositories to work on the task. And then combine them into one central, in which order and purity.
In addition, it is very convenient to work without having direct access to TS.
The project manager can create tasks and assign specific people to them. Letters will come to their mail that they have created a task or that he has been appointed responsible. He sees in the message message what the task is and the branch name for git, starts work on the task. At the end of the work, he quietly commits in the repository and may no longer worry that the task needs to be completed. Moreover, he can also remotely create subtasks knowing the key of the task of the ancestor by sending letters to the email from which mail is imported to TS with the subject in which the desired key is present. In response, letters will also come that he created the task.

Sources


Also popular now: