#!/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()