mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
A Repo is a Collection (kind='repo') plus a `repository` row, kept up to date by a recurring Download handled by the new `git` downloader. * Full history by default (all branches and tags, no --prune; a force push saves the old HEAD under refs/wrolpi/backup/); snapshot mode (--depth 1) and a pinned branch are optional. `/tree/<branch>` URLs select a branch. * git runs HTTPS-only, never prompts, never runs hooks, and checks out symlinks as plain files. Upstream disappearing defers the download with git's error; the local copy is kept. * Repos are cloned to `repos_destination` (repos/%(repo_tag)s/%(repo_name)s) and the `repos` directory is always ignored, so repo files are never indexed. * repos.yaml is the source of truth (import, dump, backup restore). * /api/repos: list, create, get, edit, delete (files kept unless delete_files), update now, and a per-directory file tree. Tagging uses the collections tag endpoint. Also: * run_command/process_runner can merge stderr into stdout (git writes progress to stderr). * move_collection deletes an old directory that only holds empty directories (the move left an emptied .git behind). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.6 KiB
Python
97 lines
3.6 KiB
Python
from http import HTTPStatus
|
|
|
|
from sanic import Request, Blueprint, response
|
|
from sanic_ext.extensions.openapi import openapi
|
|
|
|
from wrolpi.api_utils import json_response
|
|
from wrolpi.common import wrol_mode_check, logger
|
|
from wrolpi.downloader import download_manager
|
|
from wrolpi.errors import ValidationError
|
|
from . import lib, schema
|
|
from .models import Repository
|
|
|
|
repos_bp = Blueprint('Repos', '/api/repos')
|
|
|
|
logger = logger.getChild(__name__)
|
|
|
|
|
|
@repos_bp.get('/')
|
|
@openapi.definition(summary='List all git Repos')
|
|
async def get_repos(request: Request):
|
|
repos = lib.get_repositories(request.ctx.session)
|
|
return json_response(dict(repos=repos))
|
|
|
|
|
|
@repos_bp.post('/')
|
|
@openapi.definition(
|
|
summary='Add a git Repo; it will be cloned, then kept up to date',
|
|
body=schema.RepoCreateRequest,
|
|
validate=True,
|
|
)
|
|
@wrol_mode_check
|
|
async def post_repo(request: Request, body: schema.RepoCreateRequest):
|
|
session = request.ctx.session
|
|
repo = lib.create_repository(
|
|
session,
|
|
body.url,
|
|
tag_name=body.tag_name or None,
|
|
frequency=body.frequency or lib.DEFAULT_REPO_FREQUENCY,
|
|
mode=body.mode or 'full',
|
|
branch=body.branch or None,
|
|
name=body.name or None,
|
|
description=body.description or None,
|
|
)
|
|
repo = Repository.find_by_id(session, repo.id, options=Repository.json_options())
|
|
return json_response(dict(repo=repo.__json__()), status=HTTPStatus.CREATED)
|
|
|
|
|
|
@repos_bp.get('/<repo_id:int>')
|
|
@openapi.definition(summary='Get a git Repo')
|
|
async def get_repo(request: Request, repo_id: int):
|
|
repo = Repository.find_by_id(request.ctx.session, repo_id, options=Repository.json_options())
|
|
return json_response(dict(repo=repo.__json__()))
|
|
|
|
|
|
@repos_bp.put('/<repo_id:int>')
|
|
@openapi.definition(
|
|
summary="Change a git Repo's settings",
|
|
body=schema.RepoUpdateRequest,
|
|
validate=True,
|
|
)
|
|
@wrol_mode_check
|
|
async def put_repo(request: Request, repo_id: int, body: schema.RepoUpdateRequest):
|
|
session = request.ctx.session
|
|
lib.update_repository(session, repo_id, description=body.description, frequency=body.frequency,
|
|
mode=body.mode, branch=body.branch)
|
|
repo = Repository.find_by_id(session, repo_id, options=Repository.json_options())
|
|
return json_response(dict(repo=repo.__json__()))
|
|
|
|
|
|
@repos_bp.delete('/<repo_id:int>')
|
|
@openapi.definition(summary='Delete a git Repo. Its files are kept unless `delete_files=true`.')
|
|
@wrol_mode_check
|
|
async def delete_repo(request: Request, repo_id: int):
|
|
delete_files = request.args.get('delete_files', '').lower() in ('1', 'true', 'yes')
|
|
await lib.delete_repository(request.ctx.session, repo_id, delete_files=delete_files)
|
|
return response.empty()
|
|
|
|
|
|
@repos_bp.post('/<repo_id:int>/update')
|
|
@openapi.definition(summary='Update a git Repo now')
|
|
@wrol_mode_check
|
|
async def post_update_repo(request: Request, repo_id: int):
|
|
session = request.ctx.session
|
|
repo = Repository.find_by_id(session, repo_id, options=Repository.json_options())
|
|
if not (download := repo.download):
|
|
raise ValidationError('Repo has no download')
|
|
download_manager.restart_download(session, download.id)
|
|
return response.empty(HTTPStatus.NO_CONTENT)
|
|
|
|
|
|
@repos_bp.get('/<repo_id:int>/tree')
|
|
@openapi.definition(summary="List a directory of a git Repo's files")
|
|
@openapi.parameter('path', str, 'query', description='Directory in the repo, relative to its root', required=False)
|
|
async def get_repo_tree(request: Request, repo_id: int):
|
|
repo = Repository.find_by_id(request.ctx.session, repo_id)
|
|
tree = lib.list_repo_tree(repo, request.args.get('path', ''))
|
|
return json_response(tree)
|