Repository navigation
Mode-only changes are invisible to status, diff, diff --cached, show and commit -a #16
Description
Summary
A change that only flips the executable bit doesn't show up anywhere except git add. After chmod 644 on a committed 100755 file:
git statusandgit diffprint nothing;git commit -asays "nothing to commit";- after
git add,git statusandgit diff --cachedstill print nothing; git showof the commit that records it prints the subject and no diff.
git add does stage the new mode, and the commit records it, so the data is right. The user just can't see the change before or after committing it. Content changes work fine; only mode-only changes are affected.
Repro (just-git 1.9.1 + just-bash 3.6.0)
import { Bash, InMemoryFs } from "just-bash";
import { createGit } from "just-git";
const env = { GIT_AUTHOR_NAME: "a", GIT_AUTHOR_EMAIL: "a@b.c", GIT_COMMITTER_NAME: "a", GIT_COMMITTER_EMAIL: "a@b.c" };
const bash = new Bash({ fs: new InMemoryFs(), customCommands: [createGit()], env, cwd: "/home/user" });
const r = await bash.exec(`
mkdir r && cd r && git init -q
printf 'x\\n' > run.sh && chmod 755 run.sh && git add run.sh && git commit -q -m add
chmod 644 run.sh
echo '--- status'; git status --short
echo '--- diff'; git diff
echo '--- commit -a'; git commit -q -am chmod; echo rc=$?
git add run.sh
echo '--- status (staged)'; git status --short
echo '--- diff --cached'; git diff --cached
git commit -q -m chmod
echo '--- show'; git show --format=%s HEAD
`);
console.log(r.stdout);just-git 1.9.1:
--- status
--- diff
--- commit -a
On branch main
nothing to commit, working tree clean
rc=1
--- status (staged)
--- diff --cached
--- show
chmod
git 2.34.1, same steps:
--- status
M run.sh
--- diff
diff --git a/run.sh b/run.sh
old mode 100755
new mode 100644
--- commit -a
rc=0
With commit -a succeeding there, the staged half has nothing left to show. Running the staged steps on their own in git gives M run.sh, the same old mode / new mode header from git diff --cached, and the same header under the subject in git show.
Where
All at b509001 (1.9.1).
src/lib/worktree.tsL93,diffIndexToWorkTree: when the stat check fails, it re-hashes the file and reportsmodifiedonly ifworkTreeHash !== entry.hash. A mode-only change has the same hash, so it's dropped.status,diffandcommit -aall go through this.indexStatMatchesFile(src/lib/index.tsL201) already notices the mode mismatch; the hash comparison then throws that away.src/lib/status-format.tsL455,getStagedChanges:headEntry.hash !== entry.hashonly, so a staged mode change isn't listed.src/commands/diff.tsL393 (--cached/ against a commit) and L544 (unmerged paths): hash-only comparisons again.src/lib/diff-algorithm.tsL1284,formatUnifiedDiff:if (hunks.length === 0 && !isRename) return "";returns before theold mode/new modeheader thatpushDiffGitHeader(L1189–L1192) would write. That's whygit showis empty even thoughdiffTrees(src/lib/tree-ops.tsL190) does comparemodeand reports the file.formatBinaryDiffhas the same early return at L1202.
Suggested fix
- In 1–3, treat an entry as changed when the hash or the git mode differs. For the worktree, compare
gitModeFromFileStat(st)againstentry.mode, the wayindexStatMatchesFilealready does. - In 4, return
""only when there are no hunks and the modes match; otherwise emit thediff --gitline plus the mode header.
Why it matters to us
We run just-git inside sandboxes where agents clone repos, edit them and commit. When a tool rewrites a script and drops its exec bit, git status looks clean, so neither the agent nor a reviewer notices until the mode change lands in a commit. Related to #10, which is the checkout side of the same exec-bit handling.
Activity
blindmansion commentedon Oct 14, 2026
Fixed by #15 (merged): status, diff, diff --cached, show and commit -a now see mode-only changes. Further mode fixes land in 1.10.0.