A listed file the repo lacks is now written whole, with no keep rule. A prompts/NAME.md goes to docs/NAME.md unless the repo keeps it at NAME.md, and any other file goes to its own path. A language's files are skipped when git tracks no source file of that language. A listed path git ignores now stops the script before it writes. README bullets split into short sentences, and a false sentence about template repos deleted. Model: opus-5-5
This commit is contained in:
1 parent
f4275b9ac0
commit
76b75fa79f
4 files changed
+166
-45
No files matched your search
@@ -142,11 +142,11 @@ alpine. We provide:
|
||||
- `script/install-precommit` — installs the git pre-commit hook (our own
|
||||
extension); `make hooks` shims to it
|
||||
- `script/install` — fetch the vendored policy files from this repo over HTTP
|
||||
and write them into the repo given as its argument, or its own repo with no
|
||||
argument (our own extension); it stops when run here
|
||||
and write them into a repo (our own extension). It writes into the repo given
|
||||
as its argument, or its own repo with no argument. It stops when run here.
|
||||
- `script/update-policy` — fetch the current `script/install`, run it on its own
|
||||
repo and commit the result without pushing (our own extension); it is for the
|
||||
repos that vendor the policy files and stops when run here
|
||||
repo and commit the result without pushing (our own extension). It is for the
|
||||
repos that vendor the policy files. It stops when run here.
|
||||
|
||||
`make hooks` installs the pre-commit hook that runs `script/precommit`.
|
||||
|
||||
|
||||
@@ -943,12 +943,14 @@ style conventions are in separate documents:
|
||||
the files that changed and does not push, so a person reviews and pushes the
|
||||
commit.
|
||||
|
||||
`script/install` fetches each vendored file the repo carries from `prompts`
|
||||
over HTTP. It also installs `script/install` and `script/update-policy` in
|
||||
the repo, so a repo updated once can update itself. Every repo made from a
|
||||
template repo already has both. The list of vendored files is at the top of
|
||||
`script/install`. The script replaces each file and keeps only the repo's
|
||||
own entries:
|
||||
`script/install` fetches each vendored file from `prompts` over HTTP and
|
||||
writes it into the repo. It skips a language's files when git tracks no
|
||||
source file of that language. A file the repo lacks is written whole. A
|
||||
`prompts/NAME.md` goes to `docs/NAME.md`, unless the repo keeps it at
|
||||
`NAME.md`. It also installs `script/install` and `script/update-policy` in
|
||||
the repo, so a repo updated once can update itself. The list of vendored
|
||||
files is at the top of `script/install`. The script replaces each file and
|
||||
keeps only the repo's own entries:
|
||||
- in `.gitignore`, `.editorconfig` and `.dockerignore`: the lines below the
|
||||
comment that ends the canonical file
|
||||
- in `.golangci.yml`: the entries the repo added to the `deny` list of the
|
||||
|
||||
+42
-20
@@ -24,8 +24,9 @@ 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. A file the target repo does not carry is
|
||||
# skipped, except the two scripts, which every repo gets.
|
||||
# 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
|
||||
@@ -58,15 +59,36 @@ fetch() {
|
||||
die "cannot fetch $1"
|
||||
}
|
||||
|
||||
# local_path <path in prompts>: print where the target repo keeps the
|
||||
# file, or nothing if git does not track it there. A prompts/NAME.md
|
||||
# lands at docs/NAME.md or NAME.md.
|
||||
# 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" ] && [ -n "$(git ls-files -- "docs/$name")" ]; then
|
||||
echo "docs/$name"
|
||||
elif [ -n "$(git ls-files -- "$name")" ]; then
|
||||
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
|
||||
}
|
||||
|
||||
@@ -177,9 +199,8 @@ $own"
|
||||
# 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. A repo without the script is fine.
|
||||
# delete it.
|
||||
stop_on_own_script() {
|
||||
[ -e "$2" ] || return 0
|
||||
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:
|
||||
@@ -210,23 +231,24 @@ main() {
|
||||
fetch "$PROMPTS_URL/compare/$PROMPTS_FIRST_COMMIT...$commit.patch" \
|
||||
"$tmp/history.patch"
|
||||
|
||||
carried=""
|
||||
to_write=""
|
||||
for file in $VENDORED_FILES; do
|
||||
case "$file" in
|
||||
script/*) path="$file" ;;
|
||||
*) path="$(local_path "$file")" ;;
|
||||
esac
|
||||
[ -n "$path" ] || continue
|
||||
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"
|
||||
carried="$carried $path"
|
||||
to_write="$to_write $path"
|
||||
done
|
||||
|
||||
for path in $carried; do
|
||||
for path in $to_write; do
|
||||
[ -e "$path" ] || continue
|
||||
case "$path" in
|
||||
.gitignore | .editorconfig | .dockerignore)
|
||||
keep_lines_below_last "$tmp/$path" "$path"
|
||||
@@ -241,12 +263,12 @@ main() {
|
||||
done
|
||||
|
||||
echo "$commit"
|
||||
for path in $carried; do
|
||||
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.
|
||||
mkdir -p script
|
||||
cp "$tmp/$path" "$path.new"
|
||||
chmod 755 "$path.new"
|
||||
mv "$path.new" "$path"
|
||||
|
||||
+112
-15
@@ -117,13 +117,20 @@ EOF
|
||||
echo '{ "tabWidth": 4 }' > "$dir/canonical/.prettierrc"
|
||||
echo "new policy" > "$dir/canonical/prompts/REPO_POLICIES.md"
|
||||
echo "new api policy" > "$dir/canonical/prompts/API_POLICIES.md"
|
||||
for name in CODE_STYLEGUIDE CODE_STYLEGUIDE_GO CODE_STYLEGUIDE_JS \
|
||||
CODE_STYLEGUIDE_PYTHON EXISTING_REPO_CHECKLIST \
|
||||
GO_HTTP_SERVER_CONVENTIONS LLM_PROSE_TELLS NEW_REPO_CHECKLIST; do
|
||||
echo "new $name" > "$dir/canonical/prompts/$name.md"
|
||||
done
|
||||
mkdir -p "$dir/canonical/.gitea/workflows"
|
||||
echo "name: check" > "$dir/canonical/.gitea/workflows/check.yml"
|
||||
}
|
||||
|
||||
# commit_canonical: commit the canonical files as they stand. The stub
|
||||
# curl serves only the newest commit; every other commit gets a 404.
|
||||
commit_canonical() {
|
||||
git -C "$dir/canonical" add .gitignore .dockerignore .editorconfig \
|
||||
.golangci.yml .prettierrc prompts script
|
||||
.golangci.yml .prettierrc .gitea prompts script
|
||||
git -C "$dir/canonical" commit -q -m "Canonical"
|
||||
HASH="$(git -C "$dir/canonical" rev-parse HEAD)"
|
||||
}
|
||||
@@ -174,7 +181,8 @@ EOF
|
||||
}
|
||||
|
||||
# start_scenario <name>: make a new temp directory holding a repo with
|
||||
# the outdated files, not yet committed, and an empty origin.
|
||||
# a Go source file and the outdated files, not yet committed, and an
|
||||
# empty origin.
|
||||
start_scenario() {
|
||||
scenario="$1"
|
||||
rm -rf "$dir"
|
||||
@@ -203,18 +211,46 @@ start_scenario() {
|
||||
cp "$INSTALL" "$UPDATE_POLICY" "$dir/repo/script/"
|
||||
printf '#!/bin/sh\necho example\n' > "$dir/repo/script/projectname"
|
||||
chmod +x "$dir/repo/script/projectname"
|
||||
mkdir -p "$dir/repo/cmd/app"
|
||||
echo "package main" > "$dir/repo/cmd/app/main.go"
|
||||
write_outdated_files
|
||||
}
|
||||
|
||||
# commit_and_push_fixture <paths...>: commit those paths, remove the
|
||||
# rest of the outdated files, and record the commit as $before.
|
||||
# commit_and_push_fixture <paths...>: commit those paths as they stand
|
||||
# and every other shared file as the canonical file has it, so the
|
||||
# script changes only those paths. Record the commit as $before.
|
||||
commit_and_push_fixture() {
|
||||
(
|
||||
cd "$dir/repo"
|
||||
git add script "$@"
|
||||
git add script cmd "$@"
|
||||
git clean -q -f -x
|
||||
for file in $(git -C "$dir/canonical" ls-files); do
|
||||
case "$file" in
|
||||
script/*) continue ;;
|
||||
prompts/REPO_POLICIES.md) path="REPO_POLICIES.md" ;;
|
||||
prompts/*) path="docs/${file#prompts/}" ;;
|
||||
*) path="$file" ;;
|
||||
esac
|
||||
if [ -z "$(git ls-files -- "$path")" ]; then
|
||||
mkdir -p "$(dirname "$path")"
|
||||
cp "$dir/canonical/$file" "$path"
|
||||
git add "$path"
|
||||
fi
|
||||
done
|
||||
git commit -q -m "Fixture"
|
||||
git push -q origin HEAD
|
||||
git clean -q -f -x
|
||||
)
|
||||
before="$(git -C "$dir/repo" rev-parse HEAD)"
|
||||
}
|
||||
|
||||
# remove_from_fixture <paths...>: delete those paths from the repo and
|
||||
# its origin, and record the commit as $before.
|
||||
remove_from_fixture() {
|
||||
(
|
||||
cd "$dir/repo"
|
||||
git rm -q -r "$@"
|
||||
git commit -q -m "Remove from fixture"
|
||||
git push -q origin HEAD
|
||||
)
|
||||
before="$(git -C "$dir/repo" rev-parse HEAD)"
|
||||
}
|
||||
@@ -365,25 +401,55 @@ EOF
|
||||
[ -z "$(ls -A "$dir/tmp")" ] || fail "a temporary directory is left"
|
||||
}
|
||||
|
||||
test_files_not_carried_are_skipped() {
|
||||
start_scenario "files the repo does not carry skipped"
|
||||
test_files_the_repo_lacks_are_written_whole() {
|
||||
start_scenario "files the repo lacks are written whole"
|
||||
mkdir "$dir/repo/docs"
|
||||
mv "$dir/repo/REPO_POLICIES.md" "$dir/repo/docs/REPO_POLICIES.md"
|
||||
commit_and_push_fixture docs/REPO_POLICIES.md
|
||||
echo "print()" > "$dir/repo/tool.py"
|
||||
echo "export {}" > "$dir/repo/web.ts"
|
||||
commit_and_push_fixture docs/REPO_POLICIES.md tool.py web.ts
|
||||
remove_from_fixture REPO_POLICIES.md .gitignore .golangci.yml .gitea \
|
||||
docs/API_POLICIES.md docs/CODE_STYLEGUIDE_GO.md \
|
||||
docs/CODE_STYLEGUIDE_JS.md docs/CODE_STYLEGUIDE_PYTHON.md
|
||||
run_update_policy
|
||||
expect_success
|
||||
|
||||
expect_same_as_canonical .gitignore
|
||||
expect_same_as_canonical .golangci.yml
|
||||
expect_same_as_canonical .gitea/workflows/check.yml
|
||||
expect_file docs/REPO_POLICIES.md << 'EOF'
|
||||
new policy
|
||||
EOF
|
||||
expect_absent REPO_POLICIES.md
|
||||
expect_file docs/API_POLICIES.md << 'EOF'
|
||||
new api policy
|
||||
EOF
|
||||
expect_absent API_POLICIES.md
|
||||
expect_absent docs/API_POLICIES.md
|
||||
expect_absent .gitignore
|
||||
expect_absent .dockerignore
|
||||
expect_absent .editorconfig
|
||||
expect_file docs/CODE_STYLEGUIDE_GO.md << 'EOF'
|
||||
new CODE_STYLEGUIDE_GO
|
||||
EOF
|
||||
expect_file docs/CODE_STYLEGUIDE_JS.md << 'EOF'
|
||||
new CODE_STYLEGUIDE_JS
|
||||
EOF
|
||||
expect_file docs/CODE_STYLEGUIDE_PYTHON.md << 'EOF'
|
||||
new CODE_STYLEGUIDE_PYTHON
|
||||
EOF
|
||||
expect_new_commit_not_pushed
|
||||
}
|
||||
|
||||
test_language_files_need_a_source_file() {
|
||||
start_scenario "a repo with no source file of a language gets none of its files"
|
||||
commit_and_push_fixture .prettierrc
|
||||
remove_from_fixture cmd .golangci.yml docs/CODE_STYLEGUIDE_GO.md \
|
||||
docs/GO_HTTP_SERVER_CONVENTIONS.md docs/CODE_STYLEGUIDE_JS.md \
|
||||
docs/CODE_STYLEGUIDE_PYTHON.md
|
||||
run_update_policy
|
||||
expect_success
|
||||
expect_absent .golangci.yml
|
||||
expect_absent .prettierrc
|
||||
expect_absent docs/CODE_STYLEGUIDE_GO.md
|
||||
expect_absent docs/GO_HTTP_SERVER_CONVENTIONS.md
|
||||
expect_absent docs/CODE_STYLEGUIDE_JS.md
|
||||
expect_absent docs/CODE_STYLEGUIDE_PYTHON.md
|
||||
expect_new_commit_not_pushed
|
||||
}
|
||||
|
||||
@@ -463,6 +529,19 @@ EOF
|
||||
[ -z "$(ls -A "$dir/tmp")" ] || fail "a temporary directory is left"
|
||||
}
|
||||
|
||||
test_install_without_script_directory() {
|
||||
start_scenario "script/install with a path writes both scripts into a clone with no script directory"
|
||||
commit_and_push_fixture .prettierrc
|
||||
remove_from_fixture script
|
||||
run_install "$dir/repo"
|
||||
expect_success
|
||||
expect_same_as_canonical script/install
|
||||
expect_same_as_canonical script/update-policy
|
||||
expect_executable script/install
|
||||
expect_executable script/update-policy
|
||||
expect_no_commit
|
||||
}
|
||||
|
||||
test_install_without_argument_updates_own_repo() {
|
||||
start_scenario "script/install with no argument updates the repo it sits in"
|
||||
commit_and_push_fixture .prettierrc
|
||||
@@ -580,6 +659,21 @@ EOF
|
||||
expect_no_commit
|
||||
}
|
||||
|
||||
test_ignored_file_stops() {
|
||||
start_scenario "an ignored file at a listed path is not overwritten"
|
||||
commit_and_push_fixture .prettierrc
|
||||
remove_from_fixture docs/API_POLICIES.md
|
||||
echo "docs/API_POLICIES.md" >> "$dir/repo/.git/info/exclude"
|
||||
echo "our own notes" > "$dir/repo/docs/API_POLICIES.md"
|
||||
run_install "$dir/repo"
|
||||
expect_failure
|
||||
expect_output "docs/API_POLICIES.md is ignored by git"
|
||||
expect_file docs/API_POLICIES.md << 'EOF'
|
||||
our own notes
|
||||
EOF
|
||||
expect_no_commit
|
||||
}
|
||||
|
||||
test_own_script_stops() {
|
||||
start_scenario "a tracked script/install with a line the canonical file has never held stops"
|
||||
echo "echo our own installer" >> "$dir/repo/script/install"
|
||||
@@ -731,11 +825,13 @@ test_line_removed_from_another_file_still_stops() {
|
||||
|
||||
main() {
|
||||
test_own_entries_kept
|
||||
test_files_not_carried_are_skipped
|
||||
test_files_the_repo_lacks_are_written_whole
|
||||
test_language_files_need_a_source_file
|
||||
test_nothing_changed
|
||||
test_target_lacking_install_gets_both_scripts
|
||||
test_update_policy_replaces_itself
|
||||
test_install_with_path_commits_nothing
|
||||
test_install_without_script_directory
|
||||
test_install_without_argument_updates_own_repo
|
||||
test_install_bad_target
|
||||
test_failed_fetch_stops
|
||||
@@ -744,6 +840,7 @@ main() {
|
||||
test_own_entry_above_closing_comment
|
||||
test_uncommitted_change_stops
|
||||
test_untracked_script_stops
|
||||
test_ignored_file_stops
|
||||
test_own_script_stops
|
||||
test_gitignore_lacks_canonical_last_line
|
||||
test_fetched_golangci_without_deny_list
|
||||
|
||||
Reference in new issue
Block a user