Compare commits

22 Commits

Author SHA1 Message Date
66c1c5c55f chore(changelog): update changelog for v0.4.0
All checks were successful
Build and Publish nightly package / build-and-publish (release) Successful in 33s
2025-11-27 18:58:29 +00:00
89c4a4a073 chore(version): bump version to 0.4.0
All checks were successful
Auto Changelog & (Release) / release (push) Successful in 8s
Build and Publish nightly package / build-and-publish (push) Successful in 33s
2025-11-27 19:58:18 +01:00
72cb0cc20f chore(changelog): update unreleased changelog 2025-11-27 18:57:52 +00:00
39f01b266f fix(archive): skip categories with zero or unset days config
All checks were successful
Auto Changelog & (Release) / release (push) Successful in 6s
Build and Publish nightly package / build-and-publish (push) Successful in 31s
2025-11-27 19:57:43 +01:00
31c3f7d438 feat(archive): enable multithreaded zstd compression 2025-11-27 19:57:43 +01:00
4ea3b28081 chore(changelog): update changelog for v0.3.0
All checks were successful
Build and Publish nightly package / build-and-publish (release) Successful in 33s
2025-11-27 17:23:12 +00:00
702aa2a75b chore(version): bump version to 0.3.0
Some checks failed
Auto Changelog & (Release) / release (push) Successful in 7s
Build and Publish nightly package / build-and-publish (push) Has been cancelled
2025-11-27 18:23:02 +01:00
529cd19ec6 chore(changelog): update unreleased changelog 2025-11-27 17:22:45 +00:00
fdff738fa7 chore(changelog): simplify release workflow using composite action
Some checks failed
Build and Publish nightly package / build-and-publish (push) Has been cancelled
Auto Changelog & (Release) / release (push) Successful in 5s
2025-11-27 18:22:34 +01:00
7f629fd752 feat(clean): add snapshot support for volatile repo cleanup
Some checks failed
Auto Changelog & Release / detect-version-change (push) Successful in 3s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Failing after 6s
Build and Publish nightly package / build-and-publish (push) Successful in 33s
2025-11-27 18:21:20 +01:00
801c8e24a1 docs: fix typo in German documentation comment
Some checks failed
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Failing after 7s
Build and Publish nightly package / build-and-publish (push) Successful in 37s
2025-11-27 18:14:11 +01:00
f63ef85dd7 chore(changelog): update unreleased changelog 2025-05-11 13:35:57 +00:00
c2013fc205 chore(scripts): update file mode to make script executable
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 8s
Build and Publish nightly package / build-and-publish (push) Successful in 19s
- Changes file mode from 644 to 755 to allow script execution
- Ensures proper permissions for usage in relevant environments
2025-05-11 15:35:41 +02:00
e5015ec2f3 chore(changelog): update unreleased changelog 2025-05-11 13:35:06 +00:00
1163744af9 chore(scripts): rename cleanup script for broader usage
Some checks failed
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 8s
Build and Publish nightly package / build-and-publish (push) Failing after 17s
- Renames the cleanup_dev_versions script to cleanup_versions
2025-05-11 15:34:51 +02:00
e3c703aa32 chore(changelog): update unreleased changelog 2025-05-11 13:33:50 +00:00
aff319cc5f feat(workflows): add cleanup step for old dev versions
Some checks failed
Auto Changelog & Release / detect-version-change (push) Successful in 5s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 8s
Build and Publish nightly package / build-and-publish (push) Failing after 16s
- Introduces a step to remove old development versions using a script
- Helps maintain a cleaner environment during nightly builds
2025-05-11 15:33:35 +02:00
fc8b885214 feat(scripts): add script to clean old PyPI dev versions
- Introduces a Bash script to delete outdated PyPI dev versions
  from the Gitea package registry while keeping the latest version
- Uses environment variables for authentication and repository details
2025-05-11 15:33:35 +02:00
7067f0872b chore(gitignore): update ignored files for environment and build
- Add `.env` files to the ignore list to exclude environment configs
- Retain `dist/` directory in the ignore list for build artifacts
2025-05-11 15:33:35 +02:00
d5c2873d39 chore(changelog): update changelog for v0.2.0
All checks were successful
Build and Publish nightly package / build-and-publish (release) Successful in 18s
2025-05-11 12:57:51 +00:00
c005d1d38c chore(version): bump version to 0.2.0
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 5s
Auto Changelog & Release / changelog-only (push) Has been skipped
Build and Publish nightly package / build-and-publish (push) Successful in 17s
Auto Changelog & Release / release (push) Successful in 9s
2025-05-11 14:57:32 +02:00
5c0db1c656 chore(version): bump to 0.2.0
- Updates the project version to 0.2.0 in preparation for a new release
2025-05-11 14:57:24 +02:00
10 changed files with 174 additions and 225 deletions

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
# cleanup_dev_versions.sh - Delete old PyPI dev versions from Gitea package registry
# Required environment variables
USERNAME="${TWINE_USERNAME}"
TOKEN="${TWINE_PASSWORD}"
REPO="${GITHUB_REPOSITORY}" # e.g., maxp/repocat
API_BASE="${GITHUB_API_URL%/}" # Strip trailing slash if present
OWNER="${REPO%%/*}"
PACKAGE_NAME="${REPO##*/}"
API_URL="${API_BASE}/packages/${OWNER}/pypi/${PACKAGE_NAME}"
# Fetch the list of versions
response=$(curl -s -u "$USERNAME:$TOKEN" "$API_URL")
# Extract all .dev versions, sort by creation time
mapfile -t versions_to_delete < <(echo "$response" | jq -r '
map(select(.version | test("\\.dev"))) |
sort_by(.created_at) |
.[0:-1][] |
.version')
# Determine latest version to keep
latest_version=$(echo "$response" | jq -r '
map(select(.version | test("\\.dev"))) |
sort_by(.created_at) |
last.version')
if [[ -z "$latest_version" || ${#versions_to_delete[@]} -eq 0 ]]; then
echo "No old .dev versions to delete."
exit 0
fi
echo "Keeping latest .dev version: $latest_version"
# Delete old .dev versions
for version in "${versions_to_delete[@]}"; do
echo "Deleting old .dev version: $version"
curl -s -X DELETE -u "$USERNAME:$TOKEN" "$API_URL/$version"
done
echo "Cleanup complete."

View File

@@ -53,3 +53,10 @@ jobs:
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
run: | run: |
poetry run twine upload --repository-url ${{ secrets.TWINE_URL }} dist/* poetry run twine upload --repository-url ${{ secrets.TWINE_URL }} dist/*
- name: Cleanup old dev versions
run: |
.gitea/scripts/cleanup_versions.sh '\.dev'
env:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}

View File

@@ -1,222 +1,18 @@
name: Auto Changelog & Release name: Auto Changelog & (Release)
on: on:
push: push:
branches: branches:
- main - main
- '**'
jobs: jobs:
detect-version-change:
runs-on: ubuntu-latest
outputs:
version_changed: ${{ steps.set.outputs.version_changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if VERSION file changed
if: github.ref == 'refs/heads/main'
run: |
echo "🔍 Vergleich mit github.event.before:"
echo "Before: ${{ github.event.before }}"
echo "After: ${{ github.sha }}"
echo "📄 Changed files between before and after:"
git diff --name-only ${{ github.event.before }} ${{ github.sha }} || echo "(diff failed)"
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q '^VERSION$'; then
echo "✅ VERSION file was changed"
echo "VERSION_CHANGED=true" >> $GITHUB_ENV
else
echo "ℹ️ VERSION file not changed"
echo "VERSION_CHANGED=false" >> $GITHUB_ENV
fi
- name: Set output (always)
id: set
run: |
echo "version_changed=${VERSION_CHANGED:-false}" >> $GITHUB_OUTPUT
changelog-only:
needs: detect-version-change
if: github.ref != 'refs/heads/main' || needs.detect-version-change.outputs.version_changed == 'false'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set Git Author
run: |
git config user.name "$CI_COMMIT_AUTHOR_NAME"
git config user.email "$CI_COMMIT_AUTHOR_EMAIL"
- name: Read CLIFF_VERSION from cliff.toml
id: cliff_version
run: |
echo "version=$(awk -F '=' '/^# CLIFF_VERSION=/ { gsub(/[" ]/, "", $2); print $2 }' cliff.toml)" >> $GITHUB_OUTPUT
- name: Restore git-cliff cache
id: restore-cliff
uses: https://git.0xmax42.io/actions/cache@v1
with:
key: cargo-cliff-${{ steps.cliff_version.outputs.version }}
paths: |
/root/.cargo/bin
- name: Install git-cliff
if: steps.restore-cliff.outputs.cache-hit != 'true'
run: |
cargo install git-cliff --version "${{ steps.cliff_version.outputs.version }}" --features gitea
- name: Generate unreleased changelog (if file exists or on main)
run: |
if [[ -f CHANGELOG.md || "${GITHUB_REF##refs/heads/}" == "main" ]]; then
echo "Generating CHANGELOG.md..."
git-cliff -c cliff.toml -o CHANGELOG.md
else
echo "CHANGELOG.md does not exist and this is not 'main'. Skipping generation."
fi
- name: Commit updated CHANGELOG
run: |
git add CHANGELOG.md
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore(changelog): update unreleased changelog"
git push origin "${GITHUB_REF##refs/heads/}"
fi
release: release:
needs: detect-version-change
if: needs.detect-version-change.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Release
- name: Set Git Author uses: https://git.0xmax42.io/actions/auto-changelog-release-action@v1
run: |
git config user.name "$CI_COMMIT_AUTHOR_NAME"
git config user.email "$CI_COMMIT_AUTHOR_EMAIL"
- name: Read VERSION
id: version
run: echo "value=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Read CLIFF_VERSION from cliff.toml
id: cliff_version
run: |
echo "version=$(awk -F '=' '/^# CLIFF_VERSION=/ { gsub(/[" ]/, "", $2); print $2 }' cliff.toml)" >> $GITHUB_OUTPUT
- name: Restore git-cliff cache
id: restore-cliff
uses: https://git.0xmax42.io/actions/cache@v1
with: with:
key: cargo-cliff-${{ steps.cliff_version.outputs.version }} token: ${{ secrets.RELEASE_PUBLISH_TOKEN }}
paths: |
/root/.cargo/bin
- name: Install git-cliff
if: steps.restore-cliff.outputs.cache-hit != 'true'
run: |
cargo install git-cliff --version "${{ steps.cliff_version.outputs.version }}" --features gitea
- name: Generate changelog for release and tag
id: generate-changelog
run: |
VERSION=${{ steps.version.outputs.value }}
git-cliff -c cliff.toml -t "v$VERSION" -o CHANGELOG.md
BODY=$(mktemp)
ESCAPED_VERSION=$(echo "$VERSION" | sed 's/\./\\./g')
awk -v ver="$ESCAPED_VERSION" '
$0 ~ "^## \\[" ver "\\]" {
print_flag=1
line = $0
sub(/^## /, "", line)
sub(/\\s*\\(.*\\)/, "", line) # entfernt z. B. "(...)" oder "(*)"
print line
next
}
$0 ~ "^## \\[" && $0 !~ "^## \\[" ver "\\]" {
print_flag=0
}
print_flag
' CHANGELOG.md > "$BODY"
echo "changelog_body_path=$BODY" >> $GITHUB_OUTPUT
- name: Commit updated CHANGELOG
run: |
git add CHANGELOG.md
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore(changelog): update changelog for v${{ steps.version.outputs.value }}"
git push origin main
fi
- name: Create Git tag (if not exists)
run: |
VERSION=${{ steps.version.outputs.value }}
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "Tag v$VERSION already exists, skipping tag creation."
else
git tag -a "v$VERSION" -F "${{ steps.generate-changelog.outputs.changelog_body_path }}" --cleanup=verbatim
git push origin "v$VERSION"
fi
- name: Create Gitea release
env:
RELEASE_PUBLISH_TOKEN: ${{ secrets.RELEASE_PUBLISH_TOKEN }}
run: |
VERSION=${{ steps.version.outputs.value }}
BODY_FILE="${{ steps.generate-changelog.outputs.changelog_body_path }}"
OWNER=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f1)
REPO=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2)
# Token-Auswahl
TOKEN="${RELEASE_PUBLISH_TOKEN:-$ACTIONS_RUNTIME_TOKEN}"
if [[ -z "${RELEASE_PUBLISH_TOKEN:-}" ]]; then
echo "::warning title=Limited Release Propagation::"
echo "RELEASE_PUBLISH_TOKEN is not set. Using ACTIONS_RUNTIME_TOKEN instead."
echo "⚠️ Release events may not trigger other workflows if created with the runtime token."
echo
fi
# Prüfe, ob der Release schon existiert
if curl -sf "$GITHUB_API_URL/repos/$OWNER/$REPO/releases/tags/v$VERSION" \
-H "Authorization: token $TOKEN" > /dev/null; then
echo "🔁 Release for tag v$VERSION already exists, skipping."
exit 0
fi
echo "🚀 Creating Gitea release for v$VERSION"
# Release-Beschreibung vorbereiten
RELEASE_BODY=$(tail -n +2 "$BODY_FILE" | jq -Rs .)
curl -X POST "$GITHUB_API_URL/repos/$OWNER/$REPO/releases" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"tag_name": "v$VERSION",
"target_commitish": "main",
"name": "Release v$VERSION",
"body": $RELEASE_BODY,
"draft": false,
"prerelease": false
}
EOF
echo "✅ Release for tag $VERSION created successfully."

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*__pycache__* *__pycache__*
dist/ dist/
**/.env

View File

@@ -2,7 +2,35 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [unreleased] ## [0.4.0](https://git.0xmax42.io/maxp/repoCat/compare/v0.3.0..v0.4.0) - 2025-11-27
### 🚀 Features
- *(archive)* Enable multithreaded zstd compression - ([31c3f7d](https://git.0xmax42.io/maxp/repoCat/commit/31c3f7d43880210f8056e4484b441e5f30500184))
### 🐛 Bug Fixes
- *(archive)* Skip categories with zero or unset days config - ([39f01b2](https://git.0xmax42.io/maxp/repoCat/commit/39f01b266f9d58509a6fb69fcd0734a85b2fd0ff))
## [0.3.0](https://git.0xmax42.io/maxp/repoCat/compare/v0.2.0..v0.3.0) - 2025-11-27
### 🚀 Features
- *(clean)* Add snapshot support for volatile repo cleanup - ([7f629fd](https://git.0xmax42.io/maxp/repoCat/commit/7f629fd752d7301c6c84d8227918853dd0538ff5))
- *(workflows)* Add cleanup step for old dev versions - ([aff319c](https://git.0xmax42.io/maxp/repoCat/commit/aff319cc5f5c8f954367a6a6b89c84ece6b19f4e))
- *(scripts)* Add script to clean old PyPI dev versions - ([fc8b885](https://git.0xmax42.io/maxp/repoCat/commit/fc8b885214b1f5b1d9dcff20612a583aca89f6f9))
### 📚 Documentation
- Fix typo in German documentation comment - ([801c8e2](https://git.0xmax42.io/maxp/repoCat/commit/801c8e24a139f36f714bada00e5308efb848d4f4))
### ⚙️ Miscellaneous Tasks
- *(scripts)* Update file mode to make script executable - ([c2013fc](https://git.0xmax42.io/maxp/repoCat/commit/c2013fc20538a377fc5de45bd384b6ac438fb61c))
- *(scripts)* Rename cleanup script for broader usage - ([1163744](https://git.0xmax42.io/maxp/repoCat/commit/1163744af9a3d908d6e7dbb716818bd67ec76a63))
- *(gitignore)* Update ignored files for environment and build - ([7067f08](https://git.0xmax42.io/maxp/repoCat/commit/7067f0872bdd8b456d52b3f9dab4834fd0798977))
## [0.2.0](https://git.0xmax42.io/maxp/repoCat/compare/v0.1.0..v0.2.0) - 2025-05-11
### 🚀 Features ### 🚀 Features

View File

@@ -1 +1 @@
0.1.0 0.4.0

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "repocat" name = "repocat"
version = "0.1.0" version = "0.4.0"
description = "" description = ""
authors = ["Max P. <Mail@MPassarello.de>"] authors = ["Max P. <Mail@MPassarello.de>"]
readme = "README.md" readme = "README.md"

View File

@@ -28,7 +28,7 @@ def archive_repo(source: Path, target_dir: Path, dry_run: bool) -> Path:
return archive_path return archive_path
with open(archive_path, "wb") as f: with open(archive_path, "wb") as f:
cctx = zstandard.ZstdCompressor(level=20) cctx = zstandard.ZstdCompressor(level=20, threads=-1)
with cctx.stream_writer(f) as compressor: with cctx.stream_writer(f) as compressor:
with tarfile.open(fileobj=compressor, mode="w|") as tar: with tarfile.open(fileobj=compressor, mode="w|") as tar:
tar.add(source, arcname=source.name) tar.add(source, arcname=source.name)
@@ -69,6 +69,9 @@ def run(
continue continue
if category and cat.config.name not in category: if category and cat.config.name not in category:
continue continue
if cat.config.days == 0 or cat.config.days is None:
continue
limit_days = cat.config.days if cat.config.days is not None else older_than limit_days = cat.config.days if cat.config.days is not None else older_than

View File

@@ -2,6 +2,8 @@ from typer import Typer, Option
from rich.console import Console from rich.console import Console
from rich.table import Table from rich.table import Table
import shutil import shutil
import os
from datetime import datetime
from repocat.config import load_config from repocat.config import load_config
from repocat.models.catalog import RepoCatalogState from repocat.models.catalog import RepoCatalogState
@@ -21,9 +23,18 @@ def clean_volatile(
category: list[str] = Option(None, "--category", "-c", help="Nur bestimmte Kategorien reinigen", autocompletion=complete_categories), category: list[str] = Option(None, "--category", "-c", help="Nur bestimmte Kategorien reinigen", autocompletion=complete_categories),
): ):
""" """
Löscht **bedingungslos** alle Repositories aus `volatile`-Kategorien (z.\u200bB. `scratches`). Löscht **bedingungslos** alle Repositories aus `volatile`-Kategorien (z.B. `scratches`).
""" """
catalog = RepoCatalogState.from_config(load_config()) config = load_config()
catalog = RepoCatalogState.from_config(config)
# Snapshot configuration
use_snapshots = config.defaults.snapshots if config.defaults else False
snapshot_count = config.defaults.snapshot_count if config.defaults else 5
snapshot_root = config.base_dir / ".snapshots"
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
current_snapshot_dir = snapshot_root / timestamp
table = Table(title="Clean – Volatile (Zerstörung)") table = Table(title="Clean – Volatile (Zerstörung)")
table.add_column("Kategorie") table.add_column("Kategorie")
@@ -33,6 +44,7 @@ def clean_volatile(
deleted = 0 deleted = 0
skipped = 0 skipped = 0
snapshotted_categories = set()
for cat in catalog.categories: for cat in catalog.categories:
if not cat.config.volatile: if not cat.config.volatile:
@@ -40,23 +52,78 @@ def clean_volatile(
if category and cat.config.name not in category: if category and cat.config.name not in category:
continue continue
for repo in cat.repos: # If snapshots are enabled, we handle the whole category at once
size_mb = repo.size_mb if use_snapshots:
if not cat.path.exists():
continue
if dry_run: if dry_run:
action = "[yellow]Würde löschen[/]" action = f"[yellow]Würde verschieben nach .snapshots/{timestamp}[/]"
else: else:
try: try:
shutil.rmtree(repo.path) # Create snapshot dir only if we actually have something to move
action = "[red]Gelöscht[/]" if not current_snapshot_dir.exists():
deleted += 1 current_snapshot_dir.mkdir(parents=True, exist_ok=True)
target_dir = current_snapshot_dir / cat.config.subdir
# Ensure parent of target exists (if subdir is nested)
target_dir.parent.mkdir(parents=True, exist_ok=True)
# Move the whole category folder
# Note: shutil.move(src, dst) where dst does not exist moves src to dst.
# If dst exists, it moves src inside dst.
# We want to rename cat.path to target_dir.
shutil.move(str(cat.path), str(target_dir))
# Recreate the empty category folder
cat.path.mkdir(parents=True, exist_ok=True)
action = f"[green]Verschoben nach .snapshots/{timestamp}[/]"
snapshotted_categories.add(cat.config.name)
except Exception as e: except Exception as e:
action = f"[red]Fehler: {e}[/]" action = f"[red]Fehler beim Verschieben: {e}[/]"
table.add_row(cat.config.name, repo.name, f"{size_mb:.1f}", action) # Add rows for all repos in this category to show what happened to them
if cat.repos:
for repo in cat.repos:
table.add_row(cat.config.name, repo.name, f"{repo.size_mb:.1f}", action)
deleted += 1 # Count as "processed/removed from volatile"
else:
# If no repos but folder existed (and maybe had other files), show a generic row
table.add_row(cat.config.name, "(Alle Dateien)", "-", action)
else:
# Old behavior: Delete individual repos
for repo in cat.repos:
size_mb = repo.size_mb
if dry_run:
action = "[yellow]Würde löschen[/]"
else:
try:
shutil.rmtree(repo.path)
action = "[red]Gelöscht[/]"
deleted += 1
except Exception as e:
action = f"[red]Fehler: {e}[/]"
table.add_row(cat.config.name, repo.name, f"{size_mb:.1f}", action)
console.print(table) console.print(table)
console.print(f"\n[green]✓ Volatile-Clean abgeschlossen[/] – {deleted} gelöscht, {skipped} übersprungen")
# Prune old snapshots
if use_snapshots and not dry_run and snapshot_root.exists():
try:
snapshots = sorted([p for p in snapshot_root.iterdir() if p.is_dir()], key=lambda p: p.name)
if len(snapshots) > snapshot_count:
to_delete = snapshots[:-snapshot_count]
for snap in to_delete:
shutil.rmtree(snap)
console.print(f"[dim]Alte Snapshots bereinigt: {len(to_delete)} gelöscht[/]")
except Exception as e:
console.print(f"[red]Fehler beim Bereinigen der Snapshots: {e}[/]")
console.print(f"\n[green]✓ Volatile-Clean abgeschlossen[/] – {deleted} Repositories verarbeitet")
@app.command() @app.command()

View File

@@ -13,6 +13,8 @@ class RepoCategory(BaseModel):
class RepoCatDefaults(BaseModel): class RepoCatDefaults(BaseModel):
dry_run: bool = True dry_run: bool = True
auto_create_directories: bool = True auto_create_directories: bool = True
snapshots: bool = False
snapshot_count: int = 5
class RepoCatConfig(BaseModel): class RepoCatConfig(BaseModel):
base_dir: Path = Field(default=Path("~/Repositories").expanduser()) base_dir: Path = Field(default=Path("~/Repositories").expanduser())