343 lines
13 KiB
Python
Executable File
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()
|