Skip to content

Instantly share code, notes, and snippets.

@ryanmark
Last active September 2, 2015 13:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ryanmark/40b58941a95813402705 to your computer and use it in GitHub Desktop.
Save ryanmark/40b58941a95813402705 to your computer and use it in GitHub Desktop.
Import issues from markdown formatted text into github issues

Generate Github Issues

This script will read a markdown-formatted text file and generate milestones and issues in a Github project.

The script will go line by line, looking for lines that start with #, - or *. Lines that start with # will create a new milestone. All bulleted items after a header will be created as issues and assigned to the last milestone.

Milestones will be automatically assigned due-dates. Starting with the closest Friday at 1:30pm EST, every milestone will get a different due-date spaced one week apart.

To run the script, you need to first get a Github API key. Visit the personal access token page and generate a new token. Make sure it has repo access.

Add this line, including your new key, to your ~/.bash_profile or run it as a command before you use the import script.

export GITHUB_API_TOKEN="mykey"

Make sure you have all necessary python libs installed

$ easy_install pip
$ pip install pygithub3

Now create a new text file and start adding your issues. When you're ready to upload them, run this command...

$ python import.py -h
usage: import.py [-h] ticket_file project

Load issues into github

positional arguments:
  ticket_file  file with tickets
  project      GitHub project (EX: account/project)

optional arguments:
  -h, --help   show this help message and exit

$ python import.py myproject.md account/project
#!/usr/bin/env python
import os
import argparse
import re
from datetime import datetime, timedelta
from pygithub3 import Github
import requests
from time import sleep
# Milestones are preceded by a '#'
MILESTONE_RE = re.compile('^\s*#\s*(?P<text>.+)\s*$')
# Issues are preceded by a '*' or '-'
ISSUE_RE = re.compile('^\s*(-|\*)\s*(?P<text>.+)\s*$')
# Every Friday
REVIEW_DOW = 4
# 1:30pm EST
REVIEW_TIME = '18:30'
gh = Github(token=os.environ['GITHUB_API_TOKEN'])
CACHE = dict()
def main():
parser = argparse.ArgumentParser(description='Load issues into github')
parser.add_argument('ticket_file',
help='file with tickets')
parser.add_argument('project',
help='GitHub project (EX: account/project)')
args = parser.parse_args()
CACHE['account'], CACHE['project'] = args.project.strip().split('/')
milestones = list()
milestone = None
issues = list()
with open(args.ticket_file) as f:
for line in f.readlines():
milestone_match = MILESTONE_RE.match(line)
issue_match = ISSUE_RE.match(line)
if milestone_match:
milestone = {
'title': milestone_match.group('text'),
'due_on': review_day(len(milestones)).isoformat() + 'Z',
'issues': list()
}
milestones.append(milestone)
elif issue_match:
issues.append(issue_match.group('text'))
if milestone:
milestone['issues'].append(
issue_match.group('text'))
try:
if milestones:
print "I haz %i milestones and %i issues" % (
len(milestones), len(issues))
create_milestones(milestones)
elif issues:
print "I haz %i issues" % len(issues)
create_issues(issues)
except requests.exceptions.HTTPError, ex:
print ex.response.body
def create_milestones(milestones):
milestones.reverse()
while len(milestones) > 0:
milestone = milestones.pop()
issues = milestone.pop('issues')
milestone_id = get_or_create_milestone(milestone)
create_issues(issues, milestone_id=milestone_id)
def create_issues(issues, milestone_id=None):
issues.reverse()
while len(issues) > 0:
issue = {'title': issues.pop()}
if milestone_id:
issue['milestone'] = int(milestone_id)
get_or_create_issue(issue)
def get_or_create_milestone(data):
if 'milestones' not in CACHE:
CACHE['milestones'] = gh.issues.milestones.list(CACHE['account'], CACHE['project']).all()
sleep(1)
for m in CACHE['milestones']:
if m.title == data['title']:
print "Found milestone \"%s\"" % data['title']
return m.number
print "Create milestone \"%s\"" % data['title']
resp = gh.issues.milestones.create(data, CACHE['account'], CACHE['project'])
sleep(1)
CACHE['milestones'].append(resp)
return resp.number
def get_or_create_issue(data):
if 'issues' not in CACHE:
CACHE['issues'] = gh.issues.list_by_repo(CACHE['account'], CACHE['project']).all()
sleep(1)
for m in CACHE['issues']:
if m.title == data['title']:
print "Already created issue \"%s\"" % data['title']
return m.number
print "Create issue \"%s\"" % data['title']
resp = gh.issues.create(data, CACHE['account'], CACHE['project'])
sleep(1)
CACHE['issues'].append(resp)
return resp.number
def review_day(weeks_from_now):
now = datetime.utcnow()
if now.weekday() >= REVIEW_DOW:
next_day = now + timedelta(days=(7 - now.weekday() - REVIEW_DOW))
else:
next_day = now + timedelta(days=(REVIEW_DOW - now.weekday()))
args = list(next_day.timetuple())[:-2]
(args[3], args[4]) = map(lambda x: int(x), REVIEW_TIME.split(':'))
args[5] = 0
due_date = datetime(*args)
return due_date + timedelta(weeks=weeks_from_now)
if __name__ == '__main__':
main()

Iteration 1

  • thing 1
  • thing 2

Iteration 2

  • thing 3
  • thing 4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment