# [BUG] merge-ort: --no-overwrite-ignore overwrites an ignored file at a directory-rename destination

1 messages from 2026-10-01 to 2026-10-01. Participants: Coy Geek.
Thread: https://gitlist.dev/t/66440

## Coy Geek, 2026-10-01 23:54

Subject: [BUG] merge-ort: --no-overwrite-ignore overwrites an ignored file at a directory-rename destination
Message-ID: <CACgTecNrMKHGexWTkm1bqpVzL7SVWD5Hwo17wVokVGe3_Rnx0A@mail.gmail.com>

```
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

```
