Rename script/fetch-policy to script/commit-current-upstream-policy-docs
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
sneak, on the PR: the script does more than fetch, and short names are not wanted. The make target, the test names, the variables and the messages take the new name. Behaviour is unchanged. Model: opus-5-5
This commit is contained in:
1 parent
917fe4bc1d
commit
ed60414378
9 files changed
+145
-129
No files matched your search
Executable
+302
@@ -0,0 +1,302 @@
|
||||
#!/bin/sh
|
||||
# script/commit-current-upstream-policy-docs: write the current policy
|
||||
# files from the prompts repo into a repo clone, over HTTP. It commits
|
||||
# nothing; script/update-policy does that. Identical in all repos. Our
|
||||
# own extension to scripts-to-rule-them-all.
|
||||
#
|
||||
# Usage: script/commit-current-upstream-policy-docs [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/commit-current-upstream-policy-docs
|
||||
script/update-policy
|
||||
"
|
||||
|
||||
die() {
|
||||
echo "commit-current-upstream-policy-docs: $*" >&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/commit-current-upstream-policy-docs [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 script/update-policy may
|
||||
# be the script running.
|
||||
cp "$tmp/$path" "$path.new"
|
||||
chmod 755 "$path.new"
|
||||
mv "$path.new" "$path"
|
||||
;;
|
||||
*)
|
||||
cp "$tmp/$path" "$path"
|
||||
;;
|
||||
esac
|
||||
echo "$path"
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in new issue
Block a user