1

Usually, when you merge two branches, the conflicts appears in the files with something like:

<<<<<<<<<< HEAD

However, I the merge I'm trying to do has conflicts, but I don't know why the file is not changed at all, and therefore does not contain any <<<<<< HEAD inside.

Here are the command I tried:

$ md5sum myfile.txt 
76dd0814b656b23a61d7e8204cd776de  myfile.txt
$ git merge --no-ff tmp_branch
warning: Cannot merge binary files: myfile.txt (HEAD vs. tmp_branch)
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt
Automatic merge failed; fix conflicts and then commit the result.
$ md5sum myfile.txt 
76dd0814b656b23a61d7e8204cd776de  myfile.txt
$ git status
On branch master

Your branch is up to date with 'origin/master'.

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:   myfile.txt

no changes added to commit (use "git add" and/or "git commit -a")

I don't know if it can have any impact, but I'm using git-crypt as backend.

-- EDIT 01 -- I'm pretty sure that it's due a bug in git-crypt. I came up with a quite dirty solution, but that seems to work in the meantime:

First, manually copy the files from the common ancestor commit:

git checkout <your commit>
cp yourfile.txt yourfile_base.txt

Then, manually copy the files in the foreign branch:

git checkout <your branch>
cp yourfile.txt yourfile_other_branch.txt

And finally come back to your main branch and use git merge-file with something like that:

git checkout master
git merge-file yourfile.txt yourfile_base.txt yourfile_other_branch.txt

Then, you can open yourfile.txt, search for the string <<<<< and manually correct the conflicts! If you have a quicker workaround, please let me know!

-- EDIT 02 -- I found a quicker way: instead of checkout & copy, you can do it in one command:

git show <your commit>:./yourfile.txt | git-crypt smudge > ./yourfile.txt<

-- EDIT 03 --

After the suggestion of torek, I finally found a way to solve the problem:

First, add in your .gitattributes an option merge=git-crypt for all files managed by git-crypt:

crypt/** filter=git-crypt diff=git-crypt merge=git-crypt

then, add at the end of the file .git/config the following stuff:

[merge "git-crypt"]
    name = A custom merge driver used to merge git-crypted files.
    driver = ./my-merge-tool.sh %O %A %B
    recursive = binary

and finally create a file at the root of the repo my-merge-tool.sh containing:

ancestor_decrypted="$1__decrypt"
current_decrypted="$2__decrypt"
other_decrypted="$3__decrypt"
echo ""
echo "###########################"
echo "# Git crypt driver called #"
echo "###########################"
echo ""

echo "Decrypting ancestor file..."
cat $1 | git-crypt smudge > "${ancestor_decrypted}"
echo "Decrypting current file..."
cat $2 | git-crypt smudge > "${current_decrypted}"
echo "Decrypting other file..."
cat $3 | git-crypt smudge > "${other_decrypted}"
echo ""

echo "Merging ..."
git merge-file -L "current branch" -L "ancestor branch" -L "other branch" "${current_decrypted}" "${ancestor_decrypted}" "${other_decrypted}"
exit_code=$?
cat "${current_decrypted}" | git-crypt clean > $2

echo "Removing temporary files..."
rm "${other_decrypted}" "${ancestor_decrypted}" "${current_decrypted}"

if [ "$exit_code" -eq "0" ]
then
    echo "@@@ No conflict!"
else
    echo "@@@ You need to solve some conflicts..."
fi

exit $exit_code

and make sure it's executable:

chmod +x my-merge-tool.sh

That's all, you can now merge, cherry-pick, and even use your favorite mergetool as usual !

tobiasBora
  • 1,542
  • 14
  • 23

1 Answers1

4

It's not precisely really a bug in git-crypt, it's a side effect of the way Git does merging vs the way git-crypt does encryption. Git only sees the "cleaned" files, which are the encrypted files. Git literally can't merge anything because of that.

You can, however, supply your own merge driver, and there is an outstanding GitHub pull request that claims to do this. It's been out for more than a year with no motion on it (and I see you found it yourself, and noted that it did not work for you). It may well just not do enough. I have not investigated the code within git-crypt itself to say one way or another.

A proper version of the merge driver would perform the merge, and if the result is unconflicted, exit with a zero status. Git would then use the resulting %A version as the merge result. If the file is conflicted, the merge driver should leave conflict markers in the %A file. To make this work particularly well (to make it work with git mergetool for instance), you might even want to decrypt all three inputs and use git update-index to enter them into the index as stages 1, 2, and 3. Note that this has security implications that must be spelled out very carefully in additional documentation. (In particular this would leave un-encrypted loose objects; you might want to store them in alternate object directories, purpose-created for the duration of the merge, and have a post-merge clean-up step to remove them, but even then there's a risk of them becoming packed objects. To reduce, but not eliminate, that risk, before creating the loose objects you could run a git gc.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for this explanation. The thing is that I don't understand why it's not possible to do a stupid merge driver, that basically does what I put in my answer: for each file that needs to be merged, use `git show ... | git-crypt smudge > a_temp_file` for both the based file and the file on the other branch, then apply a `git-merge file` on these three file, and finally delete the useless temp files. – tobiasBora Jan 27 '18 at 23:41
  • That's pretty much how you'd write the simple (non-fancy, not-`git-mergetool` ready) version of the driver. Note that your driver is invoked with file name arguments which would contain the encrypted data: you won't know which *commit* contains the data, but you *have* the data in three input files (merge base and both left and right branch tip files). Decrypt and merge to appropriate (`%A`) file path, deleting the decrypted files. – torek Jan 27 '18 at 23:44
  • Great, thank you, that works great! Do you know now how I could create a custom git-mergetool ? – tobiasBora Jan 28 '18 at 00:47
  • That's rather a bit harder. When Git fails to merge files, what it does is leave them in the index (which really means: a Git hash ID in the index, with the raw file contents in the repository). In this case the raw data are encrypted, so any existing merge tool will have the same problem as Git. It's up to you to figure out how you want to work around this. See also, e.g., https://stackoverflow.com/a/44083266/1256452 – torek Jan 28 '18 at 01:09
  • Hum, in fact are you sure it's not working? Because I just tried to put in my .git/config : "[merge] tool = meld" and it works nicely! – tobiasBora Jan 28 '18 at 01:37
  • That should be fine for non-encrypted files. For encrypted files ... hm, maybe it *will* work, the script uses `git show` to extract them, which will filter them! (You shouldn't need an extra smudge step, if the `.gitattributes` file defines a smudge filter for them.) – torek Jan 28 '18 at 01:47
  • What do you mean by "which will filter them"? And if I don't do the `smudge`, `git show` outputs binary... – tobiasBora Jan 28 '18 at 02:25
  • Ah, I might be wrong. Git has, for various commands, `--textconv` or `--no-textconv` and I thought `git show` defaulted to using `--textconv`, which applies smudge filters. But perhaps you need `--textconv` as an argument to `git show` to make that happen automatically. – torek Jan 28 '18 at 02:45