mirror of
				https://github.com/open-webui/openapi-servers
				synced 2025-06-26 18:17:04 +00:00 
			
		
		
		
	feat: git server
This commit is contained in:
		
							parent
							
								
									37d84e6a97
								
							
						
					
					
						commit
						068f232fad
					
				
							
								
								
									
										1
									
								
								servers/git/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								servers/git/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Untested MCP Port, Contribution Welcome. | ||||||
							
								
								
									
										275
									
								
								servers/git/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								servers/git/main.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,275 @@ | |||||||
|  | from fastapi import FastAPI, HTTPException | ||||||
|  | from fastapi.middleware.cors import CORSMiddleware | ||||||
|  | 
 | ||||||
|  | import logging | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import List, Optional | ||||||
|  | from enum import Enum | ||||||
|  | import git | ||||||
|  | from pydantic import BaseModel, Field | ||||||
|  | 
 | ||||||
|  | app = FastAPI( | ||||||
|  |     title="Git Management API", | ||||||
|  |     description="An API to manage Git repositories with explicit endpoints, inputs, and outputs for better OpenAPI schemas.", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | origins = ["*"] | ||||||
|  | 
 | ||||||
|  | app.add_middleware( | ||||||
|  |     CORSMiddleware, | ||||||
|  |     allow_origins=origins, | ||||||
|  |     allow_credentials=True, | ||||||
|  |     allow_methods=["*"], | ||||||
|  |     allow_headers=["*"], | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # ----------------- ENUMS ----------------- | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitTools(str, Enum): | ||||||
|  |     STATUS = "status" | ||||||
|  |     DIFF_UNSTAGED = "diff_unstaged" | ||||||
|  |     DIFF_STAGED = "diff_staged" | ||||||
|  |     DIFF = "diff" | ||||||
|  |     COMMIT = "commit" | ||||||
|  |     ADD = "add" | ||||||
|  |     RESET = "reset" | ||||||
|  |     LOG = "log" | ||||||
|  |     CREATE_BRANCH = "create_branch" | ||||||
|  |     CHECKOUT = "checkout" | ||||||
|  |     SHOW = "show" | ||||||
|  |     INIT = "init" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # ----------------- MODELS ----------------- | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitRepoPath(BaseModel): | ||||||
|  |     repo_path: str = Field(..., description="File system path to the Git repository.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitStatusRequest(GitRepoPath): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitDiffUnstagedRequest(GitRepoPath): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitDiffStagedRequest(GitRepoPath): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitDiffRequest(GitRepoPath): | ||||||
|  |     target: str = Field(..., description="The branch or commit to diff against.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitCommitRequest(GitRepoPath): | ||||||
|  |     message: str = Field(..., description="Commit message for recording the change.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitAddRequest(GitRepoPath): | ||||||
|  |     files: List[str] = Field( | ||||||
|  |         ..., description="List of file paths to add to the staging area." | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitResetRequest(GitRepoPath): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitLogRequest(GitRepoPath): | ||||||
|  |     max_count: int = Field(10, description="Maximum number of commits to retrieve.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitCreateBranchRequest(GitRepoPath): | ||||||
|  |     branch_name: str = Field(..., description="Name of the branch to create.") | ||||||
|  |     base_branch: Optional[str] = Field( | ||||||
|  |         None, description="Optional base branch name to create the new branch from." | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitCheckoutRequest(GitRepoPath): | ||||||
|  |     branch_name: str = Field(..., description="Branch name to checkout.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitShowRequest(GitRepoPath): | ||||||
|  |     revision: str = Field( | ||||||
|  |         ..., description="The commit hash or branch/tag name to show." | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class GitInitRequest(GitRepoPath): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TextResponse(BaseModel): | ||||||
|  |     result: str = Field(..., description="Description of the operation result.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LogResponse(BaseModel): | ||||||
|  |     commits: List[str] = Field( | ||||||
|  |         ..., description="A list of formatted commit log entries." | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # ----------------- UTILITY FUNCTIONS ----------------- | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_repo(repo_path: str) -> git.Repo: | ||||||
|  |     try: | ||||||
|  |         return git.Repo(repo_path) | ||||||
|  |     except git.InvalidGitRepositoryError: | ||||||
|  |         raise HTTPException( | ||||||
|  |             status_code=400, detail=f"Invalid Git repository at '{repo_path}'" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # ----------------- API ENDPOINTS ----------------- | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/status", | ||||||
|  |     response_model=TextResponse, | ||||||
|  |     description="Get the current status of the Git repository.", | ||||||
|  | ) | ||||||
|  | def get_status(request: GitStatusRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     status = repo.git.status() | ||||||
|  |     return TextResponse(result=status) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/diff_unstaged", | ||||||
|  |     response_model=TextResponse, | ||||||
|  |     description="Get differences of unstaged changes.", | ||||||
|  | ) | ||||||
|  | def diff_unstaged(request: GitDiffUnstagedRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     diff = repo.git.diff() | ||||||
|  |     return TextResponse(result=diff) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/diff_staged", | ||||||
|  |     response_model=TextResponse, | ||||||
|  |     description="Get differences of staged changes.", | ||||||
|  | ) | ||||||
|  | def diff_staged(request: GitDiffStagedRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     diff = repo.git.diff("--cached") | ||||||
|  |     return TextResponse(result=diff) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/diff", | ||||||
|  |     response_model=TextResponse, | ||||||
|  |     description="Get comparison between two branches or commits.", | ||||||
|  | ) | ||||||
|  | def diff_target(request: GitDiffRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     diff = repo.git.diff(request.target) | ||||||
|  |     return TextResponse(result=diff) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/commit", | ||||||
|  |     response_model=TextResponse, | ||||||
|  |     description="Commit staged changes to the repository.", | ||||||
|  | ) | ||||||
|  | def commit_changes(request: GitCommitRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     commit = repo.index.commit(request.message) | ||||||
|  |     return TextResponse(result=f"Committed changes with hash {commit.hexsha}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post("/add", response_model=TextResponse, description="Stage files for commit.") | ||||||
|  | def add_files(request: GitAddRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     repo.index.add(request.files) | ||||||
|  |     return TextResponse(result="Files staged successfully.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/reset", response_model=TextResponse, description="Unstage all staged changes." | ||||||
|  | ) | ||||||
|  | def reset_changes(request: GitResetRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     repo.index.reset() | ||||||
|  |     return TextResponse(result="All staged changes reset.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/log", | ||||||
|  |     response_model=LogResponse, | ||||||
|  |     description="Get recent commit history of the repository.", | ||||||
|  | ) | ||||||
|  | def get_log(request: GitLogRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     commits = [ | ||||||
|  |         f"Commit: {commit.hexsha}\n" | ||||||
|  |         f"Author: {commit.author}\n" | ||||||
|  |         f"Date: {commit.authored_datetime}\n" | ||||||
|  |         f"Message: {commit.message.strip()}\n" | ||||||
|  |         for commit in repo.iter_commits(max_count=request.max_count) | ||||||
|  |     ] | ||||||
|  |     return LogResponse(commits=commits) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/create_branch", response_model=TextResponse, description="Create a new branch." | ||||||
|  | ) | ||||||
|  | def create_branch(request: GitCreateBranchRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     if request.base_branch is None: | ||||||
|  |         base_branch = repo.active_branch | ||||||
|  |     else: | ||||||
|  |         base_branch = repo.refs[request.base_branch] | ||||||
|  |     repo.create_head(request.branch_name, base_branch) | ||||||
|  |     return TextResponse( | ||||||
|  |         result=f"Created branch '{request.branch_name}' from '{base_branch}'." | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/checkout", response_model=TextResponse, description="Checkout an existing branch." | ||||||
|  | ) | ||||||
|  | def checkout_branch(request: GitCheckoutRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     repo.git.checkout(request.branch_name) | ||||||
|  |     return TextResponse(result=f"Switched to branch '{request.branch_name}'.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/show", | ||||||
|  |     response_model=TextResponse, | ||||||
|  |     description="Show details and diff of a specific commit.", | ||||||
|  | ) | ||||||
|  | def show_revision(request: GitShowRequest): | ||||||
|  |     repo = get_repo(request.repo_path) | ||||||
|  |     commit = repo.commit(request.revision) | ||||||
|  |     details = ( | ||||||
|  |         f"Commit: {commit.hexsha}\n" | ||||||
|  |         f"Author: {commit.author}\n" | ||||||
|  |         f"Date: {commit.authored_datetime}\n" | ||||||
|  |         f"Message: {commit.message.strip()}\n" | ||||||
|  |     ) | ||||||
|  |     diff = commit.diff( | ||||||
|  |         commit.parents[0] if commit.parents else git.NULL_TREE, create_patch=True | ||||||
|  |     ) | ||||||
|  |     diff_text = "\n".join(d.diff.decode("utf-8") for d in diff) | ||||||
|  |     return TextResponse(result=details + "\n" + diff_text) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post( | ||||||
|  |     "/init", response_model=TextResponse, description="Initialize a new Git repository." | ||||||
|  | ) | ||||||
|  | def init_repo(request: GitInitRequest): | ||||||
|  |     try: | ||||||
|  |         repo = git.Repo.init(path=request.repo_path, mkdir=True) | ||||||
|  |         return TextResponse( | ||||||
|  |             result=f"Initialized empty Git repository at '{repo.git_dir}'" | ||||||
|  |         ) | ||||||
|  |     except Exception as e: | ||||||
|  |         raise HTTPException(status_code=500, detail=str(e)) | ||||||
							
								
								
									
										7
									
								
								servers/git/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								servers/git/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | fastapi | ||||||
|  | uvicorn[standard] | ||||||
|  | pydantic | ||||||
|  | python-multipart | ||||||
|  | 
 | ||||||
|  | pytz | ||||||
|  | python-dateutil | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user