diff --git a/docs/extras/integrations/toolkits/gitlab.ipynb b/docs/extras/integrations/toolkits/gitlab.ipynb
new file mode 100644
index 00000000000..a8f28f09fa3
--- /dev/null
+++ b/docs/extras/integrations/toolkits/gitlab.ipynb
@@ -0,0 +1,244 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Gitlab\n",
+ "\n",
+ "The `Gitlab` toolkit contains tools that enable an LLM agent to interact with a gitlab repository. \n",
+ "The tool is a wrapper for the [python-gitlab](https://github.com/python-gitlab/python-gitlab) library. \n",
+ "\n",
+ "## Quickstart\n",
+ "1. Install the python-gitlab library\n",
+ "2. Create a Gitlab personal access token\n",
+ "3. Set your environmental variables\n",
+ "4. Pass the tools to your agent with `toolkit.get_tools()`\n",
+ "\n",
+ "Each of these steps will be explained in greate detail below.\n",
+ "\n",
+ "1. **Get Issues**- fetches issues from the repository.\n",
+ "\n",
+ "2. **Get Issue**- feteches details about a specific issue.\n",
+ "\n",
+ "3. **Comment on Issue**- posts a comment on a specific issue.\n",
+ "\n",
+ "4. **Create Pull Request**- creates a pull request from the bot's working branch to the base branch.\n",
+ "\n",
+ "5. **Create File**- creates a new file in the repository.\n",
+ "\n",
+ "6. **Read File**- reads a file from the repository.\n",
+ "\n",
+ "7. **Update File**- updates a file in the repository.\n",
+ "\n",
+ "8. **Delete File**- deletes a file from the repository.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1. Install the `python-gitlab` library "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "vscode": {
+ "languageId": "shellscript"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "%pip install python-gitlab"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 2. Create a Gitlab personal access token\n",
+ "\n",
+ "[Follow the instructions here](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) to create a Gitlab personal access token. Make sure your app has the following repository permissions:\n",
+ "* read_api\n",
+ "* read_repository\n",
+ "* write_repository\n",
+ "\n",
+ "### 3. Set Environmental Variables\n",
+ "\n",
+ "Before initializing your agent, the following environmental variables need to be set:\n",
+ "\n",
+ "* **GITLAB_PERSONAL_ACCESS_TOKEN**- The personal access token you created in the last step\n",
+ "* **GITLAB_REPOSITORY**- The name of the Gitlab repository you want your bot to act upon. Must follow the format {username}/{repo-name}.\n",
+ "* **GITLAB_BRANCH**- The branch where the bot will make its commits. Defaults to 'main.'\n",
+ "* **GITLAB_BASE_BRANCH**- The base branch of your repo, usually either 'main' or 'master.' This is where pull requests will base from. Defaults to 'main.'\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Example: Simple Agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "from langchain.agents import AgentType\n",
+ "from langchain.agents import initialize_agent\n",
+ "from langchain.agents.agent_toolkits.gitlab.toolkit import GitLabToolkit\n",
+ "from langchain.llms import OpenAI\n",
+ "from langchain.utilities.gitlab import GitLabAPIWrapper"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set your environment variables using os.environ\n",
+ "os.environ[\"GITLAB_PERSONAL_ACCESS_TOKEN\"] = \"\"\n",
+ "os.environ[\"GITLAB_REPOSITORY\"] = \"username/repo-name\"\n",
+ "os.environ[\"GITLAB_BRANCH\"] = \"bot-branch-name\"\n",
+ "os.environ[\"GITLAB_BASE_BRANCH\"] = \"main\"\n",
+ "\n",
+ "# This example also requires an OpenAI API key\n",
+ "os.environ[\"OPENAI_API_KEY\"] = \"\"\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "llm = OpenAI(temperature=0)\n",
+ "gitlab = GitLabAPIWrapper()\n",
+ "toolkit = GitLabToolkit.from_gitlab_api_wrapper(gitlab)\n",
+ "agent = initialize_agent(\n",
+ " toolkit.get_tools(), llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "\n",
+ "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
+ "\u001b[32;1m\u001b[1;3m I need to look at the open issues and figure out how to solve them.\n",
+ "Action: Get Issues\n",
+ "Action Input: N/A\u001b[0m\n",
+ "Observation: \u001b[36;1m\u001b[1;3mFound 1 issues:\n",
+ "[{'title': 'Add tic-tac-toe game', 'number': 15}]\u001b[0m\n",
+ "Thought:\u001b[32;1m\u001b[1;3m I need to look at the details of this issue to figure out how to solve it.\n",
+ "Action: Get Issue\n",
+ "Action Input: 15\u001b[0m\n",
+ "Observation: \u001b[33;1m\u001b[1;3m{\"title\": \"Add tic-tac-toe game\", \"body\": \"Create a tic-tac-toe game using HTML, CSS, and JavaScript. Create a new file called game.html and store the code there.\", \"comments\": \"[]\"}\u001b[0m\n",
+ "Thought:\u001b[32;1m\u001b[1;3m I need to create the game.html file and add the code.\n",
+ "Action: Create File\n",
+ "Action Input: game.html\n",
+ "\n",
+ "test contents\u001b[0m\n",
+ "Observation: \u001b[33;1m\u001b[1;3mCreated file game.html\u001b[0m\n",
+ "Thought:\u001b[32;1m\u001b[1;3m I need to add the code to the game.html file.\n",
+ "Action: Update File\n",
+ "Action Input: game.html\n",
+ "\n",
+ "OLD <<<<\n",
+ "test contents\n",
+ ">>>> OLD\n",
+ "NEW <<<<\n",
+ "\n",
+ "
\n",
+ " Tic-Tac-Toe\n",
+ " \n",
+ " \n",
+ " Tic-Tac-Toe
\n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ "\n",
+ ">>>> NEW\u001b[0m\n",
+ "Observation: \u001b[36;1m\u001b[1;3mUpdated file game.html\u001b[0m\n",
+ "Thought:\u001b[32;1m\u001b[1;3m I need to create a pull request to submit my changes.\n",
+ "Action: Create Pull Request\n",
+ "Action Input: Add tic-tac-toe game\n",
+ "\n",
+ "added tic-tac-toe game, closes issue #15\u001b[0m\n",
+ "Observation: \u001b[36;1m\u001b[1;3mSuccessfully created PR number 12\u001b[0m\n",
+ "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n",
+ "Final Answer: I have created a pull request with number 12 that solves issue 15.\u001b[0m\n",
+ "\n",
+ "\u001b[1m> Finished chain.\u001b[0m\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'I have created a pull request with number 12 that solves issue 15.'"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "agent.run(\n",
+ " \"You have the software engineering capabilities of a Google Principle engineer. You are tasked with completing issues on a gitlab repository. Please look at the open issues and complete them by creating pull requests that solve the issues.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/libs/langchain/langchain/agents/agent_toolkits/gitlab/__init__.py b/libs/langchain/langchain/agents/agent_toolkits/gitlab/__init__.py
new file mode 100644
index 00000000000..7d3ca720636
--- /dev/null
+++ b/libs/langchain/langchain/agents/agent_toolkits/gitlab/__init__.py
@@ -0,0 +1 @@
+"""GitLab Toolkit."""
diff --git a/libs/langchain/langchain/agents/agent_toolkits/gitlab/toolkit.py b/libs/langchain/langchain/agents/agent_toolkits/gitlab/toolkit.py
new file mode 100644
index 00000000000..87e83756cc5
--- /dev/null
+++ b/libs/langchain/langchain/agents/agent_toolkits/gitlab/toolkit.py
@@ -0,0 +1,84 @@
+"""GitHub Toolkit."""
+from typing import Dict, List
+
+from langchain.agents.agent_toolkits.base import BaseToolkit
+from langchain.tools import BaseTool
+from langchain.tools.gitlab.prompt import (
+ COMMENT_ON_ISSUE_PROMPT,
+ CREATE_FILE_PROMPT,
+ CREATE_PULL_REQUEST_PROMPT,
+ DELETE_FILE_PROMPT,
+ GET_ISSUE_PROMPT,
+ GET_ISSUES_PROMPT,
+ READ_FILE_PROMPT,
+ UPDATE_FILE_PROMPT,
+)
+from langchain.tools.gitlab.tool import GitLabAction
+from langchain.utilities.gitlab import GitLabAPIWrapper
+
+
+class GitLabToolkit(BaseToolkit):
+ """GitLab Toolkit."""
+
+ tools: List[BaseTool] = []
+
+ @classmethod
+ def from_gitlab_api_wrapper(
+ cls, gitlab_api_wrapper: GitLabAPIWrapper
+ ) -> "GitLabToolkit":
+ operations: List[Dict] = [
+ {
+ "mode": "get_issues",
+ "name": "Get Issues",
+ "description": GET_ISSUES_PROMPT,
+ },
+ {
+ "mode": "get_issue",
+ "name": "Get Issue",
+ "description": GET_ISSUE_PROMPT,
+ },
+ {
+ "mode": "comment_on_issue",
+ "name": "Comment on Issue",
+ "description": COMMENT_ON_ISSUE_PROMPT,
+ },
+ {
+ "mode": "create_pull_request",
+ "name": "Create Pull Request",
+ "description": CREATE_PULL_REQUEST_PROMPT,
+ },
+ {
+ "mode": "create_file",
+ "name": "Create File",
+ "description": CREATE_FILE_PROMPT,
+ },
+ {
+ "mode": "read_file",
+ "name": "Read File",
+ "description": READ_FILE_PROMPT,
+ },
+ {
+ "mode": "update_file",
+ "name": "Update File",
+ "description": UPDATE_FILE_PROMPT,
+ },
+ {
+ "mode": "delete_file",
+ "name": "Delete File",
+ "description": DELETE_FILE_PROMPT,
+ },
+ ]
+ tools = [
+ GitLabAction(
+ name=action["name"],
+ description=action["description"],
+ mode=action["mode"],
+ api_wrapper=gitlab_api_wrapper,
+ )
+ for action in operations
+ ]
+ return cls(tools=tools)
+
+ def get_tools(self) -> List[BaseTool]:
+ """Get the tools in the toolkit."""
+ return self.tools
diff --git a/libs/langchain/langchain/tools/gitlab/__init__.py b/libs/langchain/langchain/tools/gitlab/__init__.py
new file mode 100644
index 00000000000..4b6d6367663
--- /dev/null
+++ b/libs/langchain/langchain/tools/gitlab/__init__.py
@@ -0,0 +1 @@
+""" GitLab Tool """
diff --git a/libs/langchain/langchain/tools/gitlab/prompt.py b/libs/langchain/langchain/tools/gitlab/prompt.py
new file mode 100644
index 00000000000..3f303155cd4
--- /dev/null
+++ b/libs/langchain/langchain/tools/gitlab/prompt.py
@@ -0,0 +1,70 @@
+# flake8: noqa
+GET_ISSUES_PROMPT = """
+This tool will fetch a list of the repository's issues. It will return the title, and issue number of 5 issues. It takes no input.
+"""
+
+GET_ISSUE_PROMPT = """
+This tool will fetch the title, body, and comment thread of a specific issue. **VERY IMPORTANT**: You must specify the issue number as an integer.
+"""
+
+COMMENT_ON_ISSUE_PROMPT = """
+This tool is useful when you need to comment on a GitLab issue. Simply pass in the issue number and the comment you would like to make. Please use this sparingly as we don't want to clutter the comment threads. **VERY IMPORTANT**: Your input to this tool MUST strictly follow these rules:
+
+- First you must specify the issue number as an integer
+- Then you must place two newlines
+- Then you must specify your comment
+"""
+CREATE_PULL_REQUEST_PROMPT = """
+This tool is useful when you need to create a new pull request in a GitLab repository. **VERY IMPORTANT**: Your input to this tool MUST strictly follow these rules:
+
+- First you must specify the title of the pull request
+- Then you must place two newlines
+- Then you must write the body or description of the pull request
+
+To reference an issue in the body, put its issue number directly after a #.
+For example, if you would like to create a pull request called "README updates" with contents "added contributors' names, closes issue #3", you would pass in the following string:
+
+README updates
+
+added contributors' names, closes issue #3
+"""
+CREATE_FILE_PROMPT = """
+This tool is a wrapper for the GitLab API, useful when you need to create a file in a GitLab repository. **VERY IMPORTANT**: Your input to this tool MUST strictly follow these rules:
+
+- First you must specify which file to create by passing a full file path (**IMPORTANT**: the path must not start with a slash)
+- Then you must specify the contents of the file
+
+For example, if you would like to create a file called /test/test.txt with contents "test contents", you would pass in the following string:
+
+test/test.txt
+
+test contents
+"""
+
+READ_FILE_PROMPT = """
+This tool is a wrapper for the GitLab API, useful when you need to read the contents of a file in a GitLab repository. Simply pass in the full file path of the file you would like to read. **IMPORTANT**: the path must not start with a slash
+"""
+
+UPDATE_FILE_PROMPT = """
+This tool is a wrapper for the GitLab API, useful when you need to update the contents of a file in a GitLab repository. **VERY IMPORTANT**: Your input to this tool MUST strictly follow these rules:
+
+- First you must specify which file to modify by passing a full file path (**IMPORTANT**: the path must not start with a slash)
+- Then you must specify the old contents which you would like to replace wrapped in OLD <<<< and >>>> OLD
+- Then you must specify the new contents which you would like to replace the old contents with wrapped in NEW <<<< and >>>> NEW
+
+For example, if you would like to replace the contents of the file /test/test.txt from "old contents" to "new contents", you would pass in the following string:
+
+test/test.txt
+
+This is text that will not be changed
+OLD <<<<
+old contents
+>>>> OLD
+NEW <<<<
+new contents
+>>>> NEW
+"""
+
+DELETE_FILE_PROMPT = """
+This tool is a wrapper for the GitLab API, useful when you need to delete a file in a GitLab repository. Simply pass in the full file path of the file you would like to delete. **IMPORTANT**: the path must not start with a slash
+"""
diff --git a/libs/langchain/langchain/tools/gitlab/tool.py b/libs/langchain/langchain/tools/gitlab/tool.py
new file mode 100644
index 00000000000..fc8105c50af
--- /dev/null
+++ b/libs/langchain/langchain/tools/gitlab/tool.py
@@ -0,0 +1,32 @@
+"""
+This tool allows agents to interact with the python-gitlab library
+and operate on a GitLab repository.
+
+To use this tool, you must first set as environment variables:
+ GITLAB_PRIVATE_ACCESS_TOKEN
+ GITLAB_REPOSITORY -> format: {owner}/{repo}
+
+"""
+from typing import Optional
+
+from langchain.callbacks.manager import CallbackManagerForToolRun
+from langchain.pydantic_v1 import Field
+from langchain.tools.base import BaseTool
+from langchain.utilities.gitlab import GitLabAPIWrapper
+
+
+class GitLabAction(BaseTool):
+ """Tool for interacting with the GitLab API."""
+
+ api_wrapper: GitLabAPIWrapper = Field(default_factory=GitLabAPIWrapper)
+ mode: str
+ name: str = ""
+ description: str = ""
+
+ def _run(
+ self,
+ instructions: str,
+ run_manager: Optional[CallbackManagerForToolRun] = None,
+ ) -> str:
+ """Use the GitLab API to run an operation."""
+ return self.api_wrapper.run(self.mode, instructions)
diff --git a/libs/langchain/langchain/utilities/gitlab.py b/libs/langchain/langchain/utilities/gitlab.py
new file mode 100644
index 00000000000..0ad8db3c995
--- /dev/null
+++ b/libs/langchain/langchain/utilities/gitlab.py
@@ -0,0 +1,319 @@
+"""Util that calls gitlab."""
+from __future__ import annotations
+
+import json
+from typing import TYPE_CHECKING, Any, Dict, List, Optional
+
+from langchain.pydantic_v1 import BaseModel, Extra, root_validator
+from langchain.utils import get_from_dict_or_env
+
+if TYPE_CHECKING:
+ from gitlab.v4.objects import Issue
+
+
+class GitLabAPIWrapper(BaseModel):
+ """Wrapper for GitLab API."""
+
+ gitlab: Any #: :meta private:
+ gitlab_repo_instance: Any #: :meta private:
+ gitlab_repository: Optional[str] = None
+ """The name of the GitLab repository, in the form {username}/{repo-name}."""
+ gitlab_personal_access_token: Optional[str] = None
+ """Personal access token for the GitLab service, used for authentication."""
+ gitlab_branch: Optional[str] = None
+ """The specific branch in the GitLab repository where the bot will make
+ its commits. Defaults to 'main'.
+ """
+ gitlab_base_branch: Optional[str] = None
+ """The base branch in the GitLab repository, used for comparisons.
+ Usually 'main' or 'master'. Defaults to 'main'.
+ """
+
+ class Config:
+ """Configuration for this pydantic object."""
+
+ extra = Extra.forbid
+
+ @root_validator()
+ def validate_environment(cls, values: Dict) -> Dict:
+ """Validate that api key and python package exists in environment."""
+ gitlab_repository = get_from_dict_or_env(
+ values, "gitlab_repository", "GITLAB_REPOSITORY"
+ )
+
+ gitlab_personal_access_token = get_from_dict_or_env(
+ values, "gitlab_personal_access_token", "GITLAB_PERSONAL_ACCESS_TOKEN"
+ )
+
+ gitlab_branch = get_from_dict_or_env(
+ values, "gitlab_branch", "GITLAB_BRANCH", default="main"
+ )
+ gitlab_base_branch = get_from_dict_or_env(
+ values, "gitlab_base_branch", "GITLAB_BASE_BRANCH", default="main"
+ )
+
+ try:
+ import gitlab
+
+ except ImportError:
+ raise ImportError(
+ "python-gitlab is not installed. "
+ "Please install it with `pip install python-gitlab`"
+ )
+
+ g = gitlab.Gitlab(private_token=gitlab_personal_access_token)
+
+ g.auth()
+
+ values["gitlab"] = g
+ values["gitlab_repo_instance"] = g.projects.get(gitlab_repository)
+ values["gitlab_repository"] = gitlab_repository
+ values["gitlab_personal_access_token"] = gitlab_personal_access_token
+ values["gitlab_branch"] = gitlab_branch
+ values["gitlab_base_branch"] = gitlab_base_branch
+
+ return values
+
+ def parse_issues(self, issues: List[Issue]) -> List[dict]:
+ """
+ Extracts title and number from each Issue and puts them in a dictionary
+ Parameters:
+ issues(List[Issue]): A list of gitlab Issue objects
+ Returns:
+ List[dict]: A dictionary of issue titles and numbers
+ """
+ parsed = []
+ for issue in issues:
+ title = issue.title
+ number = issue.iid
+ parsed.append({"title": title, "number": number})
+ return parsed
+
+ def get_issues(self) -> str:
+ """
+ Fetches all open issues from the repo
+
+ Returns:
+ str: A plaintext report containing the number of issues
+ and each issue's title and number.
+ """
+ issues = self.gitlab_repo_instance.issues.list(state="opened")
+ if len(issues) > 0:
+ parsed_issues = self.parse_issues(issues)
+ parsed_issues_str = (
+ "Found " + str(len(parsed_issues)) + " issues:\n" + str(parsed_issues)
+ )
+ return parsed_issues_str
+ else:
+ return "No open issues available"
+
+ def get_issue(self, issue_number: int) -> Dict[str, Any]:
+ """
+ Fetches a specific issue and its first 10 comments
+ Parameters:
+ issue_number(int): The number for the gitlab issue
+ Returns:
+ dict: A dictionary containing the issue's title,
+ body, and comments as a string
+ """
+ issue = self.gitlab_repo_instance.issues.get(issue_number)
+ page = 0
+ comments: List[dict] = []
+ while len(comments) <= 10:
+ comments_page = issue.notes.list(page=page)
+ if len(comments_page) == 0:
+ break
+ for comment in comments_page:
+ comment = issue.notes.get(comment.id)
+ comments.append(
+ {"body": comment.body, "user": comment.author["username"]}
+ )
+ page += 1
+
+ return {
+ "title": issue.title,
+ "body": issue.description,
+ "comments": str(comments),
+ }
+
+ def create_pull_request(self, pr_query: str) -> str:
+ """
+ Makes a pull request from the bot's branch to the base branch
+ Parameters:
+ pr_query(str): a string which contains the PR title
+ and the PR body. The title is the first line
+ in the string, and the body are the rest of the string.
+ For example, "Updated README\nmade changes to add info"
+ Returns:
+ str: A success or failure message
+ """
+ if self.gitlab_base_branch == self.gitlab_branch:
+ return """Cannot make a pull request because
+ commits are already in the master branch"""
+ else:
+ try:
+ title = pr_query.split("\n")[0]
+ body = pr_query[len(title) + 2 :]
+ pr = self.gitlab_repo_instance.mergerequests.create(
+ {
+ "source_branch": self.gitlab_branch,
+ "target_branch": self.gitlab_base_branch,
+ "title": title,
+ "description": body,
+ "labels": ["created-by-agent"],
+ }
+ )
+ return f"Successfully created PR number {pr.iid}"
+ except Exception as e:
+ return "Unable to make pull request due to error:\n" + str(e)
+
+ def comment_on_issue(self, comment_query: str) -> str:
+ """
+ Adds a comment to a gitlab issue
+ Parameters:
+ comment_query(str): a string which contains the issue number,
+ two newlines, and the comment.
+ for example: "1\n\nWorking on it now"
+ adds the comment "working on it now" to issue 1
+ Returns:
+ str: A success or failure message
+ """
+ issue_number = int(comment_query.split("\n\n")[0])
+ comment = comment_query[len(str(issue_number)) + 2 :]
+ try:
+ issue = self.gitlab_repo_instance.issues.get(issue_number)
+ issue.notes.create({"body": comment})
+ return "Commented on issue " + str(issue_number)
+ except Exception as e:
+ return "Unable to make comment due to error:\n" + str(e)
+
+ def create_file(self, file_query: str) -> str:
+ """
+ Creates a new file on the gitlab repo
+ Parameters:
+ file_query(str): a string which contains the file path
+ and the file contents. The file path is the first line
+ in the string, and the contents are the rest of the string.
+ For example, "hello_world.md\n# Hello World!"
+ Returns:
+ str: A success or failure message
+ """
+ file_path = file_query.split("\n")[0]
+ file_contents = file_query[len(file_path) + 2 :]
+ try:
+ self.gitlab_repo_instance.files.get(file_path, self.gitlab_branch)
+ return f"File already exists at {file_path}. Use update_file instead"
+ except Exception:
+ data = {
+ "branch": self.gitlab_branch,
+ "commit_message": "Create " + file_path,
+ "file_path": file_path,
+ "content": file_contents,
+ }
+
+ self.gitlab_repo_instance.files.create(data)
+
+ return "Created file " + file_path
+
+ def read_file(self, file_path: str) -> str:
+ """
+ Reads a file from the gitlab repo
+ Parameters:
+ file_path(str): the file path
+ Returns:
+ str: The file decoded as a string
+ """
+ file = self.gitlab_repo_instance.files.get(file_path, self.gitlab_branch)
+ return file.decode().decode("utf-8")
+
+ def update_file(self, file_query: str) -> str:
+ """
+ Updates a file with new content.
+ Parameters:
+ file_query(str): Contains the file path and the file contents.
+ The old file contents is wrapped in OLD <<<< and >>>> OLD
+ The new file contents is wrapped in NEW <<<< and >>>> NEW
+ For example:
+ test/hello.txt
+ OLD <<<<
+ Hello Earth!
+ >>>> OLD
+ NEW <<<<
+ Hello Mars!
+ >>>> NEW
+ Returns:
+ A success or failure message
+ """
+ try:
+ file_path = file_query.split("\n")[0]
+ old_file_contents = (
+ file_query.split("OLD <<<<")[1].split(">>>> OLD")[0].strip()
+ )
+ new_file_contents = (
+ file_query.split("NEW <<<<")[1].split(">>>> NEW")[0].strip()
+ )
+
+ file_content = self.read_file(file_path)
+ updated_file_content = file_content.replace(
+ old_file_contents, new_file_contents
+ )
+
+ if file_content == updated_file_content:
+ return (
+ "File content was not updated because old content was not found."
+ "It may be helpful to use the read_file action to get "
+ "the current file contents."
+ )
+
+ commit = {
+ "branch": self.gitlab_branch,
+ "commit_message": "Create " + file_path,
+ "actions": [
+ {
+ "action": "update",
+ "file_path": file_path,
+ "content": updated_file_content,
+ }
+ ],
+ }
+
+ self.gitlab_repo_instance.commits.create(commit)
+ return "Updated file " + file_path
+ except Exception as e:
+ return "Unable to update file due to error:\n" + str(e)
+
+ def delete_file(self, file_path: str) -> str:
+ """
+ Deletes a file from the repo
+ Parameters:
+ file_path(str): Where the file is
+ Returns:
+ str: Success or failure message
+ """
+ try:
+ self.gitlab_repo_instance.files.delete(
+ file_path, self.gitlab_branch, "Delete " + file_path
+ )
+ return "Deleted file " + file_path
+ except Exception as e:
+ return "Unable to delete file due to error:\n" + str(e)
+
+ def run(self, mode: str, query: str) -> str:
+ if mode == "get_issues":
+ return self.get_issues()
+ elif mode == "get_issue":
+ return json.dumps(self.get_issue(int(query)))
+ elif mode == "comment_on_issue":
+ return self.comment_on_issue(query)
+ elif mode == "create_file":
+ return self.create_file(query)
+ elif mode == "create_pull_request":
+ return self.create_pull_request(query)
+ elif mode == "read_file":
+ return self.read_file(query)
+ elif mode == "update_file":
+ return self.update_file(query)
+ elif mode == "delete_file":
+ return self.delete_file(query)
+ else:
+ raise ValueError("Invalid mode" + mode)