fix(install): force exec bits from git's index on every restore

core.fileMode=false (repo-local or a machine-wide /etc/gitconfig) can
silently drop the +x bit on every tracked script during checkout, which
then surfaces later as a confusing "Permission denied" on whatever
script happens to run next rather than as an obvious failure up front.
ensure_exec_bits() re-applies +x from `git ls-files -s` (mode 100755)
right after every pull, so a restore is self-healing regardless of cause.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jon Wingender
2026-07-30 11:58:13 -05:00
co-authored by Claude Sonnet 5
parent 3b0354735c
commit c211be57a1
+21
View File
@@ -43,6 +43,26 @@ def run(*args, capture=False, check=False):
)
def ensure_exec_bits() -> None:
"""Force +x on every path git tracks as executable (mode 100755), straight
from `git ls-files` rather than a hardcoded list - self-maintaining as
scripts are added. Guards against core.fileMode=false (repo-local or a
machine-wide /etc/gitconfig) silently dropping exec bits on checkout, which
otherwise surfaces later as a confusing "Permission denied" on whichever
script happens to run next (fetch-ollama.sh, the ncp symlink target, ...)
rather than as an obvious failure right here."""
if os.name == "nt":
return
result = run("git", "ls-files", "-s", capture=True)
for line in result.stdout.splitlines():
mode, _, rest = line.partition(" ")
if mode != "100755":
continue
path = ROOT / rest.split("\t", 1)[1]
if path.exists():
path.chmod(path.stat().st_mode | 0o111)
def linux_stage(script: str, *args) -> None:
"""Run one of the Linux-only bash stages. A no-op on Windows, where apt,
xfconf, plank and the rest have nothing to act on."""
@@ -252,6 +272,7 @@ def cmd_restore(args) -> int:
if run("git", "pull", "--ff-only", "origin", "main").returncode:
print("Pull failed (diverged? stash/commit local changes).")
return 1
ensure_exec_bits()
restore_db()
rebuild_env()
linux_stage("restore-linux.sh", "runtime")