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?
As pointed out in the comments and here this might be a problem with
<(...)
providing a FIFO like file that doesn't supportseek
. The same behavior is shown in ZSH unless invoked like this:This means that the problem originates from
git merge-file
expecting files that it canseek
on, while<(...)
doesn't provide these.