feat: implement config profile git mounts

- Add git_mounts column to config_profiles table (JSONB)
- Create GitMount Pydantic models with validation
- Add repo validation in create/update endpoints
- Update profile resolver to merge git mounts from includes
- Implement auto-clone, branch checkout, and glob expansion
- Add parallel processing for git mount resolution
- Create GitMountEditor frontend component
- Update TypeScript types and API clients
- Add CSS styles for git mount UI
- Frontend type check and build pass

Implements tasks 1.1-7.9 of config-profile-git-mounts spec
This commit is contained in:
Alex Blank
2026-05-26 22:27:04 +02:00
parent adda76a2ff
commit 4c11163bff
15 changed files with 1118 additions and 3 deletions
@@ -48,6 +48,7 @@ class ResolvedProfile:
env_vars: dict[str, str] = field(default_factory=dict)
runtime_hints: dict[str, Any] = field(default_factory=dict)
mounts: dict[str, ResolvedMount] = field(default_factory=dict)
git_mounts: list[dict[str, Any]] = field(default_factory=list)
files: dict[str, str] = field(default_factory=dict)
env_overrides: dict[str, str] = field(default_factory=dict)
hint_overrides: dict[str, str] = field(default_factory=dict)
@@ -168,6 +169,28 @@ def _merge_mounts(
return result
def _merge_git_mounts(
base: list[dict[str, Any]],
overlay: list[dict[str, Any]],
source_name: str,
) -> list[dict[str, Any]]:
"""Merge git mounts from included profiles.
Later mounts override earlier ones with the same repo_id + target_path combo.
"""
result = list(base)
# Build lookup by (repo_id, target_path)
seen = {(m["repo_id"], m["target_path"]): i for i, m in enumerate(result)}
for mount in overlay:
key = (mount["repo_id"], mount["target_path"])
if key in seen:
result[seen[key]] = dict(mount)
else:
seen[key] = len(result)
result.append(dict(mount))
return result
async def _resolve_profile_recursive(
session: AsyncSession,
profile_id: uuid.UUID,
@@ -244,6 +267,9 @@ async def _resolve_profile_recursive(
result.mount_overrides,
included.profile_name,
)
result.git_mounts = _merge_git_mounts(
result.git_mounts, included.git_mounts, included.profile_name
)
# Apply the profile's own settings (selected profile overrides includes)
result.env_vars = _merge_env_vars(
@@ -270,7 +296,11 @@ async def _resolve_profile_recursive(
result.mount_overrides,
profile.name,
)
result.git_mounts = _merge_git_mounts(
result.git_mounts,
profile.git_mounts or [],
profile.name,
)
return result
@@ -449,5 +479,6 @@ def resolved_profile_to_dict(resolved: ResolvedProfile) -> dict[str, Any]:
"files": resolved.file_overrides,
"mounts": resolved.mount_overrides,
},
"git_mounts": resolved.git_mounts,
"included_profiles": resolved.included_profiles,
}