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 }}
run: |
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:
push:
branches:
- main
- '**'
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:
needs: detect-version-change
if: needs.detect-version-change.outputs.version_changed == 'true' && github.ref == 'refs/heads/main'
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 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
- name: Release
uses: https://git.0xmax42.io/actions/auto-changelog-release-action@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 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."
token: ${{ secrets.RELEASE_PUBLISH_TOKEN }}

1
.gitignore vendored
View File

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

View File

@@ -2,7 +2,35 @@
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

View File

@@ -1 +1 @@
0.1.0
0.4.0

View File

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

View File

@@ -28,7 +28,7 @@ def archive_repo(source: Path, target_dir: Path, dry_run: bool) -> Path:
return archive_path
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 tarfile.open(fileobj=compressor, mode="w|") as tar:
tar.add(source, arcname=source.name)
@@ -70,6 +70,9 @@ def run(
if category and cat.config.name not in category:
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
for repo in cat.repos:

View File

@@ -2,6 +2,8 @@ from typer import Typer, Option
from rich.console import Console
from rich.table import Table
import shutil
import os
from datetime import datetime
from repocat.config import load_config
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),
):
"""
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.add_column("Kategorie")
@@ -33,6 +44,7 @@ def clean_volatile(
deleted = 0
skipped = 0
snapshotted_categories = set()
for cat in catalog.categories:
if not cat.config.volatile:
@@ -40,6 +52,48 @@ def clean_volatile(
if category and cat.config.name not in category:
continue
# If snapshots are enabled, we handle the whole category at once
if use_snapshots:
if not cat.path.exists():
continue
if dry_run:
action = f"[yellow]Würde verschieben nach .snapshots/{timestamp}[/]"
else:
try:
# Create snapshot dir only if we actually have something to move
if not current_snapshot_dir.exists():
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:
action = f"[red]Fehler beim Verschieben: {e}[/]"
# 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
@@ -56,7 +110,20 @@ def clean_volatile(
table.add_row(cat.config.name, repo.name, f"{size_mb:.1f}", action)
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()

View File

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