Skip to content
This repository was archived by the owner on Apr 13, 2023. It is now read-only.

Commit 4227adb

Browse files
author
staticdev
committed
Initial commit
1 parent 7cee37d commit 4227adb

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
THIS PROJECT IS ALPHA, UNSTABLE AND INCOMPLETE. DON'T USE IT YET.
2+
=================================================================

src/__main__.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import getpass
2+
import logging
3+
import sys
4+
from typing import Any
5+
from typing import Dict
6+
7+
import github
8+
import inquirer
9+
10+
11+
# starting log
12+
FORMAT = "%(asctime)s %(message)s"
13+
DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
14+
logging.basicConfig(level=logging.ERROR, format=FORMAT, datefmt=DATE_FORMAT)
15+
LOGGER = logging.getLogger(__name__)
16+
17+
18+
def _not_empty_validation(answers: Dict[str, Any], current: str) -> bool:
19+
"""Validade if current answer is not just spaces.
20+
21+
Args:
22+
answers (Dict[str, Any]): answers to previous questions (ignored).
23+
current (str): answer to current question.
24+
25+
Returns:
26+
bool: if is valid output.
27+
"""
28+
current_without_spaces = current.strip()
29+
return True if current_without_spaces else False
30+
31+
32+
def _ignore_if_not_confirmed(answers: Dict[str, Any]) -> bool:
33+
return not answers["confirmation"]
34+
35+
36+
class Manager():
37+
def __init__(self):
38+
self.github_repos = []
39+
self.selected_github_repos = []
40+
self.github_username = getpass.getuser()
41+
self.github_connection = None
42+
43+
def issue_create(self) -> None:
44+
questions = [
45+
inquirer.Text('title', message='Write an issue title', validate=_not_empty_validation),
46+
inquirer.Text('body', message='Write an issue body [optional]'),
47+
inquirer.Text('labels', message='Write issue labels separated by comma [optional]'),
48+
inquirer.Confirm('correct', message='Confirm creation of issue for the project(s) {}. Continue?'.format(self.selected_github_repos), default=False),
49+
]
50+
answers = inquirer.prompt(questions)
51+
labels = [label.strip() for label in answers['labels'].split(",")]
52+
if answers['correct']:
53+
for github_repo in self.selected_github_repos:
54+
repo = self.github_connection.get_repo(github_repo)
55+
try:
56+
repo.create_issue(title=answers['title'], body=answers['body'], labels=labels)
57+
print("{}: issue created successfully.".format(github_repo))
58+
except github.GithubException as github_exception:
59+
if github_exception.data["message"] == "Issues are disabled for this repo":
60+
print("{}: {}. It may be a fork.".format(github_repo, github_exception.data["message"]))
61+
else:
62+
print("{}: {}.".format(github_repo, github_exception.data["message"]))
63+
64+
def pull_request_create(self) -> None:
65+
# TODO create issue for github to add labels to their API
66+
questions = [
67+
inquirer.Text('base', message='Write base branch name (destination)', default="master", validate=_not_empty_validation),
68+
inquirer.Text('head', message='Write the head branch name (source)', validate=_not_empty_validation),
69+
inquirer.Text('title', message='Write an pull request title', validate=_not_empty_validation),
70+
inquirer.Text('body', message='Write an pull request body [optional]'),
71+
inquirer.Confirm('draft', message='Do you want to create a draft pull request?', default=False),
72+
inquirer.Confirm('confirmation', message='Do you want to link pull request to issues by title?', default=False),
73+
inquirer.Text('link', message='Write issue title (or part of it)', validate=_not_empty_validation, ignore=_ignore_if_not_confirmed),
74+
inquirer.Confirm('correct', message='Confirm creation of pull request(s) for the project(s) {}. Continue?'.format(self.selected_github_repos), default=False)
75+
]
76+
answers = inquirer.prompt(questions)
77+
if answers['correct']:
78+
body = answers['body']
79+
for github_repo in self.selected_github_repos:
80+
repo = self.github_connection.get_repo(github_repo)
81+
# link issues
82+
if answers['confirmation']:
83+
issues = repo.get_issues(state='open')
84+
closes = ""
85+
for issue in issues:
86+
if answers['link'] in issue.title:
87+
closes += "#{} ".format(issue.number)
88+
closes = closes.strip()
89+
if closes:
90+
body += "\n\nCloses {}".format(closes)
91+
try:
92+
repo.create_pull(title=answers['title'], body=body, head=answers['head'], base=answers['base'], draft=answers['draft'])
93+
print("{}: PR created successfully.".format(github_repo))
94+
except github.GithubException as github_exception:
95+
extra = ""
96+
for error in github_exception.data["errors"]:
97+
if "message" in error:
98+
extra += "{} ".format(error["message"])
99+
else:
100+
extra += "Invalid field {}. ".format(error["field"])
101+
print("{}: {}. {}".format(github_repo, github_exception.data["message"], extra))
102+
103+
def pull_request_merge(self) -> None:
104+
"""Merge pull request."""
105+
state = "open"
106+
questions = [
107+
inquirer.Text('base', message='Write base branch name (destination)', default="master", validate=_not_empty_validation),
108+
inquirer.Text('head', message='Write the head branch name (source)', validate=_not_empty_validation),
109+
inquirer.Text('prefix', message='Write base user or organization name from PR head', default=self.github_username, validate=_not_empty_validation),
110+
inquirer.Confirm('correct', message='Confirm merging of pull request(s) for the project(s) {}. Continue?'.format(self.selected_github_repos), default=False)
111+
]
112+
answers = inquirer.prompt(questions)
113+
# Important note: base and head arguments have different import formats.
114+
# https://developer.github.com/v3/pulls/#list-pull-requests
115+
# head needs format "user/org:branch"
116+
head = "{}:{}".format(answers["prefix"], answers["head"])
117+
118+
if answers['correct']:
119+
for github_repo in self.selected_github_repos:
120+
repo = self.github_connection.get_repo(github_repo)
121+
pulls = repo.get_pulls(state=state, base=answers['base'], head=head)
122+
if pulls.totalCount == 1:
123+
pull = pulls[0]
124+
if pull.mergeable:
125+
try:
126+
pull.merge()
127+
print("{}: PR merged successfully.".format(github_repo))
128+
except github.GithubException as github_exception:
129+
print("{}: {}.".format(github_repo, github_exception.data["message"]))
130+
else:
131+
print("{}: PR not mergeable, GitHub checks may be running.".format(github_repo))
132+
else:
133+
print("{}: no open PR found for {}:{}.".format(github_repo, answers['base'], answers['head']))
134+
135+
def branch_delete(self):
136+
questions = [
137+
inquirer.Text('branch', message='Write the branch name', validate=_not_empty_validation),
138+
inquirer.Confirm('correct', message='Confirm deleting of branch(es) for the project(s) {}. Continue?'.format(self.selected_github_repos), default=False)
139+
]
140+
answers = inquirer.prompt(questions)
141+
if answers['correct']:
142+
for github_repo in self.selected_github_repos:
143+
repo = self.github_connection.get_repo(github_repo)
144+
try:
145+
branch = repo.get_branch(branch=answers['branch'])
146+
147+
print("{}: branch deleted successfully.".format(github_repo))
148+
except github.GithubException as github_exception:
149+
print("{}: {}.".format(github_repo, github_exception.data["message"]))
150+
151+
def github_connect(self):
152+
print("We are going to connect to GitHub")
153+
questions = [
154+
inquirer.Text('github_username', message='GitHub username', default=self.github_username, validate=_not_empty_validation),
155+
inquirer.Password('github_access_token', message='GitHub access token', validate=_not_empty_validation),
156+
inquirer.Text('github_hostname', message='GitHub hostname (change ONLY if you use GitHub Enterprise)'),
157+
]
158+
answers = inquirer.prompt(questions)
159+
self.github_username = answers["github_username"]
160+
access_token = answers["github_access_token"].strip()
161+
# GitHub Enterprise
162+
if answers["github_hostname"]:
163+
base_url = "https://{}/api/v3".format(answers["github_hostname"])
164+
self.github_connection = github.Github(base_url=base_url, login_or_token=access_token)
165+
# GitHub.com
166+
else:
167+
self.github_connection = github.Github(access_token)
168+
user = self.github_connection.get_user()
169+
self.github_repos = user.get_repos()
170+
self.github_repos_select()
171+
172+
def github_repos_select(self):
173+
try:
174+
repo_names = [repo.full_name for repo in self.github_repos]
175+
except github.BadCredentialsException:
176+
print("Wrong GitHub credentials. Please try again.")
177+
return self.github_connect()
178+
except github.GithubException as github_exception:
179+
LOGGER.exception(github_exception)
180+
print("Wrong GitHub token/permissions. Please try again.")
181+
return self.github_connect()
182+
183+
while True:
184+
selected = inquirer.prompt([inquirer.Checkbox("github_repos",
185+
message="Which repos are you working on? (Select pressing space)",
186+
choices=repo_names,
187+
)])["github_repos"]
188+
if len(selected) == 0:
189+
print("Please select with `space` at least one repo.\n")
190+
else:
191+
break
192+
193+
self.selected_github_repos = selected
194+
self.issue_create()
195+
196+
197+
def main():
198+
manager = Manager()
199+
manager.github_connect()
200+
201+
202+
if __name__ == "__main__":
203+
main()

0 commit comments

Comments
 (0)