gitea-cli/gitea_cli.py

343 lines
13 KiB
Python
Executable File

#!/opt/homebrew/anaconda3/bin/python
import os
import sys
import click
import requests
import subprocess
class GiteaAPI:
def __init__(self, base_url, token):
self.base_url = base_url.rstrip('/')
self.token = token
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'token {token}',
'Content-Type': 'application/json',
})
def create_repository(self, name, description="", private=False):
"""Create a new repository on Gitea."""
endpoint = f"{self.base_url}/api/v1/user/repos"
data = {
"name": name,
"description": description,
"private": private,
"auto_init": True
}
response = self.session.post(endpoint, json=data)
if response.status_code == 201:
return response.json()
else:
response.raise_for_status()
def update_repository_visibility(self, name, visibility):
"""Update repository visibility."""
endpoint = f"{self.base_url}/api/v1/repos/{name}"
data = {
"visibility": visibility
}
response = self.session.patch(endpoint, json=data)
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()
def list_user_repositories(self, username, page=1, limit=30):
"""List repositories for a specific user.
Args:
username: The user to list repositories for
page: Page number (default: 1)
limit: Items per page (default: 30, max: 50)
"""
endpoint = f"{self.base_url}/api/v1/users/{username}/repos"
params = {
'page': page,
'limit': min(limit, 50) # Gitea's max limit is 50
}
response = self.session.get(endpoint, params=params)
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()
def search_user_repositories(self, username, query=None, private=None,
search_in='name', page=1, limit=30):
"""Search repositories for a specific user with advanced filters.
Args:
username: The user to search repositories for
query: Optional search term (searches name/description)
private: Optional filter by private status (True/False)
search_in: Where to search ('name', 'description', or 'both')
page: Page number (default: 1)
limit: Items per page (default: 30, max: 50)
"""
endpoint = f"{self.base_url}/api/v1/repos/search"
params = {
'uid': username,
'page': page,
'limit': min(limit, 50)
}
if query:
if search_in == 'name':
params['q'] = query
elif search_in == 'description':
params['q'] = f'description:{query}'
else: # both - search all fields
params['q'] = query
if private is not None:
params['private'] = 'true' if private else 'false'
response = self.session.get(endpoint, params=params)
if response.status_code == 200:
return response.json().get('data', [])
else:
response.raise_for_status()
def get_gitea_config(url=None, token=None):
"""Get Gitea configuration from environment or command line arguments."""
config = {
'url': url or os.environ.get('GITEA_URL'),
'token': token or os.environ.get('GITEA_TOKEN')
}
missing = []
if not config['url']:
missing.append('GITEA_URL')
if not config['token']:
missing.append('GITEA_TOKEN')
if missing:
missing_str = ', '.join(missing)
click.echo(f"Error: Missing required configuration: {missing_str}")
click.echo("Please either:")
click.echo("1. Set environment variables for the missing values")
click.echo("2. Provide them as command line arguments:")
click.echo(" --gitea-url URL --gitea-token TOKEN")
sys.exit(1)
return config
def is_git_repository():
"""Check if current directory is a git repository."""
try:
subprocess.run(['git', 'rev-parse', '--git-dir'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True)
return True
except subprocess.CalledProcessError:
return False
def set_git_remote(repo_url):
"""Set or update the git remote 'origin' for the current repository."""
try:
# Check if remote exists
result = subprocess.run(['git', 'remote'],
stdout=subprocess.PIPE,
text=True,
check=True)
if 'origin' in result.stdout.split():
# Remove existing origin
subprocess.run(['git', 'remote', 'remove', 'origin'],
check=True)
# Add new origin
subprocess.run(['git', 'remote', 'add', 'origin', repo_url],
check=True)
click.echo(f"Successfully set remote 'origin' to: {repo_url}")
return True
except subprocess.CalledProcessError as e:
click.echo(f"Error setting git remote: {str(e)}")
return False
@click.group()
def cli():
"""Gitea CLI - Command line interface for Gitea
Commands:
create-repo: Create a new repository
list-repos: List user repositories
search-repos: Search repositories with filters
mode: Toggle repository visibility
Run 'gitea_cli.py COMMAND --help' for details.
"""
pass
@cli.command()
@click.option('--name', required=True, help='Name of the repository')
@click.option('--description', default="", help='Repository description')
@click.option('--private', is_flag=True, default=False, help='Make repository private')
@click.option('--set-remote', is_flag=True, default=False,
help='Set the remote origin for the current git repository')
@click.option('--gitea-url',
help='Gitea server URL (overrides GITEA_URL environment variable)')
@click.option('--gitea-token',
help='Gitea API token (overrides GITEA_TOKEN environment variable)')
def create_repo(name, description, private, set_remote, gitea_url, gitea_token):
"""Create a new repository on Gitea.
Examples:
Using environment variables:
$ export GITEA_URL=https://gitea.example.com
$ export GITEA_TOKEN=your_token
$ gitea_cli.py create-repo --name my-repo
Using command line arguments:
$ gitea_cli.py create-repo --gitea-url https://gitea.example.com --gitea-token your_token --name my-repo
Create private repo and set as remote:
$ gitea_cli.py create-repo --name my-repo --private --set-remote
"""
config = get_gitea_config(gitea_url, gitea_token)
try:
gitea = GiteaAPI(config['url'], config['token'])
repo = gitea.create_repository(name, description, private)
click.echo(f"Successfully created repository: {repo['html_url']}")
if set_remote:
if not is_git_repository():
click.echo("Error: Current directory is not a git repository")
sys.exit(1)
clone_url = repo['clone_url']
# Replace HTTPS URL with token-based URL for authentication
if clone_url.startswith('https://'):
parsed_url = clone_url.split('://')
clone_url = f"{parsed_url[0]}://oauth2:{config['token']}@{parsed_url[1]}"
set_git_remote(clone_url)
except requests.exceptions.RequestException as e:
click.echo(f"Error creating repository: {str(e)}")
sys.exit(1)
@cli.command()
@click.option('--name', required=True, help='Name of the repository')
@click.option('--public', is_flag=True, default=False, help='Set repository to public')
@click.option('--private', is_flag=True, default=False, help='Set repository to private')
def mode(name, public, private):
"""Toggle repository visibility between public and private"""
if public and private:
click.echo("Error: Cannot specify both --public and --private")
return
if not public and not private:
click.echo("Error: Must specify either --public or --private")
return
config = get_gitea_config()
try:
gitea = GiteaAPI(config['url'], config['token'])
visibility = 'public' if public else 'private'
gitea.update_repository_visibility(name, visibility)
click.echo(f"Successfully set repository {name} to {visibility}")
except Exception as e:
click.echo(f"Error: {str(e)}")
@cli.command()
@click.option('--user', required=True, help='Username to list repositories for')
@click.option('--page', default=1, help='Page number (default: 1)')
@click.option('--limit', default=30, help='Items per page (default: 30, max: 50)')
@click.option('--gitea-url',
help='Gitea server URL (overrides GITEA_URL environment variable)')
@click.option('--gitea-token',
help='Gitea API token (overrides GITEA_TOKEN environment variable)')
def list_repos(user, page, limit, gitea_url, gitea_token):
"""List repositories for a specific user.
Examples:
$ gitea_cli.py list-repos --user stwhite
$ gitea_cli.py list-repos --user stwhite --page 2 --limit 50
"""
config = get_gitea_config(gitea_url, gitea_token)
try:
gitea = GiteaAPI(config['url'], config['token'])
repos = gitea.list_user_repositories(user, page=page, limit=limit)
if not repos:
click.echo(f"No repositories found for user: {user}")
return
click.echo(f"Repositories for {user}:")
for repo in repos:
visibility = "private" if repo['private'] else "public"
click.echo(f"- {repo['name']} ({visibility}): {repo['html_url']}")
except requests.exceptions.RequestException as e:
click.echo(f"Error listing repositories: {str(e)}")
sys.exit(1)
@cli.command()
@click.option('--user', required=True, help='Username to search repositories for')
@click.option('--query', help='Search term (searches name by default)')
@click.option('--private', type=bool, help='Filter by private status (true/false)')
@click.option('--search-in', default='name',
type=click.Choice(['name', 'description', 'both']),
help='Where to search (name/description/both)')
@click.option('--page', default=1, help='Page number (default: 1)')
@click.option('--limit', default=30, help='Items per page (default: 30, max: 50)')
@click.option('--gitea-url',
help='Gitea server URL (overrides GITEA_URL environment variable)')
@click.option('--gitea-token',
help='Gitea API token (overrides GITEA_TOKEN environment variable)')
def search_repos(user, query, private, search_in, page, limit, gitea_url, gitea_token):
"""Search repositories for a specific user with advanced filters.
Examples:
# Basic name search
$ gitea_cli.py search-repos --user stwhite --query paper
# Search descriptions
$ gitea_cli.py search-repos --user stwhite --query "research" --search-in description
# Filter private repos
$ gitea_cli.py search-repos --user stwhite --private true
# Combined search with pagination
$ gitea_cli.py search-repos --user stwhite --query project --search-in both --private false --page 2 --limit 50
"""
config = get_gitea_config(gitea_url, gitea_token)
try:
gitea = GiteaAPI(config['url'], config['token'])
repos = gitea.search_user_repositories(
user,
query=query,
private=private,
search_in=search_in,
page=page,
limit=limit
)
if not repos:
msg = f"No repositories found"
if query:
msg += f" matching '{query}'"
if private is not None:
msg += f" (private: {private})"
msg += f" for user: {user}"
click.echo(msg)
return
click.echo(f"Repositories matching '{query}' for {user}:")
for repo in repos:
visibility = "private" if repo['private'] else "public"
click.echo(f"- {repo['name']} ({visibility}): {repo['html_url']}")
except requests.exceptions.RequestException as e:
click.echo(f"Error searching repositories: {str(e)}")
sys.exit(1)
if __name__ == '__main__':
cli()