Installing the patch permanently blocked updating it
CI / shell (shellcheck + syntax) (push) Successful in 9s
CI / python 3.11 (push) Successful in 14s
CI / python 3.12 (push) Successful in 16s
CI / python 3.13 (push) Successful in 17s
TrueNAS compatibility / compat (push) Successful in 11s
Release / release (push) Successful in 14s
CI / shell (shellcheck + syntax) (push) Successful in 9s
CI / python 3.11 (push) Successful in 14s
CI / python 3.12 (push) Successful in 16s
CI / python 3.13 (push) Successful in 17s
TrueNAS compatibility / compat (push) Successful in 11s
Release / release (push) Successful in 14s
install.sh chmod +x's update.sh, and git recorded update.sh as 100644 -- so the chmod was a TRACKED modification, and update.sh refuses to run over a dirty tree. Install once and you could never update again. The error even told you to 'git checkout -- .', which just undoes the exec bit so the next install can re-dirty it. Found on the real box, which had been sitting on v0.4.1 for exactly this reason. Fixed on both sides: the scripts install.sh chmods are executable in git (so the chmod is a no-op), and update.sh's dirty check now looks at CONTENT, not mode -- git diff --numstat reports 0 0 for a mode-only change. A test asserts every script in install.sh's chmod loop is already 100755 in git.
This commit is contained in:
@@ -66,6 +66,18 @@ worse than no alert, because one day it carries a security fix.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Installing the patch permanently blocked updating it.** `install.sh` does
|
||||
`chmod +x update.sh`, and git recorded `update.sh` as `100644` — so the chmod was a
|
||||
*tracked modification*, and `update.sh` refuses to run over a dirty tree. Install
|
||||
once and you could never update again; the error even told you to run
|
||||
`git checkout -- .`, which just undoes the exec bit so the next install can re-dirty
|
||||
it. A real box sat on an old version for exactly this reason.
|
||||
|
||||
Fixed on both sides: the scripts `install.sh` chmods are now executable in git (so
|
||||
the chmod is a no-op), and `update.sh`'s dirty check now looks at **content**, not
|
||||
file mode — `git diff --numstat` reports `0 0` for a mode-only change. A test
|
||||
asserts every script in `install.sh`'s chmod loop is already `100755` in git.
|
||||
|
||||
- **Nested snapshots were broken on TrueNAS 24.10 and 25.04, and had been all
|
||||
along.** `SYNC_BLOCK`'s wrapper spelled out the stock signature and forwarded five
|
||||
arguments — but those releases declare `restic_backup(middleware, job,
|
||||
|
||||
Regular → Executable
@@ -94,3 +94,40 @@ class TestTheReadmeStaysAReadme:
|
||||
assert "24.10" in text[:text.index("## Install")], (
|
||||
"the minimum TrueNAS version must be visible above the install steps"
|
||||
)
|
||||
|
||||
|
||||
class TestInstallDoesNotDirtyTheCheckout:
|
||||
"""install.sh chmod +x's scripts. If git records them as 100644, that chmod is a
|
||||
TRACKED MODIFICATION -- and update.sh refuses to run over a dirty tree.
|
||||
|
||||
So installing once permanently blocked updating, for every user, with a message
|
||||
telling them to `git checkout -- .` (which would just undo the exec bit and let
|
||||
the next install re-dirty it). Found on a real box that had been stuck on an old
|
||||
version for exactly this reason.
|
||||
|
||||
Every script install.sh makes executable must already be executable in git.
|
||||
"""
|
||||
|
||||
def test_every_chmodded_script_is_already_executable_in_git(self):
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
with open(os.path.join(ROOT, "install.sh"), encoding="utf-8") as fh:
|
||||
m = re.search(r"^for _exe in (.+?); do", fh.read(), re.M)
|
||||
assert m, "could not find install.sh's chmod loop"
|
||||
scripts = m.group(1).split()
|
||||
|
||||
out = subprocess.run(
|
||||
["git", "ls-files", "-s", *scripts],
|
||||
cwd=ROOT, capture_output=True, text=True, check=True,
|
||||
).stdout
|
||||
|
||||
not_exec = [
|
||||
line.split("\t")[-1] for line in out.strip().splitlines()
|
||||
if not line.startswith("100755")
|
||||
]
|
||||
assert not not_exec, (
|
||||
"install.sh chmod +x's these, but git records them as non-executable — "
|
||||
"so installing dirties the checkout and update.sh then refuses to run:\n "
|
||||
+ "\n ".join(not_exec)
|
||||
)
|
||||
|
||||
@@ -136,9 +136,21 @@ fi
|
||||
|
||||
# A dirty tree means someone edited or scp'd files in place; merging over that
|
||||
# silently loses their changes, or conflicts halfway through.
|
||||
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
|
||||
#
|
||||
# CONTENT changes only. A mode-only change (100644 -> 100755) is not somebody's work
|
||||
# and must not block an update -- and it is not hypothetical: install.sh chmod +x's
|
||||
# these very scripts, so on any version where git recorded one as 100644, INSTALLING
|
||||
# dirtied the checkout and update.sh then refused to run. Install once, and updating
|
||||
# was blocked forever, with an error telling the user to `git checkout -- .` (which
|
||||
# merely undoes the exec bit so the next install can re-dirty it). A real box sat on
|
||||
# an old version for exactly this reason.
|
||||
#
|
||||
# `git diff --numstat` reports "0 0 file" for a mode-only change, so anything with a
|
||||
# nonzero insert or delete count is a genuine edit.
|
||||
_dirty=$(git diff --numstat HEAD -- . | awk '$1 != 0 || $2 != 0 { print $3 }')
|
||||
if [ -n "$_dirty" ]; then
|
||||
echo "ERROR: the working tree has uncommitted changes:" >&2
|
||||
git status --short --untracked-files=no >&2
|
||||
printf ' M %s\n' $_dirty >&2
|
||||
echo "" >&2
|
||||
echo " Refusing to update over them. Commit, stash, or discard them first:" >&2
|
||||
echo " git -C $PATCH_DIR checkout -- ." >&2
|
||||
|
||||
Reference in New Issue
Block a user