Use inline :exclude in josh-proxy URL instead of stored filter files

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 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 10:19:41 +03:00
parent 5929585d6c
commit d7f8618b38
4 changed files with 9 additions and 80 deletions

View File

@@ -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 "============================="

View File

@@ -541,19 +541,13 @@ targets:
### How it works
When `exclude` is present, josh-sync generates a `.josh-filter-<target>.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-<target>` (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-<target>.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

View File

@@ -7,45 +7,6 @@
#
# Requires: lib/core.sh sourced first, yq and jq on PATH
# ─── Josh Filter File Generation ──────────────────────────────────
# Generates .josh-filter-<target>.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-<name>)
# 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

View File

@@ -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-<target>.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."
}
}
}