diff --git a/gerrit_to_github_issues/cli.py b/gerrit_to_github_issues/cli.py index b84a224..ce9d53d 100644 --- a/gerrit_to_github_issues/cli.py +++ b/gerrit_to_github_issues/cli.py @@ -52,6 +52,8 @@ def main(): help='Specifies how far in the past to search for changes in Gerrit. ' 'See https://gerrit-review.googlesource.com/Documentation/user-search.html#age for more ' 'details.') + parser.add_argument('--skip-approvals', action='store_true', required=False, default=False, + help='Skips evaluation of change approvals to be written to the bot comments.') parser.add_argument('-u', '--github-user', action='store', required=False, type=str, default=os.getenv('GITHUB_USER', default=None), help='Username to use for GitHub Issues integration. Defaults to GITHUB_USER in ' diff --git a/gerrit_to_github_issues/engine.py b/gerrit_to_github_issues/engine.py index 67d9744..6ad6012 100644 --- a/gerrit_to_github_issues/engine.py +++ b/gerrit_to_github_issues/engine.py @@ -9,9 +9,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import datetime import logging import github +import pytz as pytz from github.Repository import Repository from gerrit_to_github_issues import gerrit @@ -21,15 +23,15 @@ LOG = logging.getLogger(__name__) def update(gerrit_url: str, gerrit_project_name: str, github_project_name: str, github_user: str, github_password: str, - github_token: str, change_age: str = None): - repo = github_issues.get_repo(github_project_name, github_user, github_password, github_token) + github_token: str, change_age: str = None, skip_approvals: bool = False): + gh, repo = github_issues.get_repo(github_project_name, github_user, github_password, github_token) change_list = gerrit.get_changes(gerrit_url, gerrit_project_name, change_age=change_age) for change in change_list['data']: if 'commitMessage' in change: - process_change(change, repo, gerrit_url) + process_change(gh, change, repo, skip_approvals) -def process_change(change: dict, repo: Repository, gerrit_url: str): +def process_change(gh: github.Github, change: dict, repo: Repository, skip_approvals: bool = False): issue_numbers_dict = github_issues.parse_issue_number(change['commitMessage']) issue_numbers_dict = github_issues.remove_duplicated_issue_numbers(issue_numbers_dict) if not issue_numbers_dict: @@ -42,10 +44,8 @@ def process_change(change: dict, repo: Repository, gerrit_url: str): except github.GithubException: LOG.warning(f'Issue #{issue_number} not found for project') return - comment_msg = '' - change_url = gerrit.make_gerrit_url(gerrit_url, change['number']) - link_exists = github_issues.check_issue_for_matching_comments(issue, change_url) - if issue.state == 'closed' and not link_exists: + bot_comment = github_issues.get_bot_comment(issue, gh.get_user().login, change['number']) + if issue.state == 'closed' and not bot_comment: LOG.debug(f'Issue #{issue_number} was closed, reopening...') issue.edit(state='open') issue.create_comment('Issue reopened due to new activity on Gerrit.\n\n') @@ -70,14 +70,54 @@ def process_change(change: dict, repo: Repository, gerrit_url: str): issue.remove_from_labels('wip') except github.GithubException: LOG.debug(f'`wip` tag does not exist on issue #{issue_number}') - if not link_exists: - comment_msg += '### New Related Change\n\n' \ - f'**Link:** {change_url}\n' \ - f'**Subject:** {change["subject"]}\n' \ - f'**Authored By:** {change["owner"]["name"]} ({change["owner"]["email"]})' + comment_msg = get_issue_comment(change, key, skip_approvals) + if not bot_comment: if key == 'closes': comment_msg += '\n\nThis change will close this issue when merged.' - if comment_msg: LOG.debug(f'Comment to post on #{issue_number}: {comment_msg}') issue.create_comment(comment_msg) LOG.info(f'Comment posted to issue #{issue_number}') + else: + LOG.debug(f'Comment to edit on #{issue_number}: {comment_msg}') + comment = github_issues.get_bot_comment(issue, gh.get_user().login, change['number']) + comment.edit(comment_msg) + LOG.info(f'Comment edited to issue #{issue_number}') + + +def get_issue_comment(change: dict, key: str, skip_approvals: bool = False) -> str: + comment_str = f'## Related Change [#{change["number"]}]({change["url"]})\n\n' \ + f'**Subject:** {change["subject"]}\n' \ + f'**Link:** {change["url"]}\n' \ + f'**Status:** {change["status"]}\n' \ + f'**Owner:** {change["owner"]["name"]} ({change["owner"]["email"]})\n\n' + if key == 'closes': + comment_str += 'This change will close this issue when merged.\n\n' + if not skip_approvals: + comment_str += '### Approvals\n' \ + '```diff\n' + + approval_dict = { + 'Code-Review': [], + 'Verified': [], + 'Workflow': [] + } + if 'approvals' in change['currentPatchSet']: + for approval in change['currentPatchSet']['approvals']: + if approval['type'] in approval_dict: + approval_dict[approval['type']].append((approval['by']['name'], approval['value'])) + else: + LOG.warning(f'Approval type "{approval["type"]}" is not a known approval type') + + for key in ['Code-Review', 'Verified', 'Workflow']: + comment_str += f'{key}\n' + if approval_dict[key]: + for approval in approval_dict[key]: + if int(approval[1]) > 0: + comment_str += '+' + comment_str += f'{approval[1]} {approval[0]}\n' + else: + comment_str += '! None\n' + comment_str += '```' + dt = datetime.datetime.now(pytz.timezone('America/Chicago')).strftime('%Y-%m-%d %H:%M:%S %Z').strip() + comment_str += f'\n\n*Last Updated: {dt}*' + return comment_str diff --git a/gerrit_to_github_issues/gerrit.py b/gerrit_to_github_issues/gerrit.py index c8fdded..3f058d8 100644 --- a/gerrit_to_github_issues/gerrit.py +++ b/gerrit_to_github_issues/gerrit.py @@ -15,7 +15,7 @@ from fabric import Connection def get_changes(gerrit_url: str, project_name: str, port: int = 29418, change_age: str = None) -> dict: - cmd = f'gerrit query --format=JSON status:open project:{project_name}' + cmd = f'gerrit query --format=JSON --current-patch-set project:{project_name}' if change_age: cmd += f' -- -age:{change_age}' result = Connection(gerrit_url, port=port).run(cmd) diff --git a/gerrit_to_github_issues/github_issues.py b/gerrit_to_github_issues/github_issues.py index 9dbde96..3a9b1f7 100644 --- a/gerrit_to_github_issues/github_issues.py +++ b/gerrit_to_github_issues/github_issues.py @@ -14,6 +14,7 @@ import re import github from github.Issue import Issue +from github.IssueComment import IssueComment from github.Repository import Repository from gerrit_to_github_issues import errors @@ -60,18 +61,17 @@ def remove_duplicated_issue_numbers(issue_dict: dict) -> dict: return issue_dict -def get_repo(repo_name: str, github_user: str, github_pw: str, github_token: str) -> Repository: +def get_repo(repo_name: str, github_user: str, github_pw: str, github_token: str) -> (github.Github, Repository): if github_token: gh = github.Github(github_token) elif github_user and github_pw: gh = github.Github(github_user, github_pw) else: raise errors.GithubConfigurationError - return gh.get_repo(repo_name) + return gh, gh.get_repo(repo_name) -def check_issue_for_matching_comments(issue: Issue, contains: str) -> bool: - for comment in issue.get_comments(): - if contains in comment.body: - return True - return False +def get_bot_comment(issue: Issue, bot_name: str, ps_number: str) -> IssueComment: + for i in issue.get_comments(): + if i.user.login == bot_name and ps_number in i.body: + return i