mirror of
https://github.com/ditkrg/todo-to-issue-action.git
synced 2026-01-22 22:06:43 +00:00
Initial commit
This commit is contained in:
commit
bf153fe2df
17
.github/workflows/workflow.yaml
vendored
Normal file
17
.github/workflows/workflow.yaml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: "Workflow"
|
||||||
|
on: ["push"]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- uses: "actions/checkout@master"
|
||||||
|
- name: "TODO to Issue"
|
||||||
|
uses: "alstr/todo-to-issue-action@master"
|
||||||
|
with:
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
HEAD: ${{ github.event.head }}
|
||||||
|
BEFORE: ${{ github.event.before }}
|
||||||
|
SHA: ${{ github.sha }}
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
LABEL: "# TODO"
|
||||||
|
id: "todo"
|
||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3-slim AS builder
|
||||||
|
ADD . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN pip install --target=/app requests
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/python3-debian10
|
||||||
|
COPY --from=builder /app /app
|
||||||
|
WORKDIR /app
|
||||||
|
ENV PYTHONPATH /app
|
||||||
|
CMD ["/app/main.py"]
|
||||||
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# TODO to Issue Action
|
||||||
|
|
||||||
|
This action will convert your `# TODO` comments to GitHub issues when a new commit is pushed.
|
||||||
|
|
||||||
|
It will also close an issue when a `# TODO` is removed in a pushed commit.
|
||||||
|
|
||||||
|
The `# TODO` comment is commonly used in Python, but this can be customised to whatever you want.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Create a workflow file in your .github/workflows directory as follows:
|
||||||
|
|
||||||
|
### workflow.yaml
|
||||||
|
|
||||||
|
name: "Workflow"
|
||||||
|
on: ["push"]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- uses: "actions/checkout@master"
|
||||||
|
- name: "TODO to Issue"
|
||||||
|
uses: "alstr/todo-to-issue-action@master"
|
||||||
|
with:
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
BEFORE: ${{ github.event.before }}
|
||||||
|
SHA: ${{ github.sha }}
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
LABEL: "# TODO"
|
||||||
|
id: "todo"
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
| Input | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `REPO` | The path to the repository where the action will be used, e.g. 'alstr/my-repo' (automatically set) |
|
||||||
|
| `BEFORE` | The SHA of the last pushed commit (automatically set) |
|
||||||
|
| `SHA` | The SHA of the latest commit (automatically set) |
|
||||||
|
| `TOKEN` | The GitHub access token to allow us to retrieve, create and update issues (automatically set) |
|
||||||
|
| `LABEL` | The label that will be used to identify TODO comments (by default this is `# TODO` for Python) |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Adding TODOs
|
||||||
|
|
||||||
|
def hello_world():
|
||||||
|
# TODO Come up with a more imaginative greeting
|
||||||
|
print('Hello world!')
|
||||||
|
|
||||||
|
This will create an issue called "Come up with a more imaginative greeting".
|
||||||
|
|
||||||
|
**The action expects a space to follow the `TODO` label.**
|
||||||
|
|
||||||
|
Should the title be longer than 50 characters, it will be truncated for the issue title.
|
||||||
|
|
||||||
|
The full title will be included in the issue body and a `todo` label will be attached to the issue.
|
||||||
|
|
||||||
|
### Removing TODOs
|
||||||
|
|
||||||
|
def hello_world():
|
||||||
|
print('Hello world!')
|
||||||
|
|
||||||
|
Removing the `# TODO` comment will close the issue on push.
|
||||||
|
|
||||||
|
### Updating TODOs
|
||||||
|
|
||||||
|
def hello_world():
|
||||||
|
# TODO Come up with a more imaginative greeting, like "Greetings world!"
|
||||||
|
print('Hello world!')
|
||||||
|
|
||||||
|
Should you change the `# TODO` text, this will currently create a new issue, so bear that in mind.
|
||||||
|
|
||||||
|
This may be updated in future.
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
Thanks to Jacob Tomlinson for his handy overview of GitHub Actions:
|
||||||
|
|
||||||
|
https://www.jacobtomlinson.co.uk/posts/2019/creating-github-actions-in-python/
|
||||||
6
action.yaml
Normal file
6
action.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
name: "TODO to Issue"
|
||||||
|
description: "Converts IDE TODO comments to GitHub issues"
|
||||||
|
author: "Alastair Mooney"
|
||||||
|
runs:
|
||||||
|
using: "docker"
|
||||||
|
image: "Dockerfile"
|
||||||
97
main.py
Normal file
97
main.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Convert IDE TODOs to GitHub issues."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
|
base_url = 'https://api.github.com/repos/'
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
repo = os.getenv('INPUT_REPO')
|
||||||
|
before = os.getenv('INPUT_BEFORE')
|
||||||
|
sha = os.getenv('INPUT_SHA')
|
||||||
|
label = os.getenv('INPUT_LABEL')
|
||||||
|
params = {
|
||||||
|
'access_token': os.getenv('INPUT_TOKEN')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Let's compare the last two pushed commits.
|
||||||
|
diff_url = f'{base_url}{repo}/compare/{before}...{sha}'
|
||||||
|
diff_headers = {
|
||||||
|
'Accept': 'application/vnd.github.v3.diff'
|
||||||
|
}
|
||||||
|
|
||||||
|
diff_request = requests.get(url=diff_url, headers=diff_headers, params=params)
|
||||||
|
if diff_request.status_code == 200:
|
||||||
|
diff = diff_request.text
|
||||||
|
|
||||||
|
# Check for additions in the diff.
|
||||||
|
addition_pattern = re.compile(r'(?<=^\+).*', re.MULTILINE)
|
||||||
|
additions = addition_pattern.findall(diff)
|
||||||
|
new_issues = []
|
||||||
|
|
||||||
|
# Filter the additions down to newly added TODOs.
|
||||||
|
for addition in additions:
|
||||||
|
todo_pattern = re.compile(r'(?<=' + label + r'\s).*')
|
||||||
|
todos = todo_pattern.search(addition)
|
||||||
|
if todos:
|
||||||
|
new_issues.append(todos.group(0))
|
||||||
|
|
||||||
|
# Create new issues for any newly added TODOs.
|
||||||
|
issues_url = f'{base_url}{repo}/issues'
|
||||||
|
issue_headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
for issue in new_issues:
|
||||||
|
title = issue
|
||||||
|
# Truncate the title if it's longer than 50 chars.
|
||||||
|
if len(title) > 50:
|
||||||
|
title = issue[:50] + '...'
|
||||||
|
new_issue_body = {'title': title, 'body': issue, 'labels': ['todo']}
|
||||||
|
requests.post(url=issues_url, headers=issue_headers, params=params, data=json.dumps(new_issue_body))
|
||||||
|
# Don't add too many issues too quickly.
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
# Check for deletions in the diff.
|
||||||
|
deletion_pattern = re.compile(r'(?<=^-).*', re.MULTILINE)
|
||||||
|
deletions = deletion_pattern.findall(diff)
|
||||||
|
closed_issues = []
|
||||||
|
|
||||||
|
# Filter the deletions down to removed TODOs.
|
||||||
|
for deletion in deletions:
|
||||||
|
todo_pattern = re.compile(r'(?<=' + label + r'\s).*')
|
||||||
|
todos = todo_pattern.search(deletion)
|
||||||
|
if todos:
|
||||||
|
closed_issues.append(todos.group(0))
|
||||||
|
|
||||||
|
if len(closed_issues) > 0:
|
||||||
|
# Get the list of current issues.
|
||||||
|
list_issues_request = requests.get(issues_url, headers=issue_headers, params=params)
|
||||||
|
if list_issues_request.status_code == 200:
|
||||||
|
current_issues = list_issues_request.json()
|
||||||
|
for closed_issue in closed_issues:
|
||||||
|
title = closed_issue
|
||||||
|
if len(title) > 50:
|
||||||
|
title = closed_issue[:50] + '...'
|
||||||
|
|
||||||
|
# Compare the title of each closed issue with each issue in the issues list.
|
||||||
|
for current_issue in current_issues:
|
||||||
|
if current_issue['title'] == title:
|
||||||
|
# The titles match, so we will try and close the issue.
|
||||||
|
issue_number = current_issue['number']
|
||||||
|
|
||||||
|
update_issue_url = f'{base_url}{repo}/issues/{issue_number}'
|
||||||
|
body = {'state': 'closed'}
|
||||||
|
requests.patch(update_issue_url, headers=issue_headers, params=params,
|
||||||
|
data=json.dumps(body))
|
||||||
|
# Don't update too many issues too quickly.
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user