check / check (push) Canceled after 0s
script/commit-current-upstream-policy-docs is now script/write-upstream-policy-files: it writes the policy files and commits nothing. script/update-policy is now script/commit-upstream-policy-files, and make update-policy is now make commit-upstream-policy-files. test/update-policy.sh follows. Behaviour is unchanged. Model: opus-5-5
303 lines
10 KiB
Bash
Executable File
303 lines
10 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/write-upstream-policy-files: write the current policy files
|
|
# from the prompts repo into a repo clone, over HTTP. It commits
|
|
# nothing; script/commit-upstream-policy-files does that. Identical in
|
|
# all repos. Our own extension to scripts-to-rule-them-all.
|
|
#
|
|
# Usage: script/write-upstream-policy-files [path of a repo clone]
|
|
# Without a path it updates the repo this script sits in.
|
|
#
|
|
# Standard output is the full hash of the prompts commit, then each path
|
|
# written, one per line. Messages go to standard error.
|
|
#
|
|
# The script writes nothing until every file has been fetched and
|
|
# merged. A repo's own entries are kept:
|
|
# - .gitignore, .editorconfig, .dockerignore: the lines below the
|
|
# canonical file's last line. An entry above that line which the
|
|
# canonical file has never held stops the script.
|
|
# - .gitignore, .dockerignore lacking that last line: each line the
|
|
# canonical file has never held, unless it is blank or a comment,
|
|
# goes below the canonical content.
|
|
# - .editorconfig lacking that last line: it is replaced if it has no
|
|
# such line. Otherwise the script stops, because a setting means
|
|
# nothing away from its section.
|
|
# - .golangci.yml: the entries the repo added to the deny list of the
|
|
# test-support rule. Any other line the canonical file has never
|
|
# held stops the script, unless it is blank or a comment.
|
|
set -eu
|
|
|
|
PROMPTS_URL="https://git.eeqj.de/sneak/prompts"
|
|
PROMPTS_API_URL="https://git.eeqj.de/api/v1/repos/sneak/prompts"
|
|
PROMPTS_FIRST_COMMIT="af257bef63f162a681bddc80d9b941c47a44b748"
|
|
|
|
# Paths in the prompts repo. Every repo gets each file, written whole if
|
|
# the repo lacks it. A language's files are skipped when git tracks no
|
|
# source file of that language.
|
|
VENDORED_FILES="
|
|
.dockerignore
|
|
.editorconfig
|
|
.gitea/workflows/check.yml
|
|
.gitignore
|
|
.golangci.yml
|
|
.prettierrc
|
|
prompts/API_POLICIES.md
|
|
prompts/CODE_STYLEGUIDE.md
|
|
prompts/CODE_STYLEGUIDE_GO.md
|
|
prompts/CODE_STYLEGUIDE_JS.md
|
|
prompts/CODE_STYLEGUIDE_PYTHON.md
|
|
prompts/EXISTING_REPO_CHECKLIST.md
|
|
prompts/GO_HTTP_SERVER_CONVENTIONS.md
|
|
prompts/LLM_PROSE_TELLS.md
|
|
prompts/NEW_REPO_CHECKLIST.md
|
|
prompts/REPO_POLICIES.md
|
|
script/write-upstream-policy-files
|
|
script/commit-upstream-policy-files
|
|
"
|
|
|
|
die() {
|
|
echo "write-upstream-policy-files: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# fetch <url> <output file>
|
|
fetch() {
|
|
curl --fail --silent --show-error --max-time 60 --output "$2" "$1" ||
|
|
die "cannot fetch $1"
|
|
}
|
|
|
|
# file_applies <path in prompts>: fail if the file is for a language of
|
|
# which git tracks no source file in the target repo.
|
|
file_applies() {
|
|
case "$1" in
|
|
.golangci.yml | prompts/CODE_STYLEGUIDE_GO.md | \
|
|
prompts/GO_HTTP_SERVER_CONVENTIONS.md)
|
|
[ -n "$(git ls-files -- '*.go')" ]
|
|
;;
|
|
prompts/CODE_STYLEGUIDE_JS.md)
|
|
[ -n "$(git ls-files -- '*.js' '*.mjs' '*.ts')" ]
|
|
;;
|
|
prompts/CODE_STYLEGUIDE_PYTHON.md)
|
|
[ -n "$(git ls-files -- '*.py')" ]
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# local_path <path in prompts>: print where the file goes in the target
|
|
# repo. A prompts/NAME.md stays at NAME.md if git tracks it there and not
|
|
# at docs/NAME.md; otherwise it goes to docs/NAME.md. Any other file goes
|
|
# to its own path.
|
|
local_path() {
|
|
name="${1#prompts/}"
|
|
if [ "$name" = "$1" ]; then
|
|
echo "$1"
|
|
elif [ -z "$(git ls-files -- "docs/$name")" ] &&
|
|
[ -n "$(git ls-files -- "$name")" ]; then
|
|
echo "$name"
|
|
else
|
|
echo "docs/$name"
|
|
fi
|
|
}
|
|
|
|
# former_lines <patch> <path in prompts>: print the lines earlier commits
|
|
# removed from the file. The file's section of the patch ends at the
|
|
# next diff or the next commit.
|
|
former_lines() {
|
|
awk -v header="diff --git a/$2 b/$2" '
|
|
/^diff --git / { inside = ($0 == header); next }
|
|
/^From / { inside = 0; next }
|
|
inside && /^-/ { print substr($0, 2) }
|
|
' "$1"
|
|
}
|
|
|
|
# local_only_lines <fetched> <local> [end]: print each line of the local
|
|
# file, above line end if given, that the canonical file has never held:
|
|
# not in the fetched file or in <fetched>.former, the lines earlier
|
|
# commits removed from it. Blank lines and comments do not count.
|
|
local_only_lines() {
|
|
cat "$1" "$1.former" | awk -v end="${3:-}" '
|
|
NR == FNR { held[$0]; next }
|
|
(end == "" || FNR < end) && !/^[ \t]*(#|$)/ && !($0 in held)
|
|
' - "$2"
|
|
}
|
|
|
|
# keep_lines_below_last <fetched> <local>: append to the fetched file
|
|
# whatever follows its last line in the local file. Stop if the canonical
|
|
# file has never held a line above that one, unless it is blank or a
|
|
# comment: replacing the file would delete it. If the local file lacks
|
|
# that last line, append each line the canonical file has never held,
|
|
# unless it is blank or a comment. An .editorconfig with such a line
|
|
# stops the script instead, because a setting means nothing away from
|
|
# its section.
|
|
keep_lines_below_last() {
|
|
last="$(tail -n 1 "$1")"
|
|
number="$(grep -n -F -x -- "$last" "$2" | head -n 1 | cut -d: -f1)"
|
|
if [ -z "$last" ] || [ -z "$number" ]; then
|
|
own="$(local_only_lines "$1" "$2")"
|
|
[ -n "$own" ] || return 0
|
|
if [ "$2" = .editorconfig ]; then
|
|
die "$2 lacks this line and has lines the canonical file has never held; add it and put those lines below it: $last
|
|
$own"
|
|
fi
|
|
printf '%s\n' "$own" >> "$1"
|
|
return 0
|
|
fi
|
|
own="$(local_only_lines "$1" "$2" "$number")"
|
|
if [ -n "$own" ]; then
|
|
die "$2 has entries above its last canonical line that the canonical file lacks; move each below that line to keep it, or delete it:
|
|
$own"
|
|
fi
|
|
tail -n "+$((number + 1))" "$2" >> "$1"
|
|
}
|
|
|
|
# keep_deny_entries <fetched> <local>: add to the deny list of the
|
|
# test-support rule in the fetched file the entries only the local file
|
|
# has. An entry is a "- pkg:" line, the lines under it and any comment
|
|
# above it. Stop if the local file still has a line that neither the
|
|
# result nor the canonical file has ever held, unless it is blank or a
|
|
# comment: replacing the file would delete it.
|
|
keep_deny_entries() {
|
|
awk '
|
|
function indent(line) { return match(line, /[^ ]/) - 1 }
|
|
function print_local_entries(i) {
|
|
for (i = 1; i <= count; i++)
|
|
if (!(pkg[i] in fetched_pkg))
|
|
printf "%s", entry[i]
|
|
}
|
|
|
|
FNR == 1 { in_rule = 0; in_list = 0 }
|
|
|
|
in_list && /[^ ]/ && indent($0) <= list_indent {
|
|
in_list = 0
|
|
if (FILENAME == ARGV[2]) print_local_entries()
|
|
}
|
|
|
|
in_list {
|
|
is_entry = ($1 == "-" && $2 == "pkg:")
|
|
if (FILENAME == ARGV[2]) {
|
|
if (is_entry) fetched_pkg[$3]
|
|
print
|
|
} else if (is_entry) {
|
|
count++
|
|
pkg[count] = $3
|
|
entry[count] = comment $0 "\n"
|
|
comment = ""
|
|
} else if (/^ *#/) {
|
|
comment = comment $0 "\n"
|
|
} else if (count) {
|
|
entry[count] = entry[count] $0 "\n"
|
|
}
|
|
next
|
|
}
|
|
|
|
in_rule && /^ *[^ #]/ && indent($0) <= rule_indent { in_rule = 0 }
|
|
/^ *test-support:/ { in_rule = 1; rule_indent = indent($0) }
|
|
in_rule && /^ *deny:/ {
|
|
in_rule = 0
|
|
in_list = 1
|
|
list_indent = indent($0)
|
|
if (FILENAME == ARGV[2]) found = 1
|
|
}
|
|
FILENAME == ARGV[2] { print }
|
|
|
|
END {
|
|
if (in_list) print_local_entries()
|
|
exit !found
|
|
}
|
|
' "$2" "$1" > "$1.merged" ||
|
|
die "the fetched $2 has no deny list in its test-support rule"
|
|
mv "$1.merged" "$1"
|
|
own="$(local_only_lines "$1" "$2")"
|
|
if [ -n "$own" ]; then
|
|
die "$2 has lines the canonical file has never held; only the deny list of the test-support rule may differ from canonical:
|
|
$own"
|
|
fi
|
|
}
|
|
|
|
# stop_on_own_script <fetched> <local>: stop if the local script has a
|
|
# line the canonical file has never held, unless it is blank or a
|
|
# comment: the repo wrote that script itself, and replacing it would
|
|
# delete it.
|
|
stop_on_own_script() {
|
|
own="$(local_only_lines "$1" "$2")"
|
|
if [ -n "$own" ]; then
|
|
die "$2 has lines the canonical file has never held; rename the repo's own script to keep it:
|
|
$own"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
[ "$#" -le 1 ] ||
|
|
die "usage: script/write-upstream-policy-files [path of a repo clone]"
|
|
|
|
target="${1:-$(dirname "$0")/..}"
|
|
cd "$target" || die "no such directory: $target"
|
|
if [ "$(git rev-parse --show-toplevel 2> /dev/null)" != "$(pwd -P)" ]; then
|
|
die "not the top of a git clone: $target"
|
|
fi
|
|
if [ -e prompts/REPO_POLICIES.md ]; then
|
|
die "this repo is the source of the policy files"
|
|
fi
|
|
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
|
|
fetch "$PROMPTS_API_URL/branches/main" "$tmp/branch.json"
|
|
commit="$(grep -Eo '"id":"[0-9a-f]{40}"' "$tmp/branch.json" |
|
|
head -n 1 | cut -d'"' -f4)"
|
|
[ -n "$commit" ] || die "no commit hash in $PROMPTS_API_URL/branches/main"
|
|
|
|
fetch "$PROMPTS_URL/compare/$PROMPTS_FIRST_COMMIT...$commit.patch" \
|
|
"$tmp/history.patch"
|
|
|
|
to_write=""
|
|
for file in $VENDORED_FILES; do
|
|
file_applies "$file" || continue
|
|
path="$(local_path "$file")"
|
|
if git check-ignore -q -- "$path"; then
|
|
die "$path is ignored by git; stop ignoring it"
|
|
fi
|
|
if [ -n "$(git status --porcelain -- "$path")" ]; then
|
|
die "$path has uncommitted changes"
|
|
fi
|
|
mkdir -p "$tmp/$(dirname "$path")"
|
|
fetch "$PROMPTS_URL/raw/commit/$commit/$file" "$tmp/$path"
|
|
former_lines "$tmp/history.patch" "$file" > "$tmp/$path.former"
|
|
to_write="$to_write $path"
|
|
done
|
|
|
|
for path in $to_write; do
|
|
[ -e "$path" ] || continue
|
|
case "$path" in
|
|
.gitignore | .editorconfig | .dockerignore)
|
|
keep_lines_below_last "$tmp/$path" "$path"
|
|
;;
|
|
.golangci.yml)
|
|
keep_deny_entries "$tmp/$path" "$path"
|
|
;;
|
|
script/*)
|
|
stop_on_own_script "$tmp/$path" "$path"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "$commit"
|
|
for path in $to_write; do
|
|
mkdir -p "$(dirname "$path")"
|
|
case "$path" in
|
|
script/*)
|
|
# A new name and mv, because the script running may be
|
|
# script/commit-upstream-policy-files.
|
|
cp "$tmp/$path" "$path.new"
|
|
chmod 755 "$path.new"
|
|
mv "$path.new" "$path"
|
|
;;
|
|
*)
|
|
cp "$tmp/$path" "$path"
|
|
;;
|
|
esac
|
|
echo "$path"
|
|
done
|
|
}
|
|
|
|
main "$@"
|