Skip to content

Mode-only changes are invisible to status, diff, diff --cached, show and commit -a #16

Closed

Description

@Hazzng

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 status and git diff print nothing;
  • git commit -a says "nothing to commit";
  • after git add, git status and git diff --cached still print nothing;
  • git show of 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).

  1. src/lib/worktree.ts L93, diffIndexToWorkTree: when the stat check fails, it re-hashes the file and reports modified only if workTreeHash !== entry.hash. A mode-only change has the same hash, so it's dropped. status, diff and commit -a all go through this. indexStatMatchesFile (src/lib/index.ts L201) already notices the mode mismatch; the hash comparison then throws that away.
  2. src/lib/status-format.ts L455, getStagedChanges: headEntry.hash !== entry.hash only, so a staged mode change isn't listed.
  3. src/commands/diff.ts L393 (--cached / against a commit) and L544 (unmerged paths): hash-only comparisons again.
  4. src/lib/diff-algorithm.ts L1284, formatUnifiedDiff: if (hunks.length === 0 && !isRename) return ""; returns before the old mode / new mode header that pushDiffGitHeader (L1189–L1192) would write. That's why git show is empty even though diffTrees (src/lib/tree-ops.ts L190) does compare mode and reports the file. formatBinaryDiff has 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) against entry.mode, the way indexStatMatchesFile already does.
  • In 4, return "" only when there are no hunks and the modes match; otherwise emit the diff --git line 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

  1. blindmansion commented on Oct 14, 2026

    @blindmansion
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions