diff --git a/advisors/gitee.py b/advisors/gitee.py index 8ffa0149d1b1300e16c69d0b5354f27e2acd75ee..b73f84ace34a89abec49fd9b5bab89c8b4adf7fa 100755 --- a/advisors/gitee.py +++ b/advisors/gitee.py @@ -243,6 +243,21 @@ class Gitee(): url = url_template.format(owner=owner, repo=repo, number=num) return self.__get_gitee_json(url) + def get_diff(self, repo, num, owner="src-openeuler"): + """ + Get changes of PR as diff file + """ + url_template = "https://gitee.com/{owner}/{repo}/pulls/{number}.diff" + url = url_template.format(owner=owner, repo=repo, number=num) + req = urllib.request.Request(url=url, headers=self.headers) + try: + result = urllib.request.urlopen(req) + return result.read().decode("utf-8") + except urllib.error.HTTPError as error: + print("get diff failed to access: %s" % (url)) + print("get diff failed: %d, %s" % (error.code, error.reason)) + return None + def __get_gitee_json(self, url): """ Get and load gitee json response diff --git a/advisors/gitee/prepare_token.sh b/advisors/gitee/prepare_token.sh index 39c5bc33cbb4e88468b0a11b5f6016d0652854ee..5eb285a306b1daf5ca980dfe79db45e201b98404 100755 --- a/advisors/gitee/prepare_token.sh +++ b/advisors/gitee/prepare_token.sh @@ -2,6 +2,11 @@ # refer to gitee.com/api/v5/oauth_doc#/list-item-2 source ~/.gitee_secret + echo "Refreshing ~/.gitee_token.json" -curl -s -X POST --data-urlencode "grant_type=password" --data-urlencode "username=$username" --data-urlencode "password=$password" --data-urlencode "client_id=$client_id" --data-urlencode "client_secret=$client_secret" --data-urlencode "scope=projects issues" https://gitee.com/oauth/token > ~/.gitee_token.json +rm -f ~/.gitee_token.json + +# curl -s -X POST --data-urlencode "grant_type=password" --data-urlencode "username=$username" --data-urlencode "password=$password" --data-urlencode "client_id=$client_id" --data-urlencode "client_secret=$client_secret" --data-urlencode "scope=projects issues" https://gitee.com/oauth/token > ~/.gitee_token.json +curl -X POST --data-urlencode "grant_type=password" --data-urlencode "username=${email}" --data-urlencode "password=${password}" --data-urlencode "client_id=${client_id}" --data-urlencode "client_secret=${client_secret}" --data-urlencode "scope=projects user_info issues notes" https://gitee.com/oauth/token > ~/.gitee_personal_token.json + chmod 400 ~/.gitee_token.json diff --git a/advisors/oe_review.py b/advisors/oe_review.py new file mode 100755 index 0000000000000000000000000000000000000000..45b2ff5023585c6caff88d6e2cb97ece5f2c17fa --- /dev/null +++ b/advisors/oe_review.py @@ -0,0 +1,161 @@ +#!/usr/bin/python3 +# ****************************************************************************** +# Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. +# licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# ******************************************************************************/ +""" +Review tool for openEuler submission +""" +import os +import re +import sys +import argparse +import subprocess +import shutil +import tempfile +import urllib +import urllib.request +import urllib.parse +import yaml +import json +import requests + +from openai import OpenAI +from advisors import gitee + +OE_REVIEW_PR_PROMPT=""" +You are a code reviewer of a openEuler Pull Request, providing feedback on the code changes below. + As a code reviewer, your task is: + - Review the code changes (diffs) in the patch and provide feedback. + - If there are any bugs, highlight them. + - Does the code do what it says in the commit messages? + - Do not highlight minor issues and nitpicks. + - Use bullet points if you have multiple comments. + - If no suggestions are provided, please give good feedback. + - please use chinese to give feedback. + + You are provided with the merge request changes in a diff format. + The diff to be reviewed, which is get from the Gitee source code repo, as following: +""" + +def generate_review_from_ollama(pr_content, prompt, model="llama3.1:70b"): + base_url = "http://localhost:11434/api" + json_resp = [] + resp = None + headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW 64; rv:50.0) '\ + 'Gecko/20100101 Firefox/50.0'} + headers["Content-Type"] = "application/json;charset=UTF-8" + + url = f"{base_url}/generate" + + values = {} + values["model"] = model + values['prompt'] = pr_content + values['system'] = prompt + values['stream'] = False + response = requests.post(url, headers=headers, json=values) + return response.json().get('response', '') + +def check_pr_url(url): + """ + check whether the URL of Pull Request is valid + """ + if url: + pattern = re.compile(r'https://(e.)?gitee.com/(open_euler/repos/)?' + + r'(openeuler|src-openeuler)/([A-Za-z0-9-_]*)/pulls/(\d+$)') + return pattern.match(url) + return None + +def extract_params(args): + """ + check and extract parameters we need + """ + if args.url and len(args.url) > 0: + res = check_pr_url(args.url) + if res: + group = res.group(3) + repo_name = res.group(4) + pull_id = res.group(5) + return (group, repo_name, pull_id) + print("ERROR: URL is wrong, please check!") + return () + if args.repo and args.pull and len(args.repo) > 0 and len(args.pull) > 0: + group = args.repo.split('/')[0] + repo_name = args.repo.split('/')[1] + pull_id = args.pull + return group, repo_name, pull_id + print("WARNING: please specify the URL of PR or repository name and PR's ID.\ + \nDetails use -h/--help option.") + return () + +def args_parser(): + """ + arguments parser + """ + pars = argparse.ArgumentParser() + pars.add_argument("-q", "--quiet", action='store_true', default=False, help="No log print") + pars.add_argument("-n", "--repo", type=str, help="Repository name that include group") + pars.add_argument("-p", "--pull", type=str, help="Number ID of Pull Request") + pars.add_argument("-u", "--url", type=str, help="URL of Pull Request") + pars.add_argument("-m", "--model", type=str, help="Model of selection to generate review") + pars.add_argument("-w", "--workdir", type=str, default=os.getcwd(), + help="Work directory.Default is current directory.") + pars.add_argument("-e", "--editor", type=str, default="/opt/homebrew/bin/nvim", + help="Editor of choice to edit content, default to nvim") + pars.add_argument("-c", "--clean", help="Clean environment", action="store_true") + pars.add_argument("-l", "--local", help="Using local checklist", action="store_true") + + return pars.parse_args() + +def edit_content(text, editor): + fd, path = tempfile.mkstemp(suffix=".tmp", prefix="oe_review") + with os.fdopen(fd, 'w') as tmp: + tmp.write(text) + tmp.flush() + subprocess.call([editor, path]) + text_new = open(path).read() + return text_new + +def main(): + """ + Main entrance of the functionality + """ + args = args_parser() + if args.quiet: + sys.stdout = open('/dev/null', 'w') + sys.stderr = sys.stdout + work_dir = os.path.realpath(args.workdir) + params = extract_params(args) + if not params: + return 1 + group = params[0] + repo_name = params[1] + pull_id = params[2] + try: + user_gitee = gitee.Gitee() + except NameError: + sys.exit(1) + pr_diff = user_gitee.get_diff(repo_name, pull_id, group) + if not pr_diff: + print("Failed to get PR:%s of repository:%s/%s, make sure the PR is exist." % (pull_id, group, repo_name)) + return 1 + + review = generate_review_from_ollama(pr_diff, OE_REVIEW_PR_PROMPT) + review_comment = edit_content(review + '\n\n' + pr_diff, args.editor) + + user_gitee.create_pr_comment(repo_name, pull_id, review_comment, group) + + print("push review list finish.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/command/oe_review b/command/oe_review new file mode 100755 index 0000000000000000000000000000000000000000..47a6a219394d42f49c152c7349f170ee6006420e --- /dev/null +++ b/command/oe_review @@ -0,0 +1,26 @@ +#!/usr/bin/env python3.10 + +import sys +import signal +from signal import SIG_DFL + +try: + def sig_handler(signum, frame): + print('Exit command mode') + sys.exit(0) + + signal.signal(signal.SIGINT, sig_handler) + signal.signal(signal.SIGPIPE, SIG_DFL) +except: + pass + +from advisors.oe_review import main + + +if __name__ == '__main__': + try: + sys.exit(main()) + except Exception as error: + print("WARNING: Command execution error") + print(error.message) + sys.exit(1) diff --git a/develop_env.zsh b/develop_env.zsh new file mode 100755 index 0000000000000000000000000000000000000000..fd2d62ec181c93c3476c2f6678cdd8d8204d9b5e --- /dev/null +++ b/develop_env.zsh @@ -0,0 +1,34 @@ +#!/bin/zsh +#Config environment before develop, please run: source ./develop_env.zsh + +advisor_path=$(cd $(dirname $funcstack[1]); pwd) +python_paths=$(echo ${PYTHONPATH} | sed 's/:/ /g') +existed=0 + +for p in $python_paths +do + if [ $advisor_path = $p ]; then + existed=1 + fi +done + +if [ $existed -eq 0 ]; then + export PYTHONPATH=${PYTHONPATH}:${advisor_path} +fi +echo "PYTHONPATH=${PYTHONPATH}" + + +existed=0 +for p in $fpath +do + if [ "${advisor_path}/advisors" = $p ]; then + existed=1 + fi +done + +if [ $existed -eq 0 ]; then + fpath+=("${advisor_path}/advisors" "${advisor_path}/command") + export PATH=${PATH}:${advisor_path}/advisors:${advisor_path}/command + +fi +echo "PATH=${PATH}"