diff --git a/INSTALL.md b/INSTALL.md index 74368ac0..285b3f14 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,6 +15,7 @@ There are several ways to use PR-Agent: - [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app) - [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function) - [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup) +- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server) --- ### Method 1: Use Docker image (no installation required) @@ -343,6 +344,27 @@ PYTHONPATH="/PATH/TO/PROJECTS/pr-agent" python pr_agent/cli.py \ review ``` +--- + +### Method 8 - Run a GitLab webhook server + +1. From the GitLab workspace or group, create an access token. Enable the "api" scope only. +2. Generate a random secret for your app, and save it for later. For example, you can use: + +``` +WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))") +``` +3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [Method 5](#method-5-run-as-a-github-app). +4. In the secrets file, fill in the following: + - Your OpenAI key. + - In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token. + - Set deployment_type to 'gitlab' in [configuration.toml](./pr_agent/settings/configuration.toml) +5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2. +In the "Trigger" section, check the ‘comments’ and ‘merge request events’ boxes. +6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands. + +--- + ### Appendix - **Debugging LLM API Calls** If you're testing your codium/pr-agent server, and need to see if calls were made successfully + the exact call logs, you can use the [LiteLLM Debugger tool](https://docs.litellm.ai/docs/debugging/hosted_debugging). diff --git a/README.md b/README.md index 5bf7d52e..eff850e6 100644 --- a/README.md +++ b/README.md @@ -96,26 +96,27 @@ See the [usage guide](./Usage.md) for instructions how to run the different tool ## Overview `PR-Agent` offers extensive pull request functionalities across various git providers: -| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | -|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:| -| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: -| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | -| | Improve Code | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | -| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | -| | Reflect and Review | :white_check_mark: | | | | :white_check_mark: | -| | Update CHANGELOG.md | :white_check_mark: | | | | | +| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | Gerrit | +|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:|:----------:| +| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Improve Code | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | +| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | +| | Reflect and Review | :white_check_mark: | | | | :white_check_mark: | :white_check_mark: | +| | Update CHANGELOG.md | :white_check_mark: | | | | | | | | | | | | | | | USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | App / webhook | :white_check_mark: | :white_check_mark: | | | | | | Tagging bot | :white_check_mark: | | | | | | | Actions | :white_check_mark: | | | | | +| | Web server | | | | | | :white_check_mark: | | | | | | | | | -| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Adaptive and token-aware
file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Incremental PR Review | :white_check_mark: | | | | | +| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Adaptive and token-aware
file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Incremental PR Review | :white_check_mark: | | | | | | Review the **[usage guide](./Usage.md)** section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. @@ -153,6 +154,7 @@ There are several ways to use PR-Agent: - Allowing you to automate the review process on your private or public repositories - [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function) - [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup) +- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server) ## How it works diff --git a/docker/Dockerfile b/docker/Dockerfile index 4336cacc..951f846c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,6 +18,10 @@ FROM base as github_polling ADD pr_agent pr_agent CMD ["python", "pr_agent/servers/github_polling.py"] +FROM base as gitlab_webhook +ADD pr_agent pr_agent +CMD ["python", "pr_agent/servers/gitlab_webhook.py"] + FROM base as test ADD requirements-dev.txt . RUN pip install -r requirements-dev.txt && rm requirements-dev.txt diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index 376d09f5..968f0dfc 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -5,6 +5,8 @@ from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider from pr_agent.git_providers.azuredevops_provider import AzureDevopsProvider +from pr_agent.git_providers.gerrit_provider import GerritProvider + _GIT_PROVIDERS = { 'github': GithubProvider, @@ -12,7 +14,8 @@ _GIT_PROVIDERS = { 'bitbucket': BitbucketProvider, 'azure': AzureDevopsProvider, 'codecommit': CodeCommitProvider, - 'local' : LocalGitProvider + 'local' : LocalGitProvider, + 'gerrit': GerritProvider, } def get_git_provider(): diff --git a/pr_agent/git_providers/gerrit_provider.py b/pr_agent/git_providers/gerrit_provider.py new file mode 100644 index 00000000..03faf2a1 --- /dev/null +++ b/pr_agent/git_providers/gerrit_provider.py @@ -0,0 +1,393 @@ +import json +import logging +import os +import pathlib +import shutil +import subprocess +import uuid +from collections import Counter, namedtuple +from pathlib import Path +from tempfile import mkdtemp, NamedTemporaryFile + +import requests +import urllib3.util +from git import Repo + +from pr_agent.config_loader import get_settings +from pr_agent.git_providers.git_provider import GitProvider, FilePatchInfo, \ + EDIT_TYPE +from pr_agent.git_providers.local_git_provider import PullRequestMimic + +logger = logging.getLogger(__name__) + + +def _call(*command, **kwargs) -> (int, str, str): + res = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + **kwargs, + ) + return res.stdout.decode() + + +def clone(url, directory): + logger.info("Cloning %s to %s", url, directory) + stdout = _call('git', 'clone', "--depth", "1", url, directory) + logger.info(stdout) + + +def fetch(url, refspec, cwd): + logger.info("Fetching %s %s", url, refspec) + stdout = _call( + 'git', 'fetch', '--depth', '2', url, refspec, + cwd=cwd + ) + logger.info(stdout) + + +def checkout(cwd): + logger.info("Checking out") + stdout = _call('git', 'checkout', "FETCH_HEAD", cwd=cwd) + logger.info(stdout) + + +def show(*args, cwd=None): + logger.info("Show") + return _call('git', 'show', *args, cwd=cwd) + + +def diff(*args, cwd=None): + logger.info("Diff") + patch = _call('git', 'diff', *args, cwd=cwd) + if not patch: + logger.warning("No changes found") + return + return patch + + +def reset_local_changes(cwd): + logger.info("Reset local changes") + _call('git', 'checkout', "--force", cwd=cwd) + + +def add_comment(url: urllib3.util.Url, refspec, message): + *_, patchset, changenum = refspec.rsplit("/") + message = "'" + message.replace("'", "'\"'\"'") + "'" + return _call( + "ssh", + "-p", str(url.port), + f"{url.auth}@{url.host}", + "gerrit", "review", + "--message", message, + # "--code-review", score, + f"{patchset},{changenum}", + ) + + +def list_comments(url: urllib3.util.Url, refspec): + *_, patchset, _ = refspec.rsplit("/") + stdout = _call( + "ssh", + "-p", str(url.port), + f"{url.auth}@{url.host}", + "gerrit", "query", + "--comments", + "--current-patch-set", patchset, + "--format", "JSON", + ) + change_set, *_ = stdout.splitlines() + return json.loads(change_set)["currentPatchSet"]["comments"] + + +def prepare_repo(url: urllib3.util.Url, project, refspec): + repo_url = (f"{url.scheme}://{url.auth}@{url.host}:{url.port}/{project}") + + directory = pathlib.Path(mkdtemp()) + clone(repo_url, directory), + fetch(repo_url, refspec, cwd=directory) + checkout(cwd=directory) + return directory + + +def adopt_to_gerrit_message(message): + lines = message.splitlines() + buf = [] + for line in lines: + line = line.replace("*", "").replace("``", "`") + line = line.strip() + if line.startswith('#'): + buf.append("\n" + + line.replace('#', '').removesuffix(":").strip() + + ":") + continue + elif line.startswith('-'): + buf.append(line.removeprefix('-').strip()) + continue + else: + buf.append(line) + return "\n".join(buf).strip() + + +def add_suggestion(src_filename, context: str, start, end: int): + with ( + NamedTemporaryFile("w", delete=False) as tmp, + open(src_filename, "r") as src + ): + lines = src.readlines() + tmp.writelines(lines[:start - 1]) + if context: + tmp.write(context) + tmp.writelines(lines[end:]) + + shutil.copy(tmp.name, src_filename) + os.remove(tmp.name) + + +def upload_patch(patch, path): + patch_server_endpoint = get_settings().get( + 'gerrit.patch_server_endpoint') + patch_server_token = get_settings().get( + 'gerrit.patch_server_token') + + response = requests.post( + patch_server_endpoint, + json={ + "content": patch, + "path": path, + }, + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {patch_server_token}", + } + ) + response.raise_for_status() + patch_server_endpoint = patch_server_endpoint.rstrip("/") + return patch_server_endpoint + "/" + path + + +class GerritProvider(GitProvider): + + def __init__(self, key: str, incremental=False): + self.project, self.refspec = key.split(':') + assert self.project, "Project name is required" + assert self.refspec, "Refspec is required" + base_url = get_settings().get('gerrit.url') + assert base_url, "Gerrit URL is required" + user = get_settings().get('gerrit.user') + assert user, "Gerrit user is required" + + parsed = urllib3.util.parse_url(base_url) + self.parsed_url = urllib3.util.parse_url( + f"{parsed.scheme}://{user}@{parsed.host}:{parsed.port}" + ) + + self.repo_path = prepare_repo( + self.parsed_url, self.project, self.refspec + ) + self.repo = Repo(self.repo_path) + assert self.repo + + self.pr = PullRequestMimic(self.get_pr_title(), self.get_diff_files()) + + def get_pr_title(self): + """ + Substitutes the branch-name as the PR-mimic title. + """ + return self.repo.branches[0].name + + def get_issue_comments(self): + comments = list_comments(self.parsed_url, self.refspec) + Comments = namedtuple('Comments', ['reversed']) + Comment = namedtuple('Comment', ['body']) + return Comments([Comment(c['message']) for c in reversed(comments)]) + + def get_labels(self): + raise NotImplementedError( + 'Getting labels is not implemented for the gerrit provider') + + def add_eyes_reaction(self, issue_comment_id: int): + raise NotImplementedError( + 'Adding reactions is not implemented for the gerrit provider') + + def remove_reaction(self, issue_comment_id: int, reaction_id: int): + raise NotImplementedError( + 'Removing reactions is not implemented for the gerrit provider') + + def get_commit_messages(self): + return [self.repo.head.commit.message] + + def get_repo_settings(self): + """ + TODO: Implement support of .pr_agent.toml + """ + return "" + + def get_diff_files(self) -> list[FilePatchInfo]: + diffs = self.repo.head.commit.diff( + self.repo.head.commit.parents[0], # previous commit + create_patch=True, + R=True + ) + + diff_files = [] + for diff_item in diffs: + if diff_item.a_blob is not None: + original_file_content_str = ( + diff_item.a_blob.data_stream.read().decode('utf-8') + ) + else: + original_file_content_str = "" # empty file + if diff_item.b_blob is not None: + new_file_content_str = diff_item.b_blob.data_stream.read(). \ + decode('utf-8') + else: + new_file_content_str = "" # empty file + edit_type = EDIT_TYPE.MODIFIED + if diff_item.new_file: + edit_type = EDIT_TYPE.ADDED + elif diff_item.deleted_file: + edit_type = EDIT_TYPE.DELETED + elif diff_item.renamed_file: + edit_type = EDIT_TYPE.RENAMED + diff_files.append( + FilePatchInfo( + original_file_content_str, + new_file_content_str, + diff_item.diff.decode('utf-8'), + diff_item.b_path, + edit_type=edit_type, + old_filename=None + if diff_item.a_path == diff_item.b_path + else diff_item.a_path + ) + ) + self.diff_files = diff_files + return diff_files + + def get_files(self): + diff_index = self.repo.head.commit.diff( + self.repo.head.commit.parents[0], # previous commit + R=True + ) + # Get the list of changed files + diff_files = [item.a_path for item in diff_index] + return diff_files + + def get_languages(self): + """ + Calculate percentage of languages in repository. Used for hunk + prioritisation. + """ + # Get all files in repository + filepaths = [Path(item.path) for item in + self.repo.tree().traverse() if item.type == 'blob'] + # Identify language by file extension and count + lang_count = Counter( + ext.lstrip('.') for filepath in filepaths for ext in + [filepath.suffix.lower()]) + # Convert counts to percentages + total_files = len(filepaths) + lang_percentage = {lang: count / total_files * 100 for lang, count + in lang_count.items()} + return lang_percentage + + def get_pr_description_full(self): + return self.repo.head.commit.message + + def get_user_id(self): + return self.repo.head.commit.author.email + + def is_supported(self, capability: str) -> bool: + if capability in [ + # 'get_issue_comments', + 'create_inline_comment', + 'publish_inline_comments', + 'get_labels' + ]: + return False + return True + + def split_suggestion(self, msg) -> tuple[str, str]: + is_code_context = False + description = [] + context = [] + for line in msg.splitlines(): + if line.startswith('```suggestion'): + is_code_context = True + continue + if line.startswith('```'): + is_code_context = False + continue + if is_code_context: + context.append(line) + else: + description.append( + line.replace('*', '') + ) + + return ( + '\n'.join(description), + '\n'.join(context) + '\n' if context else '' + ) + + def publish_code_suggestions(self, code_suggestions: list): + msg = [] + for suggestion in code_suggestions: + description, code = self.split_suggestion(suggestion['body']) + add_suggestion( + pathlib.Path(self.repo_path) / suggestion["relevant_file"], + code, + suggestion["relevant_lines_start"], + suggestion["relevant_lines_end"], + ) + patch = diff(cwd=self.repo_path) + patch_id = uuid.uuid4().hex[0:4] + path = "/".join(["codium-ai", self.refspec, patch_id]) + full_path = upload_patch(patch, path) + reset_local_changes(self.repo_path) + msg.append(f'* {description}\n{full_path}') + + if msg: + add_comment(self.parsed_url, self.refspec, "\n".join(msg)) + return True + + def publish_comment(self, pr_comment: str, is_temporary: bool = False): + if not is_temporary: + msg = adopt_to_gerrit_message(pr_comment) + add_comment(self.parsed_url, self.refspec, msg) + + def publish_description(self, pr_title: str, pr_body: str): + msg = adopt_to_gerrit_message(pr_body) + add_comment(self.parsed_url, self.refspec, pr_title + '\n' + msg) + + def publish_inline_comments(self, comments: list[dict]): + raise NotImplementedError( + 'Publishing inline comments is not implemented for the gerrit ' + 'provider') + + def publish_inline_comment(self, body: str, relevant_file: str, + relevant_line_in_file: str): + raise NotImplementedError( + 'Publishing inline comments is not implemented for the gerrit ' + 'provider') + + def create_inline_comment(self, body: str, relevant_file: str, + relevant_line_in_file: str): + raise NotImplementedError( + 'Creating inline comments is not implemented for the gerrit ' + 'provider') + + def publish_labels(self, labels): + # Not applicable to the local git provider, + # but required by the interface + pass + + def remove_initial_comment(self): + # remove repo, cloned in previous steps + # shutil.rmtree(self.repo_path) + pass + + def get_pr_branch(self): + return self.repo.head diff --git a/pr_agent/servers/gerrit_server.py b/pr_agent/servers/gerrit_server.py new file mode 100644 index 00000000..04232ea9 --- /dev/null +++ b/pr_agent/servers/gerrit_server.py @@ -0,0 +1,78 @@ +import copy +import logging +import sys +from enum import Enum +from json import JSONDecodeError + +import uvicorn +from fastapi import APIRouter, FastAPI, HTTPException +from pydantic import BaseModel +from starlette.middleware import Middleware +from starlette_context import context +from starlette_context.middleware import RawContextMiddleware + +from pr_agent.agent.pr_agent import PRAgent +from pr_agent.config_loader import global_settings, get_settings + +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +router = APIRouter() + + +class Action(str, Enum): + review = "review" + describe = "describe" + ask = "ask" + improve = "improve" + reflect = "reflect" + answer = "answer" + + +class Item(BaseModel): + refspec: str + project: str + msg: str + + +@router.post("/api/v1/gerrit/{action}") +async def handle_gerrit_request(action: Action, item: Item): + logging.debug("Received a Gerrit request") + context["settings"] = copy.deepcopy(global_settings) + + if action == Action.ask: + if not item.msg: + return HTTPException( + status_code=400, + detail="msg is required for ask command" + ) + await PRAgent().handle_request( + f"{item.project}:{item.refspec}", + f"/{item.msg.strip()}" + ) + + +async def get_body(request): + try: + body = await request.json() + except JSONDecodeError as e: + logging.error("Error parsing request body", e) + return {} + return body + + +@router.get("/") +async def root(): + return {"status": "ok"} + + +def start(): + # to prevent adding help messages with the output + get_settings().set("CONFIG.CLI_MODE", True) + middleware = [Middleware(RawContextMiddleware)] + app = FastAPI(middleware=middleware) + app.include_router(router) + + uvicorn.run(app, host="0.0.0.0", port=3000) + + +if __name__ == '__main__': + start() diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 10584e54..c9f25124 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -98,6 +98,7 @@ async def handle_request(body: Dict[str, Any], event: str): api_url = body["comment"]["pull_request_url"] else: return {} + logging.info(body) logging.info(f"Handling comment because of event={event} and action={action}") comment_id = body.get("comment", {}).get("id") provider = get_git_provider()(pr_url=api_url) @@ -129,6 +130,7 @@ async def handle_request(body: Dict[str, Any], event: str): args = split_command[1:] other_args = update_settings_from_args(args) new_command = ' '.join([command] + other_args) + logging.info(body) logging.info(f"Performing command: {new_command}") await agent.handle_request(api_url, new_command) diff --git a/pr_agent/servers/gitlab_webhook.py b/pr_agent/servers/gitlab_webhook.py index c9b623f7..8321cd60 100644 --- a/pr_agent/servers/gitlab_webhook.py +++ b/pr_agent/servers/gitlab_webhook.py @@ -1,21 +1,51 @@ +import copy +import json import logging +import sys import uvicorn from fastapi import APIRouter, FastAPI, Request, status from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse from starlette.background import BackgroundTasks +from starlette.middleware import Middleware +from starlette_context import context +from starlette_context.middleware import RawContextMiddleware from pr_agent.agent.pr_agent import PRAgent -from pr_agent.config_loader import get_settings +from pr_agent.config_loader import get_settings, global_settings +from pr_agent.secret_providers import get_secret_provider -app = FastAPI() +logging.basicConfig(stream=sys.stdout, level=logging.INFO) router = APIRouter() +secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None + @router.post("/webhook") async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request): + if request.headers.get("X-Gitlab-Token") and secret_provider: + request_token = request.headers.get("X-Gitlab-Token") + secret = secret_provider.get_secret(request_token) + try: + secret_dict = json.loads(secret) + gitlab_token = secret_dict["gitlab_token"] + context["settings"] = copy.deepcopy(global_settings) + context["settings"].gitlab.personal_access_token = gitlab_token + except Exception as e: + logging.error(f"Failed to validate secret {request_token}: {e}") + return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) + elif get_settings().get("GITLAB.SHARED_SECRET"): + secret = get_settings().get("GITLAB.SHARED_SECRET") + if not request.headers.get("X-Gitlab-Token") == secret: + return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) + else: + return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) + gitlab_token = get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None) + if not gitlab_token: + return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) data = await request.json() + logging.info(json.dumps(data)) if data.get('object_kind') == 'merge_request' and data['object_attributes'].get('action') in ['open', 'reopen']: logging.info(f"A merge request has been opened: {data['object_attributes'].get('title')}") url = data['object_attributes'].get('url') @@ -28,16 +58,18 @@ async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request): background_tasks.add_task(PRAgent().handle_request, url, body) return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"})) + +@router.get("/") +async def root(): + return {"status": "ok"} + def start(): gitlab_url = get_settings().get("GITLAB.URL", None) if not gitlab_url: raise ValueError("GITLAB.URL is not set") - gitlab_token = get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None) - if not gitlab_token: - raise ValueError("GITLAB.PERSONAL_ACCESS_TOKEN is not set") get_settings().config.git_provider = "gitlab" - - app = FastAPI() + middleware = [Middleware(RawContextMiddleware)] + app = FastAPI(middleware=middleware) app.include_router(router) uvicorn.run(app, host="0.0.0.0", port=3000) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 9bfdf3a3..be44ab03 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -86,6 +86,17 @@ polling_interval_seconds = 30 # description_path= "path/to/description.md" # review_path= "path/to/review.md" +[gerrit] +# endpoint to the gerrit service +# url = "ssh://gerrit.example.com:29418" +# user for gerrit authentication +# user = "ai-reviewer" +# patch server where patches will be saved +# patch_server_endpoint = "http://127.0.0.1:5000/patch" +# token to authenticate in the patch server +# patch_server_token = "" + + [pr_similar_issue] skip_comments = false force_update_dataset = false