From: Coy Geek Date: Thu, 01 Oct 2026 23:54:12 GMT Subject: [BUG] merge-ort: --no-overwrite-ignore overwrites an ignored file at a directory-rename destination Message-ID: Hello, Consider the following ... `git merge --no-overwrite-ignore` is documented to abort rather than overwrite ignored files. When directory rename detection moves a file that was added on the current branch into a path that holds an ignored, untracked file, the merge replaces that file's contents without any warning. With merge.directoryRenames=true the merge reports success (exit 0). With the default, merge.directoryRenames=conflict, it stops with a "file location" conflict, but the ignored file has already been overwritten. A direct collision with the same ignored path is correctly refused, so the protection is bypassed only for the destination path that rename detection generates. What did you do before the bug happened? (Steps to reproduce your issue) Save the script below as repro.sh and run it as "sh repro.sh true", "sh repro.sh conflict", or "sh repro.sh default" (no -c override). It works in a throwaway repository with empty global and system configuration. #!/bin/sh set -eu mode=${1:-true} unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_CONFIG_PARAMETERS export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null tmp=$(mktemp -d) cd "$tmp" git init -q -b main repo cd repo git config user.name Example git config user.email example@example.invalid echo new/private.dat >.gitignore mkdir old echo seed >old/seed git add .gitignore old/seed git commit -qm base git checkout -qb incoming git mv old new git commit -qm 'rename old/ to new/' git checkout -q main echo 'committed local bytes' >old/private.dat git add old/private.dat git commit -qm 'add old/private.dat' mkdir new echo 'unique ignored bytes' >new/private.dat git status --porcelain --ignored git version status=0 if [ "$mode" = default ]; then set -- ; else set -- -c merge.directoryRenames="$mode"; fi git "$@" merge --no-edit --no-overwrite-ignore incoming || status=$? echo "merge exit status: $status" echo "new/private.dat now contains: $(cat new/private.dat)" Before the merge, `git status --porcelain --ignored` reports only "!! new/": the worktree is clean, and new/private.dat is ignored and untracked. What did you expect to happen? (Expected behavior) As for any other merge result that would overwrite an ignored file under --no-overwrite-ignore, Git refuses before touching the worktree, names new/private.dat, and leaves its contents as "unique ignored bytes". What happened instead? (Actual behavior) With merge.directoryRenames=true: Path updated: old/private.dat added in HEAD inside a directory that was renamed in incoming; moving it to new/private.dat. Merge made by the 'ort' strategy. {old => new}/private.dat | 0 {old => new}/seed | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {old => new}/private.dat (100%) rename {old => new}/seed (100%) merge exit status: 0 new/private.dat now contains: committed local bytes With the default configuration, or merge.directoryRenames=conflict: CONFLICT (file location): old/private.dat added in HEAD inside a directory that was renamed in incoming, suggesting it should perhaps be moved to new/private.dat. Automatic merge failed; fix conflicts and then commit the result. merge exit status: 1 new/private.dat now contains: committed local bytes In both cases the ignored bytes are lost. They are not in any commit, in the index, or in a stash, so `git merge --abort` cannot restore them. What's different between what you expected and what actually happened? Control case: if "incoming" instead adds new/private.dat directly (with `git add -f`, no rename involved), the same `git merge --no-edit --no-overwrite-ignore incoming` aborts with exit 1 and leaves "unique ignored bytes" intact: error: The following untracked working tree files would be overwritten by merge: new/private.dat Please move or remove them before you merge. Aborting So --no-overwrite-ignore works for a direct collision and fails only when the destination is produced by directory rename detection in the ort strategy. This matters because --no-overwrite-ignore is the only merge-level guard for ignored content such as local configuration, credential files, or build inputs that users deliberately keep out of history. A user or tool relying on it can lose unique, unversioned data, with no error at all or with only an unrelated rename conflict message. Anything else you want to add: Reproduced with identical results, in both modes, on: git version 2.56.0.138.gc61827130 (built from next) git version 2.56.0.50.gc46c1e3772 (built from master) git version 2.56.0 (Homebrew) git version 2.55.0 (Homebrew) git version 2.54.0 (Apple Git-157) all on macOS 27 (arm64, APFS), using the empty-configuration environment shown in the script. I am happy to test a patch. Regards, CoyGeek