Skip to content

Instantly share code, notes, and snippets.

@adam-p
Last active June 15, 2017 18:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adam-p/9255324 to your computer and use it in GitHub Desktop.
Save adam-p/9255324 to your computer and use it in GitHub Desktop.
Export open Github issues for offline use
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
pip install --upgrade PyGithub
TODO:
- Configurable output directory
- Output more issue info
- Better rendering
'''
import argparse
import os
import sys
import errno
from github import Github
_OUTPUT_DIR = 'issues'
_DIVIDER_LEN = 79
def _makedirs(path):
try:
os.makedirs(path)
except OSError as e:
if e.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
def _export_issue(issue, export_path):
export_path = os.path.join(export_path, '%04d.txt' % (issue.number))
f = open(export_path, 'w')
f.write('#%d\n' % (issue.number,))
f.write('%s\n%s\n\n' % (issue.title, '='*len(issue.title.encode('utf-8'))))
f.write('%s %s\n\n' % (issue.created_at, issue.user.login))
state = '%s %s' % (issue.state, issue.closed_at if issue.closed_at else '')
f.write('%s\n\n' % (state,))
assignee = 'assigned to %s' % (issue.assignee.login,) if issue.assignee else 'unassigned'
f.write('%s\n\n' % (assignee,))
labels = ', '.join([l.name for l in issue.labels])
f.write('%s\n\n' % (labels,))
f.write('%s\n\n' % (issue.body.replace('\r\n', '\n').encode('utf-8')))
comment_pager = issue.get_comments()
current_page = 0
while True:
comments = comment_pager.get_page(current_page)
current_page += 1
current_page += 1
if not comments:
break
for comment in comments:
f.write('%s\n\n' %('-'*_DIVIDER_LEN,))
f.write('%s %s\n\n' % (comment.created_at, comment.user.login))
f.write('%s\n\n' % (comment.body.replace('\r\n', '\n').encode('utf-8')))
f.close()
def _export_issues_for_project(gh, projname, state, labels, exc_labels, export_path):
repo = gh.get_repo(projname)
real_labels = None
if labels:
real_labels = [repo.get_label(label_str) for label_str in labels.split(',')]
if exc_labels:
exc_labels = exc_labels.split(',')
else:
exc_labels = ()
# It seems impossible to get assigned and unassigned issues in a single query,
# so we'll do one for each state.
for assignee in ['*', 'none']:
for issue in repo.get_issues(state=state, labels=real_labels, assignee=assignee):
if [l for l in issue.labels if l.name in exc_labels]:
continue
print '#%d %s' % (issue.number, issue.title)
_export_issue(issue, export_path)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Export Github issues to disk')
parser.add_argument('projnames', metavar='username/projectname', type=str, nargs='+',
help='fully qualified "username/projectname" of projects to export issues from')
parser.add_argument('--auth-token', action='store',
help='Github auth token to use (optional, but helps prevent request rate denial)')
parser.add_argument('--state', action='store',
help='Indicates the state of the issues to return. Can be either open, closed, or all. Default: open')
parser.add_argument('--labels', action='store',
help='A list of comma separated label names. Example: bug,ui,@high')
parser.add_argument('--exc_labels', action='store',
help='A list of comma separated label names TO EXCLUDE. Example: enhancement,@low')
args = parser.parse_args()
gh = Github(args.auth_token)
for projname in args.projnames:
export_path = os.path.normpath(os.path.join(_OUTPUT_DIR, projname))
_makedirs(export_path)
_export_issues_for_project(
gh, projname, args.state, args.labels, args.exc_labels, export_path)
sys.exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment