feat: add repository rename orchestration CLI
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
6312db8700
commit
6b82215ae7
11 changed files with 1373 additions and 4 deletions
|
|
@ -111,6 +111,7 @@ app.include_router(recently_on_scope.router)
|
|||
app.include_router(consistency_sweep.router)
|
||||
app.include_router(repos.router)
|
||||
app.include_router(repository_renames.router)
|
||||
app.include_router(repository_renames.operation_router)
|
||||
app.include_router(topics.router)
|
||||
app.include_router(workstreams.router)
|
||||
app.include_router(workstreams.workplan_router)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from api.services.repository_rename import (
|
|||
create_operation,
|
||||
list_operations,
|
||||
load_operation,
|
||||
load_operation_by_id,
|
||||
rollback_preflight,
|
||||
verify_forge_identity,
|
||||
verify_operation,
|
||||
|
|
@ -38,6 +39,9 @@ from api.services.repository_rename import (
|
|||
|
||||
|
||||
router = APIRouter(prefix="/repos/{repo_id}", tags=["repository-renames"])
|
||||
operation_router = APIRouter(
|
||||
prefix="/repository-renames", tags=["repository-renames"]
|
||||
)
|
||||
|
||||
|
||||
def _raise(exc: RenameLifecycleError) -> None:
|
||||
|
|
@ -122,7 +126,22 @@ async def create_repository_rename_operation(
|
|||
gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
operation = await create_operation(session, gateway, repo_id, body)
|
||||
operation, no_op = await create_operation(session, gateway, repo_id, body)
|
||||
except RenameLifecycleError as exc:
|
||||
_raise(exc)
|
||||
return _operation_read(operation, no_op=no_op)
|
||||
|
||||
|
||||
@operation_router.get(
|
||||
"/operations/{operation_id}", response_model=RepositoryRenameOperationRead
|
||||
)
|
||||
async def get_repository_rename_operation_by_id(
|
||||
operation_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> dict[str, Any]:
|
||||
"""Resolve an operation journal without requiring its repository UUID."""
|
||||
try:
|
||||
operation = await load_operation_by_id(session, operation_id)
|
||||
except RenameLifecycleError as exc:
|
||||
_raise(exc)
|
||||
return _operation_read(operation)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class ForgeIdentityVerifyRequest(BaseModel):
|
|||
|
||||
|
||||
class RepositoryRenameOperationCreate(BaseModel):
|
||||
operation_id: uuid.UUID | None = None
|
||||
new_slug: str = Field(min_length=1, max_length=100, pattern=r"^[a-z0-9][a-z0-9-]*$")
|
||||
preflight_token: str
|
||||
confirmation: str
|
||||
|
|
|
|||
|
|
@ -695,7 +695,22 @@ async def create_operation(
|
|||
gateway: ForgeRepositoryGateway,
|
||||
repo_id: uuid.UUID,
|
||||
body: RepositoryRenameOperationCreate,
|
||||
) -> RepositoryRenameOperation:
|
||||
) -> tuple[RepositoryRenameOperation, bool]:
|
||||
if body.operation_id is not None:
|
||||
existing = await session.get(RepositoryRenameOperation, body.operation_id)
|
||||
if existing is not None:
|
||||
if (
|
||||
existing.repo_id != repo_id
|
||||
or existing.new_slug != body.new_slug
|
||||
or existing.actor != body.actor
|
||||
or body.confirmation
|
||||
!= confirmation_for(repo_id, existing.old_slug, existing.new_slug)
|
||||
):
|
||||
raise RenamePreconditionFailed(
|
||||
"Repository rename operation ID is already bound to another request"
|
||||
)
|
||||
return existing, True
|
||||
|
||||
token = _verify_preflight_token(body.preflight_token)
|
||||
if token.get("repo_id") != str(repo_id) or token.get("new_slug") != body.new_slug:
|
||||
raise RenamePreconditionFailed("Preflight token does not address this rename")
|
||||
|
|
@ -730,6 +745,7 @@ async def create_operation(
|
|||
forge = preflight["current"]["forge"]
|
||||
now = utcnow()
|
||||
operation = RepositoryRenameOperation(
|
||||
id=body.operation_id or uuid.uuid4(),
|
||||
repo_id=repo.id,
|
||||
forge_identity_id=identity.id,
|
||||
forge_identity_state="verified",
|
||||
|
|
@ -777,10 +793,33 @@ async def create_operation(
|
|||
await session.commit()
|
||||
except IntegrityError as exc:
|
||||
await session.rollback()
|
||||
if body.operation_id is not None:
|
||||
existing = await session.get(RepositoryRenameOperation, body.operation_id)
|
||||
if (
|
||||
existing is not None
|
||||
and existing.repo_id == repo_id
|
||||
and existing.new_slug == body.new_slug
|
||||
and existing.actor == body.actor
|
||||
and body.confirmation
|
||||
== confirmation_for(repo_id, existing.old_slug, existing.new_slug)
|
||||
):
|
||||
return existing, True
|
||||
raise RenamePreconditionFailed(
|
||||
"A conflicting repository rename operation was created"
|
||||
) from exc
|
||||
await session.refresh(operation)
|
||||
return operation, False
|
||||
|
||||
|
||||
async def load_operation_by_id(
|
||||
session: AsyncSession,
|
||||
operation_id: uuid.UUID,
|
||||
) -> RepositoryRenameOperation:
|
||||
operation = await session.get(RepositoryRenameOperation, operation_id)
|
||||
if operation is None:
|
||||
raise RenameNotFound(
|
||||
f"Repository rename operation {operation_id} was not found"
|
||||
)
|
||||
return operation
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue