Skip to content

Writing GitMate Plugins

This tutorial contains step-by-step guide to create a GitMate Plugin with an example.

We'll be creating a plugin to acknowledge/unacknowledge commits in response to ack requests. Let's name our plugin ack.

Note

  • Make sure you have set up the development environment before proceeding any further.
  • Replace every occurence of ack in this tutorial with your plugin name.

1. Initializing the plugin

From the project directory, run:

$ python manage.py startplugin ack

This will create a template plugin directory named gitmate_ack with following structure.

plugins/gitmate_ack
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── responders.py
└── tests
    ├── __init__.py
    └── test_gitmate_ack.py

2. Check system status

$ python manage.py check

You can run this command again in the future to check if everything is working.

3. Adding plugin settings

Plugin settings are the additional data your plugins might require to do their job. Our ack plugin requires two settings:

  • ack_strs: Phrases that will be recognized as ack commands(comma seperated)
  • unack_strs: Phrases that will be recognized as unack commands(comma seperated)

To add settings open plugins/gitmate_ack/models.py and add

# Auto generated code
from gitmate_config.models import SettingsBase


class Settings(SettingsBase):
    # Add your custom plugin settings here

    ack_strs = models.TextField(
        default='ack',
        help_text='Phrases that will be recognized as ack commands.')
    unack_strs = models.TextField(
        default='unack',
        help_text='Phrases that will be recognized as unack commands.')

Then migrate the settings to the database by running,

python3 manage.py makemigrations
python3 manage.py migrate

4. Writing a responder

Plugins respond to distinctive events received through webhooks. Our plugin responds to pull request comments. This means we have to attach our plugin to the event IGitt.Interfaces.Actions.MergeActions.COMMENTED. We could add in multiple events too and the responder would be executed for all such events. To do so, open plugins/gitmate_ack/responders.py and edit the ResponderRegistrar.responder decorator params. Then add your desired functionality to the responder.

A word about Responders

  • While registering the responder with ResponderRegistrar.responder decorator make sure you use the exact plugin name. (ack in this case)
  • All GitMate responder functions are registered by providing default arguments. (pr and comment in this case)
  • Take note that the responder should consist of the arguments that would be patched in through gitmate_hooks/views.py.
  • All keyword arguments passed in should match with the exact names provided in the Settings model. (ack_strs and unack_strs in this case)
@ResponderRegistrar.responder(
     'ack',
     MergeRequestActions.COMMENTED
 )
 def gitmate_ack(pr: MergeRequest,
                 comment: Comment,
                 ack_strs: str = 'ack',        # remember settings
                 unack_strs: str = 'unack'):   # make sure to add defaults
     """
     A responder to ack and unack commits
     """
     # This is responder logic for ack plugin
     # Add your own logic for your plugin

     body = comment.body.lower()
     commits = pr.commits
     pattern = '(^{k}\s)|(\s{k}\s)|(\s{k}$)'

     unack_strs = get_keywords(unack_strs)
     for kw in unack_strs:
         if re.search(pattern.format(k=kw), body):
             for commit in commits:
                 if commit.sha[:6] in body:
                     commit.unack()

     ack_strs = get_keywords(ack_strs)
     for kw in ack_strs:
         if re.search(pattern.format(k=kw), body):
             for commit in commits:
                 if commit.sha[:6] in body:
                     commit.ack()

Choose your responder event according to your needs. You could add multiple responders as well as multiple events to a responder. You can use any number of the plugin settings required by your responder. If default value is not provided, the settings won't be passed to the responder.

5. Adding a listener

Now you need to actually register the MergeRequestAction.COMMENTED listener with GitMate's webhook handler (otherwise it wouldn't know what to listen for and your function in responders.py will never be called). To do this, go to gitmate_hooks/views.py and add a listener. For example, for the MergeRequestAction.COMMENTED action we did:

    if event == 'issue_comment':
        if webhook_data['action'] != 'deleted':
            comment = webhook_data['comment']
            pull_request_obj = GitHubMergeRequest(
                token,
                repository['full_name'],
                webhook_data['issue']['number'])
            comment_obj = GitHubComment(
                token,
                repository['full_name'],
                comment['id'])
            ResponderRegistrar.respond(
                MergeRequestActions.COMMENTED,
                repo_obj,
                pull_request_obj,
                comment_obj,
                options=repo_obj.get_plugin_settings())

Note

A PR comment is called issue_comment in GitHub's API - hence the if event == 'issue_comment'.

The body of webhook is available in webhook_data which contains additional information required by our responder. The listener and the responder are connected via ResponderRegistrar.respond.

A word about params

  • First parameter is the event you registered your responder with. For ack plugin it is MergeRequestActions.COMMENTED.
  • Second parameter is repo_obj and it is mandatory.
  • The positional arguments for your responder follow next.
  • options param provide the keyword arguments(settings) to the responder.

Finally, to register the plugin to gitmate's database, run:

$ python3 manage.py upmate

6. Writing tests

Let's not forget to write tests for our newly created plugin. To add tests, open gitmate_ack/tests/test_gitmate_ack.py. Follow the template and add test methods. We use pytest along with pytest-cov, and pytest-django plugins for tests.

Success

Our ack plugin is completed and fully functional.