189 lines
6.3 KiB
Python
189 lines
6.3 KiB
Python
"""
|
|
Bitbucket Service - Client for Bitbucket Server API.
|
|
"""
|
|
from typing import Optional, Dict, Any, List
|
|
import httpx
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BitbucketClient:
|
|
"""Bitbucket Server REST API client."""
|
|
|
|
def __init__(self, base_url: str, token: str):
|
|
self.base_url = base_url.rstrip("/")
|
|
self.headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
async def get_file_content(
|
|
self,
|
|
project: str,
|
|
repo: str,
|
|
file_path: str,
|
|
ref: str = "main",
|
|
) -> str:
|
|
"""Get raw file content from a repository."""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos/{repo}/raw/{file_path}",
|
|
headers=self.headers,
|
|
params={"at": ref},
|
|
)
|
|
response.raise_for_status()
|
|
return response.text
|
|
|
|
async def list_files(
|
|
self,
|
|
project: str,
|
|
repo: str,
|
|
path: str = "",
|
|
ref: str = "main",
|
|
) -> List[Dict[str, Any]]:
|
|
"""List files in a directory."""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos/{repo}/files/{path}",
|
|
headers=self.headers,
|
|
params={"at": ref},
|
|
)
|
|
response.raise_for_status()
|
|
return response.json().get("values", [])
|
|
|
|
async def create_branch(
|
|
self,
|
|
project: str,
|
|
repo: str,
|
|
branch_name: str,
|
|
start_point: str = "main",
|
|
) -> Dict[str, Any]:
|
|
"""Create a new branch."""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos/{repo}/branches",
|
|
headers=self.headers,
|
|
json={
|
|
"name": branch_name,
|
|
"startPoint": f"refs/heads/{start_point}",
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
async def commit_file(
|
|
self,
|
|
project: str,
|
|
repo: str,
|
|
branch: str,
|
|
file_path: str,
|
|
content: str,
|
|
message: str,
|
|
) -> Dict[str, Any]:
|
|
"""Commit a file change to a branch."""
|
|
# Get current commit for the branch
|
|
async with httpx.AsyncClient() as client:
|
|
# First, get the latest commit on the branch
|
|
branch_response = await client.get(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos/{repo}/branches",
|
|
headers=self.headers,
|
|
params={"filterText": branch},
|
|
)
|
|
branch_response.raise_for_status()
|
|
branches = branch_response.json().get("values", [])
|
|
|
|
if not branches:
|
|
raise ValueError(f"Branch not found: {branch}")
|
|
|
|
latest_commit = branches[0].get("latestCommit")
|
|
|
|
# Use file edit API
|
|
response = await client.put(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos/{repo}/browse/{file_path}",
|
|
headers=self.headers,
|
|
json={
|
|
"content": content,
|
|
"message": message,
|
|
"branch": branch,
|
|
"sourceCommitId": latest_commit,
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
async def create_pull_request(
|
|
self,
|
|
project: str,
|
|
repo: str,
|
|
title: str,
|
|
description: str,
|
|
source_branch: str,
|
|
target_branch: str = "main",
|
|
target_project: Optional[str] = None,
|
|
target_repo: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
"""Create a pull request."""
|
|
target_project = target_project or project
|
|
target_repo = target_repo or repo
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos/{repo}/pull-requests",
|
|
headers=self.headers,
|
|
json={
|
|
"title": title,
|
|
"description": description,
|
|
"fromRef": {
|
|
"id": f"refs/heads/{source_branch}",
|
|
"repository": {
|
|
"slug": repo,
|
|
"project": {"key": project},
|
|
},
|
|
},
|
|
"toRef": {
|
|
"id": f"refs/heads/{target_branch}",
|
|
"repository": {
|
|
"slug": target_repo,
|
|
"project": {"key": target_project},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
async def get_repositories(self, project: str) -> List[Dict[str, Any]]:
|
|
"""List repositories in a project."""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.base_url}/rest/api/1.0/projects/{project}/repos",
|
|
headers=self.headers,
|
|
)
|
|
response.raise_for_status()
|
|
return response.json().get("values", [])
|
|
|
|
async def search_code(
|
|
self,
|
|
project: str,
|
|
repo: str,
|
|
query: str,
|
|
ref: str = "main",
|
|
) -> List[Dict[str, Any]]:
|
|
"""Search for code in a repository."""
|
|
# Bitbucket Server code search API
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.base_url}/rest/search/1.0/search",
|
|
headers=self.headers,
|
|
params={
|
|
"query": query,
|
|
"entities": "code",
|
|
"projectKey": project,
|
|
"repositorySlug": repo,
|
|
},
|
|
)
|
|
if response.status_code == 200:
|
|
return response.json().get("values", [])
|
|
return []
|