From d7f8618b3852a5ed24b8c862d01791ba7daeb030 Mon Sep 17 00:00:00 2001 From: Slim B Date: Sat, 14 Feb 2026 10:19:41 +0300 Subject: [PATCH] Use inline :exclude in josh-proxy URL instead of stored filter files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The :+ stored filter syntax doesn't work in josh-proxy URLs. Inline :exclude[::p1,::p2] works directly — no files to generate or commit, no extra dependencies. Co-Authored-By: Claude Opus 4.6 --- bin/josh-sync | 22 ------------------- docs/guide.md | 19 +++++----------- lib/config.sh | 46 ++------------------------------------- schema/config-schema.json | 2 +- 4 files changed, 9 insertions(+), 80 deletions(-) diff --git a/bin/josh-sync b/bin/josh-sync index 7cd14c6..f3dbd3b 100755 --- a/bin/josh-sync +++ b/bin/josh-sync @@ -422,28 +422,6 @@ cmd_preflight() { warn ".gitea/workflows/josh-sync-reverse.yml not found (optional)" fi - # 4. Exclude filter files - local has_excludes - has_excludes=$(echo "$JOSH_SYNC_TARGETS" | jq '[.[] | select((.exclude // []) | length > 0)] | length') - - if [ "$has_excludes" -gt 0 ]; then - echo "" - echo "4. Exclude filter files" - - while IFS= read -r target_name; do - local filter_file=".josh-filter-${target_name}.josh" - if [ -f "$filter_file" ]; then - if git ls-files --error-unmatch "$filter_file" >/dev/null 2>&1; then - pass "${filter_file} exists and is tracked" - else - warn "${filter_file} exists but is NOT committed — run: git add ${filter_file}" - fi - else - fail "${filter_file} not generated — check parse_config" - fi - done < <(echo "$JOSH_SYNC_TARGETS" | jq -r '.[] | select((.exclude // []) | length > 0) | .name') - fi - # Summary echo "" echo "=============================" diff --git a/docs/guide.md b/docs/guide.md index 664327a..b3f0435 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -541,19 +541,13 @@ targets: ### How it works -When `exclude` is present, josh-sync generates a `.josh-filter-.josh` file at the monorepo root containing a [josh stored filter](https://josh-project.github.io/josh/reference/filters.html) with `:exclude` clauses: +When `exclude` is present, josh-sync appends an inline `:exclude` filter to the josh-proxy URL. For the example above, the josh filter becomes: ``` -:/services/billing:exclude[ - ::.monorepo/ - ::**/internal/ - ::*.secret -] +:/services/billing:exclude[::.monorepo/,::**/internal/,::*.secret] ``` -The file is referenced in the josh-proxy URL as `:+.josh-filter-` (flat naming — josh-proxy's parser treats `/` in `:+` paths as a filter separator, so subdirectory paths don't work). - -Josh-proxy reads this file from the monorepo and applies the filter at the transport layer. This means: +Josh-proxy applies this filter at the transport layer — no extra files to generate or commit. This means: - **Forward sync**: the filtered clone already excludes the files - **Reverse sync**: pushes through josh also respect the exclusion - **Reset**: the subrepo history never contains excluded files @@ -574,11 +568,10 @@ Josh uses `::` patterns inside `:exclude[...]`: ### Setup 1. Add `exclude` to the target in `.josh-sync.yml` -2. Run `josh-sync preflight` — this generates `.josh-filter-.josh` -3. Commit the generated file: `git add .josh-filter-*.josh && git commit` -4. Forward sync will now exclude the specified files +2. Run `josh-sync preflight` to verify the filter works +3. Forward sync will now exclude the specified files -If you change the `exclude` list, re-run `preflight` and commit the updated `.josh-filter-*.josh` file. +No extra files to generate or commit — the exclusion is embedded directly in the josh-proxy URL. ## Adding a New Target diff --git a/lib/config.sh b/lib/config.sh index fe22fdc..bdf8b95 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -7,45 +7,6 @@ # # Requires: lib/core.sh sourced first, yq and jq on PATH -# ─── Josh Filter File Generation ────────────────────────────────── -# Generates .josh-filter-.josh for targets with exclude patterns. -# These files must be committed to the monorepo — josh-proxy reads them -# from the repo at clone time via the :+ stored filter syntax. -# Files are at the repo root (flat naming) because josh-proxy's parser -# treats "/" in :+ paths as a filter separator. - -_generate_josh_filters() { - local has_excludes - has_excludes=$(echo "$JOSH_SYNC_TARGETS" | jq '[.[] | select((.exclude // []) | length > 0)] | length') - - if [ "$has_excludes" -eq 0 ]; then - return - fi - - local target_name subfolder exclude_patterns filter_content - while IFS= read -r target_name; do - subfolder=$(echo "$JOSH_SYNC_TARGETS" | jq -r --arg n "$target_name" \ - '.[] | select(.name == $n) | .subfolder') - exclude_patterns=$(echo "$JOSH_SYNC_TARGETS" | jq -r --arg n "$target_name" \ - '.[] | select(.name == $n) | .exclude | map(" ::" + .) | join("\n")') - - filter_content=":/${subfolder}:exclude[ -${exclude_patterns} -]" - - local filter_file=".josh-filter-${target_name}.josh" - local existing="" - if [ -f "$filter_file" ]; then - existing=$(cat "$filter_file") - fi - - if [ "$filter_content" != "$existing" ]; then - echo "$filter_content" > "$filter_file" - log "WARN" "Generated ${filter_file} — commit this file to the monorepo" - fi - done < <(echo "$JOSH_SYNC_TARGETS" | jq -r '.[] | select((.exclude // []) | length > 0) | .name') -} - # ─── Phase 1: Parse Config ───────────────────────────────────────── parse_config() { @@ -75,9 +36,9 @@ parse_config() { export JOSH_SYNC_TARGETS JOSH_SYNC_TARGETS=$(echo "$config_json" | jq '[.targets[] | . + # Auto-derive josh_filter from subfolder if not set - # When exclude patterns are present, use a stored josh filter (:+.josh-filter-) + # When exclude patterns are present, append inline :exclude[::p1,::p2,...] to the filter (if (.exclude // [] | length) > 0 then - {josh_filter: (":+.josh-filter-" + .name)} + {josh_filter: (":/" + .subfolder + ":exclude[" + (.exclude | map("::" + .) | join(",")) + "]")} elif (.josh_filter // "") == "" then {josh_filter: (":/" + .subfolder)} else {} end) + @@ -98,9 +59,6 @@ parse_config() { ) ]') - # Generate .josh-filter-*.josh for targets with exclude patterns - _generate_josh_filters - # Load .env credentials (if present, not required — CI sets these via secrets) if [ -f .env ]; then # shellcheck source=/dev/null diff --git a/schema/config-schema.json b/schema/config-schema.json index c86c19b..b3c9675 100644 --- a/schema/config-schema.json +++ b/schema/config-schema.json @@ -75,7 +75,7 @@ "type": "array", "items": { "type": "string" }, "default": [], - "description": "File/directory patterns to exclude from sync via josh :exclude filter. Josh pattern syntax: 'dir/' for directories, '*.ext' for globs, '**/dir/' for nested matches. Generates a .josh-filter-.josh file that must be committed." + "description": "File/directory patterns to exclude from sync via josh :exclude filter. Josh pattern syntax: 'dir/' for directories, '*.ext' for globs, '**/dir/' for nested matches. Patterns are embedded inline in the josh-proxy URL." } } }