#!/usr/bin/env bash

# `task_source_files(only_changed=true)` narrows the list to the sources
# written since the task last succeeded, so a slow linter can be handed just
# the new work instead of the whole project.
# See: https://github.com/jdx/mise/discussions/9715

mkdir -p src

# The task definition must stay byte-identical for the whole test: the state
# key that locates the baseline hashes `run` and `sources`, so editing either
# would point at a fresh marker and make every file look changed. `fail` is a
# sentinel file instead, so a run can be made to fail without touching this.
cat <<'EOF' >mise.toml
[tasks.lint]
run = "echo 'checked:{{ task_source_files(only_changed=true) | join(sep=\" \") }}' >>log.txt && test ! -f fail"
sources = ["src/*.txt"]
EOF

echo a >src/a.txt
echo b >src/b.txt

# No marker exists before the first success, so every source is outstanding.
assert_succeed "mise run lint"
assert_contains "cat log.txt" "checked:src/a.txt src/b.txt"
rm -f log.txt

# Timestamps have to land strictly after the marker the run above wrote.
sleep 1
echo changed >src/b.txt

# Only the file written since that success is reported.
assert_succeed "mise run lint"
assert_contains "cat log.txt" "checked:src/b.txt"
assert_not_contains "cat log.txt" "src/a.txt"
rm -f log.txt

# A failed run must not advance the baseline. `save_checksum` only writes the
# marker after the task succeeds, so the same file stays outstanding.
sleep 1
echo changed-again >src/b.txt
touch fail
assert_fail "mise run lint"
rm -f log.txt fail

assert_succeed "mise run lint"
assert_contains "cat log.txt" "checked:src/b.txt"
assert_not_contains "cat log.txt" "src/a.txt"
rm -f log.txt

# Once it succeeds with nothing newly written, there is no outstanding work.
sleep 1
touch src/a.txt
assert_succeed "mise run lint"
assert_contains "cat log.txt" "checked:src/a.txt"
assert_not_contains "cat log.txt" "src/b.txt"
rm -f log.txt

# --force runs the task without consulting the sources at all. Nothing has been
# written since the success above, so the baseline accounts for none of this
# run — and a task handed no files does none of the work it was run to do.
sleep 1
assert_succeed "mise run --force lint"
assert_contains "cat log.txt" "checked:src/a.txt src/b.txt"
rm -f log.txt

# The freshness check runs immediately before the render, so it must not
# advance the baseline on its way into the run it just decided was needed. In
# content-hash mode it can reach that point with the sources matching and the
# *outputs* stale, which is where it used to write the marker anyway.
#
# This needs its own task: `lint` has no declared outputs, so mise touches an
# automatic one and the check comes out fresh. Appending a task and a setting
# leaves `lint`'s own definition, and so its state key, alone.
cat <<'EOF' >>mise.toml

[tasks.hashed]
run = "echo 'hashed:{{ task_source_files(only_changed=true) | join(sep=\" \") }}' >>hashed-log.txt && cp gen/x.txt out.txt"
sources = ["gen/*.txt"]
outputs = ["out.txt"]

[settings]
task.source_freshness_hash_contents = true
EOF

mkdir -p gen
echo x >gen/x.txt
echo y >gen/y.txt

assert_succeed "mise run hashed"
assert_contains "cat hashed-log.txt" "hashed:gen/x.txt gen/y.txt"
rm -f hashed-log.txt

# `touch` leaves the content alone, so the source hash still matches and the
# check reaches the branch above. Deleting the output is what makes it report
# stale, so the task runs — with one source written since the last success.
sleep 1
touch gen/y.txt
rm -f out.txt

assert_succeed "mise run hashed"
assert_contains "cat hashed-log.txt" "hashed:gen/y.txt"
assert_not_contains "cat hashed-log.txt" "gen/x.txt"
