Source code for langchain_community.utilities.gitlab
"""Util that calls gitlab."""from__future__importannotationsimportjsonfromtypingimportTYPE_CHECKING,Any,Dict,List,Optionalfromlangchain_core.utilsimportget_from_dict_or_envfrompydanticimportBaseModel,ConfigDict,model_validatorifTYPE_CHECKING:fromgitlab.v4.objectsimportIssue
[docs]classGitLabAPIWrapper(BaseModel):"""Wrapper for GitLab API."""gitlab:Any=None#: :meta private:gitlab_repo_instance:Any=None#: :meta private:gitlab_url:Optional[str]=None"""The url of the GitLab instance."""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'. """model_config=ConfigDict(extra="forbid",)@model_validator(mode="before")@classmethoddefvalidate_environment(cls,values:Dict)->Any:"""Validate that api key and python package exists in environment."""gitlab_url=get_from_dict_or_env(values,"gitlab_url","GITLAB_URL",default="https://gitlab.com")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:importgitlabexceptImportError:raiseImportError("python-gitlab is not installed. ""Please install it with `pip install python-gitlab`")g=gitlab.Gitlab(url=gitlab_url,private_token=gitlab_personal_access_token,keep_base_url=True,)g.auth()values["gitlab"]=gvalues["gitlab_repo_instance"]=g.projects.get(gitlab_repository)values["gitlab_url"]=gitlab_urlvalues["gitlab_repository"]=gitlab_repositoryvalues["gitlab_personal_access_token"]=gitlab_personal_access_tokenvalues["gitlab_branch"]=gitlab_branchvalues["gitlab_base_branch"]=gitlab_base_branchreturnvalues
[docs]defparse_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=[]forissueinissues:title=issue.titlenumber=issue.iidparsed.append({"title":title,"number":number})returnparsed
[docs]defget_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")iflen(issues)>0:parsed_issues=self.parse_issues(issues)parsed_issues_str=("Found "+str(len(parsed_issues))+" issues:\n"+str(parsed_issues))returnparsed_issues_strelse:return"No open issues available"
[docs]defget_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=0comments:List[dict]=[]whilelen(comments)<=10:comments_page=issue.notes.list(page=page)iflen(comments_page)==0:breakforcommentincomments_page:comment=issue.notes.get(comment.id)comments.append({"body":comment.body,"user":comment.author["username"],})page+=1return{"title":issue.title,"body":issue.description,"comments":str(comments),}
[docs]defcreate_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 """ifself.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"],})returnf"Successfully created PR number {pr.iid}"exceptExceptionase:return"Unable to make pull request due to error:\n"+str(e)
[docs]defcomment_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)exceptExceptionase:return"Unable to make comment due to error:\n"+str(e)
[docs]defcreate_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 """ifself.gitlab_branch==self.gitlab_base_branch:return("You're attempting to commit directly"f"to the {self.gitlab_base_branch} branch, which is protected. ""Please create a new branch and try again.")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)returnf"File already exists at {file_path}. Use update_file instead"exceptException: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
[docs]defread_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)returnfile.decode().decode("utf-8")
[docs]defupdate_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 """ifself.gitlab_branch==self.gitlab_base_branch:return("You're attempting to commit directly"f"to the {self.gitlab_base_branch} branch, which is protected. ""Please create a new branch and try again.")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)iffile_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_pathexceptExceptionase:return"Unable to update file due to error:\n"+str(e)
[docs]defdelete_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 """ifself.gitlab_branch==self.gitlab_base_branch:return("You're attempting to commit directly"f"to the {self.gitlab_base_branch} branch, which is protected. ""Please create a new branch and try again.")try:self.gitlab_repo_instance.files.delete(file_path,self.gitlab_branch,"Delete "+file_path)return"Deleted file "+file_pathexceptExceptionase:return"Unable to delete file due to error:\n"+str(e)
[docs]deflist_files_in_main_branch(self)->str:""" Get the list of files in the main branch of the repository Returns: str: A plaintext report containing the list of files in the repository in the main branch """ifself.gitlab_base_branchisNone:return"No base branch set. Please set a base branch."returnself._list_files(self.gitlab_base_branch)
[docs]deflist_files_in_bot_branch(self)->str:""" Get the list of files in the active branch of the repository Returns: str: A plaintext report containing the list of files in the repository in the active branch """ifself.gitlab_branchisNone:return"No active branch set. Please set a branch."returnself._list_files(self.gitlab_branch)
[docs]deflist_files_from_directory(self,path:str)->str:""" Get the list of files in the active branch of the repository from a specific directory Returns: str: A plaintext report containing the list of files in the repository in the active branch from the specified directory """ifself.gitlab_branchisNone:return"No active branch set. Please set a branch."returnself._list_files(branch=self.gitlab_branch,path=path,)
def_list_files(self,branch:str,path:str="")->str:try:files=self._get_repository_files(branch=branch,path=path,)iffiles:files_str="\n".join(files)returnf"Found {len(files)} files in branch `{branch}`:\n{files_str}"else:returnf"No files found in branch: `{branch}`"exceptExceptionase:returnf"Error: {e}"def_get_repository_files(self,branch:str,path:str="")->List[str]:repo_contents=self.gitlab_repo_instance.repository_tree(ref=branch,path=path)files:List[str]=[]forcontentinrepo_contents:ifcontent["type"]=="tree":files.extend(self._get_repository_files(branch,content["path"]))else:files.append(content["path"])returnfiles
[docs]defcreate_branch(self,proposed_branch_name:str)->str:""" Create a new branch in the repository and set it as the active branch Parameters: proposed_branch_name (str): The name of the new branch to be created Returns: str: A success or failure message """fromgitlabimportGitlabCreateErrormax_attempts=100new_branch_name=proposed_branch_nameforiinrange(max_attempts):try:response=self.gitlab_repo_instance.branches.create({"branch":new_branch_name,"ref":self.gitlab_branch,})self.gitlab_branch=response.namereturn(f"Branch '{response.name}' ""created successfully, and set as current active branch.")exceptGitlabCreateErrorase:if(e.response_code==400and"Branch already exists"ine.error_message):i+=1new_branch_name=f"{proposed_branch_name}_v{i}"else:# Handle any other exceptionsprint(f"Failed to create branch. Error: {e}")# noqa: T201raiseException("Unable to create branch name from proposed_branch_name: "f"{proposed_branch_name}")return(f"Unable to create branch. At least {max_attempts} branches exist "f"with named derived from "f"proposed_branch_name: `{proposed_branch_name}`")
[docs]deflist_branches_in_repo(self)->str:""" Get the list of branches in the repository Returns: str: A plaintext report containing the number of branches and each branch name """branches=[branch.nameforbranchinself.gitlab_repo_instance.branches.list(all=True)]ifbranches:branches_str="\n".join(branches)return(f"Found {str(len(branches))} branches in the repository:"f"\n{branches_str}")return"No branches found in the repository"
[docs]defset_active_branch(self,branch_name:str)->str:"""Equivalent to `git checkout branch_name` for this Agent. Clones formatting from Gitlab. Returns an Error (as a string) if branch doesn't exist. """curr_branches=[branch.nameforbranchinself.gitlab_repo_instance.branches.list(all=True,)]ifbranch_nameincurr_branches:self.gitlab_branch=branch_namereturnf"Switched to branch `{branch_name}`"else:return(f"Error {branch_name} does not exist,"f"in repo with current branches: {str(curr_branches)}")