git2-rs icon indicating copy to clipboard operation
git2-rs copied to clipboard

Doing in-memory merge conflict resolution

Open fin-ger opened this issue 3 years ago • 2 comments

Hi all,

first of all, I would like to thank you for providing this awesome libgit2 wrapper, including the searchable docs.rs documentation. This already helped me a lot in my project!

I am currently struggling to get an in-memory merge conflict resolution done with the libgit2. But let's first explain why I am seeking for in-memory conflict resolution:

  • I am currently building a rust-library for the pass password store. I would like to provide an easy-to-use API backed by git for decentralized synchronization (e.g. without a server) from device-to-device over e.g. the local network, ad-hoc wifi, etc. The original pass implementation (shell-script) just delegates the synchronization to git by mirroring the git cli. I therefore decided to base my synchronization approach on git as well, to make it (at least theoretically) compatible.
  • As from a user perspective a synchronization is either successful or not (maybe requiring some manual conflict resolution via e.g. a GUI popup), I would like to do the messing with git stages, etc. in-memory to prevent interfering with other tools handling a pass password store (e.g. the original cli, the qt intercace, the Firefox extension, etc.). Therefore, I thought that checking out a conflicted index to the repository is not a good idea. Also, if my lib crashes, is quit by the user, etc. my library would have to somehow continue a conflict resolution of a previous run. Furthermore, if multiple instances of my library or another pass implementation are running on the same repository I might run into race condition problems, etc. Those problems are ofc fixable with e.g. locks but it makes the implementation way more complex. That's why I settled with in-memory conflict resolution of a conflicted index.

So, what exactly is my problem? I have (somewhat) the following code:

let mut index = self.repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
// go over index.conflicts() and resolve them
// in pass passwords are gpg encrypted, not really relevant for this issue
index.add_frombuffer(new_index_entry, &encrypted_resolved_password_content)?;

However, this errors with could not initialize index entry. Index is not backed up by an existing repository. Which is true, I could not (yet) do e.g. a index.write_tree_to(repo) as my index still contains conflicts. So I tried another variant:

let mut index = self.repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
// go over index.conflicts() and resolve them
let oid = repo.blob(&encrypted_resolved_password_content)?;
index_entry.file_size = encrypted_resolved_password_content.len() as u32;
index_entry.id = oid;
index.add(&index_entry)?;

let result_tree = repo.find_tree(index.write_tree_to(repo)?)?;

But this fails on write_tree_to as the conflict has not been marked as resolved. I checked the libgit2 C source code and found out that only index.add_bypath and index.add_frombuffer mark a conflict as resolved, but not index.add (although I might have missed sth in the C source).

There are the git_index_conflict_remove and git_index_conflict_cleanup functions present in libgit2 but those are not available in the git2-rs wrapper. They are indeed available in the libgit2-sys crate but not usable as I require access to the raw pointers of the index in order to use them which I do not have in git2-rs. Also, I am not sure whether these functions are actually necessary to solve my problem, I have not checked this as I would like to avoid reimplementing my rust code in C to check whether these two functions would make it work.

I could not find working examples on how to resolve merge conflicts in-memory with git (wondering now if it's even possible...). My question is: are git_index_conflict_remove and git_index_conflict_cleanup necessary for this and if so, why are these functions missing from git2-rs?

Thank you in advance for any help!

fin-ger avatar Nov 28 '21 12:11 fin-ger

I'm not a maintainer of this project, but my guess is that nobody has needed these functions, and so nobody has implemented bindings for them.

As a workaround, you could try copying all the entries except the conflicting ones into a new index, or, since you no longer have conflicts, construct a tree object directly.

arxanas avatar Nov 29 '21 22:11 arxanas

@arxanas Thank you for your response. Copying the index sounds like something that could work. However, I now tried to add the missing functions myself, so that anyone else needing those does not end up with a workaround as well :D

Let's see if my trivial implementation approach is fine.

fin-ger avatar Nov 30 '21 11:11 fin-ger