git merge-file fails with bash process substitution operator that uses git show

300 views Asked by At

I'm trying to merge changes from a file which is in git into one which isn't under version control (more context) using a 3-way-merge.

As there is a git merge-file <local> <base> <other> which expects 3 files and as I don't fancy creating intermediate files and forgetting to clean them up, i used the bash <(...) process substitution operator as i do quite often.

It turns out that this doesn't seem to work as expected as the following example shows on git 2.4.2 and 2.4.3 on two different systems.

Example:

Let's create a small test for this so we know what we're talking about. We'll create a file foo.txt (the one in git with 2 versions at least) and a file bar.txt which is not under version control:

git init
echo -e 'foo\nbar' > foo.txt
git add foo.txt
git commit -m'init'
echo -e 'foo\nbar\nend' > foo.txt
git commit -a -m'end'
echo -e 'start\nfoo\nbar' > bar.txt
git show HEAD^:foo.txt > foo.txt.base
git show master:foo.txt > foo.txt.end

This leaves us with a couple of files that look like this:

foo.txt @ HEAD^ & foo.txt.base:

foo
bar

foo.txt @ master & foo.txt.end:

foo
bar
end

bar.txt:

start
foo
bar

With intermediate files:

So now let's run git merge-file -p bar.txt foo.txt.base foo.txt.end:

start
foo
bar
end

This is the output i'd like to get with the following method as well.

With Process substitution:

But if i run: git merge-file -p bar.txt <(git show HEAD^:foo.txt) <(git show master:foo.txt), i get this output:

start
foo
bar

This is not expected and (sometimes!) echo $? prints 1 indicating an error.

What's even weirder is that as i couldn't understand this behavior i decided to re-run the above command (up-arrow) and was even more puzzled when this output appeared:

<<<<<<< bar.txt
start
foo
bar
=======
foo
bar
end
>>>>>>> /dev/fd/62

As i didn't really expect this fail, much less to be non-deterministic i ran again a couple of times to find this output as well:

<<<<<<< bar.txt
start
foo
bar
=======
>>>>>>> /dev/fd/62

To narrow down the problem i tried git merge-file -p bar.txt <(echo -e 'foo\nbar') <(echo -e 'foo\nbar\nend') which reliably prints the expected merge:

start
foo
bar
end

Question

Can someone explain this weird behavior?

I can obviously fix it by not using process substitution, but i'd like to understand why this happens as i'm using process substitution in a lot of bash scripts. Also i'd be interested in workarounds which allow me to still use process substitution.

Also if you think this is a bug i'm interested in which component it originates from: git show, git merge-file or bash?

1

There are 1 answers

0
Jörn Hees On BEST ANSWER

As pointed out in the comments and here this might be a problem with <(...) providing a FIFO like file that doesn't support seek. The same behavior is shown in ZSH unless invoked like this:

git merge-file -p bar.txt =(git show HEAD^:foo.txt) =(git show master:foo.txt)

This means that the problem originates from git merge-file expecting files that it can seek on, while <(...) doesn't provide these.