Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions app/configs/gitmastery_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Self, Type
from typing import Self, Type, Optional, Union

from app.configs.utils import read_config

Expand All @@ -12,13 +12,43 @@
class GitMasteryConfig:
@dataclass
class ExercisesSource:
username: str
repository: str
branch: str
# "remote" or "local"
type: str = "remote"
# remote fields (legacy uses username/repository/branch)
username: Optional[str] = None
repository: Optional[str] = None
branch: Optional[str] = "main"
# local field
path: Optional[str] = None

def to_url(self) -> str:
if self.type != "remote":
raise ValueError("to_url only valid for remote ExercisesSource")
return f"https://github.com/{self.username}/{self.repository}.git"

@classmethod
def from_raw(cls, raw: Union["GitMasteryConfig.ExercisesSource", dict, None]) -> "GitMasteryConfig.ExercisesSource":
# Pass-through if already the correct instance
if isinstance(raw, cls):
return raw
# Default remote
if raw is None:
return cls(type="remote", username="git-mastery", repository="exercises", branch="main")
if isinstance(raw, dict):
typ = raw.get("type")
# explicit local
if typ == "local":
return cls(type="local", path=raw.get("path"))
# fallthrough for None (legacy)/detected remote
return cls(
type="remote",
username=raw.get("username", "git-mastery"),
repository=raw.get("repository", "exercises"),
branch=raw.get("branch", "main"),
)
raise ValueError("Unsupported exercises_source shape")


progress_local: bool
progress_remote: bool
exercises_source: ExercisesSource
Expand All @@ -44,19 +74,17 @@ def read(cls: Type[Self], path: Path, cds: int) -> Self:
raw_config = read_config(path, GITMASTERY_CONFIG_NAME)

exercises_source_raw = raw_config.get("exercises_source", {})
exercises_source = GitMasteryConfig.ExercisesSource.from_raw(exercises_source_raw)

return cls(
path=path,
cds=cds,
progress_local=raw_config.get("progress_local", True),
progress_remote=raw_config.get("progress_remote", False),
exercises_source=GitMasteryConfig.ExercisesSource(
username=exercises_source_raw.get("username", "git-mastery"),
repository=exercises_source_raw.get("repository", "exercises"),
branch=exercises_source_raw.get("branch", "main"),
),
exercises_source=exercises_source,
)


GIT_MASTERY_EXERCISES_SOURCE = GitMasteryConfig.ExercisesSource(
username="git-mastery", repository="exercises", branch="main"
GIT_MASTERY_EXERCISES_SOURCE = GitMasteryConfig.ExercisesSource.from_raw(
{"type": "remote", "username": "git-mastery", "repository": "exercises", "branch": "main"}
)
34 changes: 23 additions & 11 deletions app/utils/gitmastery.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
TypeVar,
Union,
)
import shutil

from git import Repo

Expand Down Expand Up @@ -91,17 +92,28 @@ def __enter__(self) -> Self:
else:
exercises_source = GIT_MASTERY_EXERCISES_SOURCE

info(
f"Fetching exercise information from {exercises_source.to_url()} on branch {exercises_source.branch}"
)

self.__repo = Repo.clone_from(
exercises_source.to_url(),
self.__temp_dir.name,
depth=1,
branch=exercises_source.branch,
multi_options=["--filter=blob:none", "--sparse"],
)
if exercises_source.type == "local":
# copy local repo into temp dir for isolation
if exercises_source.path is None:
raise ValueError("Path is required for using local exercises source")
info(f"Using local exercises source at {exercises_source.path}")
src = Path(exercises_source.path).expanduser().resolve()
if not src.exists():
raise FileNotFoundError(f"Local exercises source not found: {src}")
shutil.copytree(src, self.__temp_dir.name, dirs_exist_ok=True, symlinks=False, copy_function=shutil.copy2)
self.__repo = Repo(self.__temp_dir.name)
else:
info(
f"Fetching exercise information from {exercises_source.to_url()} on branch {exercises_source.branch}"
)

self.__repo = Repo.clone_from(
exercises_source.to_url(),
self.__temp_dir.name,
depth=1,
branch=exercises_source.branch,
multi_options=["--filter=blob:none", "--sparse"],
)
return self

def __exit__(
Expand Down