#!/usr/bin/env bash
# An exclusion that cannot be compiled is refused when it arrives from
# another machine too, not only when this machine declares it. Applying
# it would capture — and could publish — exactly the files the broken
# rule was written to keep out.
require_cmd git
export MISE_HISTORY_NOTIFY=false
origin="$PWD/incoming-origin.git"
git init -q --bare -b main "$origin"
mkdir -p ~/.codex/sessions
echo 'model = "x"' >~/.codex/config.toml
echo 'a transcript' >~/.codex/sessions/session.json
cat >"$MISE_CONFIG_DIR/config.toml" <<'TOML'
[dotfiles]
"~/.codex" = { mode = "track" }
"~/.config/mise/config.toml" = { mode = "track" }

[history]
exclude = ["sessions/**"]
TOML
assert_succeed 'mise dot save'
assert_succeed "mise dot origin set file://$origin --sync manual --yes"
assert_succeed 'mise dot sync'
assert_fail "git --git-dir=$origin cat-file -e main:home/.codex/sessions/session.json"

# another machine, enrolled from the same origin
second="$(dirname "$HOME")/incoming-machine"
mkdir -p "$second"
b() {
  (
    cd "$second" || exit
    env HOME="$second" XDG_CONFIG_HOME="$second/.config" MISE_CONFIG_DIR="$second/.config/mise" \
      MISE_STATE_DIR="$second/.local/state/mise" MISE_DATA_DIR="$second/.local/share/mise" \
      MISE_CACHE_DIR="$second/.cache/mise" MISE_TRUSTED_CONFIG_PATHS="$second" \
      MISE_HISTORY_NOTIFY=false mise "$@"
  )
}
export second
export -f b
assert_succeed "b bootstrap --adopt file://$origin --yes"
assert_succeed "b dot pull --yes"
assert "cat $second/.codex/config.toml" 'model = "x"'

# an older client publishes a rule this mise cannot compile: committed
# straight into the repository, the way a machine without the refusal
# would have written it
git clone -q "$origin" "$PWD/older-client"
cat >"$PWD/older-client/config/config.toml" <<'TOML'
[dotfiles]
"~/.codex" = { mode = "track" }
"~/.config/mise/config.toml" = { mode = "track" }

[history]
exclude = ["["]
TOML
git -C "$PWD/older-client" add config/config.toml
git -C "$PWD/older-client" -c user.name=test -c user.email=test@example.com commit -qm 'unusable exclusion'
git -C "$PWD/older-client" push -q origin main

# the receiving machine refuses the incoming configuration rather than
# writing it, and says which rule it could not apply
assert_succeed 'b dot sync --fetch-only'
assert_fail 'b dot pull --yes' 'conflict'
b dot status >incoming-status.log 2>&1 || true
assert_contains 'cat incoming-status.log' 'incoming configuration is invalid'
assert_contains 'cat incoming-status.log' 'cannot be applied'
# the local configuration is untouched, so the exclusion that does work
# is still the one in force
assert_contains "cat $second/.config/mise/config.toml" 'sessions/**'
assert_not_contains "cat $second/.config/mise/config.toml" 'exclude = ["["]'
# and the rule that does work is still the one applied: a save runs,
# and the transcript the broken rule would have admitted stays out
mkdir -p "$second/.codex/sessions"
echo 'newer transcript' >"$second/.codex/sessions/session.json"
assert_succeed 'b dot save'
repository="$second/.local/state/mise/history/repo.git"
assert_fail "git --git-dir=$repository cat-file -e main:home/.codex/sessions/session.json"

# an incoming `include` this mise cannot compile is refused the same
# way. Dropping it would be worse than for an exclusion: an entry with
# no usable include list selects its whole tree.
cat >"$PWD/older-client/config/config.toml" <<'TOML'
[dotfiles]
"~/.codex" = { mode = "track", include = ["["] }
"~/.config/mise/config.toml" = { mode = "track" }

[history]
exclude = ["sessions/**"]
TOML
git -C "$PWD/older-client" add config/config.toml
git -C "$PWD/older-client" -c user.name=test -c user.email=test@example.com commit -qm 'unusable include'
git -C "$PWD/older-client" push -q origin main
assert_succeed 'b dot sync --fetch-only'
assert_fail 'b dot pull --yes' 'conflict'
assert_not_contains "cat $second/.config/mise/config.toml" 'include = ["["]'

# and an `include` on an entry that is not tracked is refused rather
# than compiled and then dropped: composition would leave that entry
# out, so a pull must not report success as though it applied.
cat >"$PWD/older-client/config/config.toml" <<'TOML'
[dotfiles]
"~/.codex" = { mode = "track" }
"~/.config/mise/config.toml" = { mode = "track" }
"~/.deployed" = { source = "files/deployed", include = ["config.toml"] }

[history]
exclude = ["sessions/**"]
TOML
git -C "$PWD/older-client" add config/config.toml
git -C "$PWD/older-client" -c user.name=test -c user.email=test@example.com commit -qm 'include on a deployment entry'
git -C "$PWD/older-client" push -q origin main
assert_succeed 'b dot sync --fetch-only'
assert_fail 'b dot pull --yes' 'conflict'
assert_not_contains "cat $second/.config/mise/config.toml" '~/.deployed'

# Include lists apply to directories, including when config arrives remotely.
echo 'local file' >"$second/.single-file"
cat >"$PWD/older-client/config/config.toml" <<'TOML'
[dotfiles]
"~/.codex" = { mode = "track" }
"~/.config/mise/config.toml" = { mode = "track" }
"~/.single-file" = { mode = "track", include = ["config.toml"] }

[history]
exclude = ["sessions/**"]
TOML
git -C "$PWD/older-client" add config/config.toml
git -C "$PWD/older-client" -c user.name=test -c user.email=test@example.com commit -qm 'include on an existing file'
git -C "$PWD/older-client" push -q origin main
assert_succeed 'b dot sync --fetch-only'
assert_fail 'b dot pull --yes' 'conflict'
assert_not_contains "cat $second/.config/mise/config.toml" '~/.single-file'
assert "cat $second/.single-file" 'local file'

# fixing it upstream lets the pull through again
cat >"$PWD/older-client/config/config.toml" <<'TOML'
[dotfiles]
"~/.codex" = { mode = "track" }
"~/.config/mise/config.toml" = { mode = "track" }

[history]
exclude = ["sessions/**"]
TOML
git -C "$PWD/older-client" add config/config.toml
git -C "$PWD/older-client" -c user.name=test -c user.email=test@example.com commit -qm 'usable again'
git -C "$PWD/older-client" push -q origin main
assert_succeed 'b dot sync --fetch-only'
assert_succeed 'b dot pull --yes'
assert_succeed 'b dot save'
