libgit2 how to get merge conflict preview in buffer? - libgit2

I'm want use libgit2 get a preview of a conflict commit in a bare repo, which means there is no workdir.
have using those functions, git_merge_commits git_index_has_conflicts and git_index_conflict_get, to get relative infomation.
Main code like this:
git_index *index;
int error = git_merge_commits(&index, repo, git_commit1, git_commit2, NULL);
int conflict_count = git_index_has_conflicts(index);
git_oid index_tree_oid, index_commit_oid;
git_tree *index_tree;
if(conflict_count == 0) {
// do commit the index
} else {
// solve conflict
const git_index_entry *ancestor = NULL,
*ours = NULL,
*theirs = NULL;
error = git_index_conflict_get(
&ancestor,
&ours,
&theirs,
index,
"file2");
error_check(error);
...
How to get preview of the merge in buffer
contains <<<< HEAD like this?
aaaaaaaa
bbbbbbbb22222222
<<<<<<< HEAD
bbbbbbbb11111111
bbbbbbbb11111111
=======
bbbbbbbb22222222
>>>>>>> b3
aaaaaaaa
aaaaaaaa
aaaaaaaa
Currently, a merge only has one file can conflict.
Thanks

Related

libgit2: Resolve merge conflict automatically

I'm writing a program that is using libgit2 with a repo for management of config files across multiple instances. If there is a conflicting merge I would like to automatically resolve this (by picking the latest commits), without user interaction.
After performing a git_merge I get the git_repository_index and check if git_index_has_conflicts.
When I iterate over the conflicts I am able to get the git_index_entry for the contributing parts, but here is where my basic understanding of git falls short. My approach is to resolve what git_commit that relates to the git_index_entry and check the commit time to determine which entry to pick for merged solution. At the moment I'm a bit stuck on this so any pointers on how to go about doing or if there is a better approach to this problem is much appreciated.
I need to use the libgit2 API to solve this.
Here is some simplified sample code to show my approach:
void merge(const git_a_repository* a_repo, const git_signature* a_signature)
{
//Get the FETCH_HEAD annotated commit
git_annotated_commit* annotated = nullptr;
git_object* obj = nullptr;
git_revparse_single(&obj, a_repo, "FETCH_HEAD");
git_annotated_commit_lookup(annotated, a_repo, git_object_id(obj));
git_object_free(obj);
//Omitting git_merge_analysis for simplicity
//and assuming merge condition
//Merge the FETCH_HEAD with HEAD
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE
| GIT_CHECKOUT_ALLOW_CONFLICTS;
git_merge(a_repo,(const git_annotated_commit**)(&annotated),
1, &merge_opts, &checkout_opts);
git_index* index = nullptr;
git_a_repository_index(&index, a_repo);
//Handle conflicts
if (git_index_has_conflicts(index))
{
const git_index_entry* ancestor = nullptr;
const git_index_entry* our = nullptr;
const git_index_entry* their = nullptr;
git_index_conflict_iterator* conflicts = nullptr;
int err = 0;
std::vector entriesToKeep;
git_index_conflict_iterator_new(&conflicts, index);
while ((err = git_index_conflict_next(&ancestor, &our, &their, conflicts))
== GIT_OK)
{
git_commit* ourcommit = nullptr;
git_commit* theircommit = nullptr;
//TODO: Need to find the commit belonging to git_index_entry
// to look up the comit time and use this to select
// which git_index_entry should be staged
//entriesToKeep.push_back(selectedEntry);
}
for (auto r : entriesToKeep)
{
git_index_add(index, r);
}
git_index_conflict_iterator_free(conflicts);
git_index_conflict_cleanup(index);
}
//Commit merge
git_tree* tree = nullptr;
git_oid tree_oid, commit_oid;
git_index_write_tree(&tree_oid, index);
git_tree_lookup(&tree, a_repo, &tree_oid);
git_reference* head_ref = nullptr;
git_a_repository_head(&head_ref, a_repo);
git_commit** parents = (git_commit**)(calloc(2, sizeof(git_commit*)));
git_reference_peel((git_object**)&parents[0], head_ref, GIT_OBJECT_COMMIT);
git_commit_lookup(&parents[1], a_repo, git_annotated_commit_id(annotated));
git_commit_create(&commit_oid, a_repo, git_reference_name(head_ref),
a_signature, a_signature, NULL, "Merge", tree, 2,
(const git_commit**)parents);
free(parents);
git_tree_free(tree);
git_annotated_commit_free(annotated);
git_a_repository_state_cleanup(a_repo);
}

libgit2: How to mark resolution

I can't figure out how to mark a resolution after resolving merge conflicts with libgit2. calling git status on the repo returns Unmerged paths: and says both added: <'myfilename'>
Edit: Her is example code of how I got it to work for future reference.
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
git_merge(repo, annotated_commit, 1, &merge_opts, &checkout_opts);
git_index* index = nullptr;
git_repository_index(&index, repo);
if (git_index_has_conflicts(index))
{
git_index_conflict_iterator* conflicts = nullptr;
git_index_entry entries[3];
git_index_conflict_iterator_new(&conflicts, index);
while (git_index_conflict_next(&entries[0], &entries[1], &entries[2], conflicts)
!= GIT_ITEROVER)
{
//Write desired content to file in working copy
handle_conflict(entries[2]->path);
git_index_conflict_remove(index, entries[2]->path);
git_index_add_bypath(index, entries[2]->path);
}
git_index_conflict_iterator_free(conflicts);
git_index_conflict_cleanup(index);
//At this point index does not have more conflicts so write it
git_index_write(index);
}
git_commit* parents[2];
git_reference* head_ref = nullptr;
git_repository_head(&head_ref, repo);
git_reference_peel((git_object**)&parents[0], head_ref, GIT_OBJECT_COMMIT);
git_commit_lookup(&parents[1], repo, git_annotated_commit_id(annotated));
git_tree* tree = nullptr;
git_oid tree_oid, commit_oid;
git_index_write_tree(&tree_oid, index);
git_tree_lookup(&tree, repo, &tree_oid);
git_commit_create(&commit_oid, repo,
git_reference_name(head_ref), signature, signature, NULL, "Merge",
tree, 2, (const git_commit**)parents);
git_tree_free(tree);

failed to create commit: current tip is not the first parent error during commit in libgit2

I am using libgit2 v0.23.0 library for git pull & commit operation. I am calling git_merge(repo,their_heads,1,&merge_opt,&checkout_opts); method & it works fine & merge the changes from remote repository to local repository. But after that when I am calling git_commit_create() method, it throw error as failed to create commit: current tip is not the first parent with error code -15.
I investigate & found that FETCH_HEAD and MERGE_HEAD file contains updated oid, but ORIG_HEAD still containing previous/outdated oid. I am not sure this is the cause of error which I am getting during git_commit_create().
int fetch()
{
qDebug()<<"Fetch";
git_remote *remote = NULL;
const git_transfer_progress *stats;
struct dl_data data;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
git_repository *repo = NULL;
QString repoPath = "repopath/.git";
int error = git_repository_open(&repo, repoPath.toStdString().c_str());
if (git_remote_lookup(&remote, repo, "origin") < 0) {
if (git_remote_create_anonymous(&remote, repo,"repoURL") < 0)
return -1;
}
fetch_opts.callbacks.update_tips = &update_cb;
fetch_opts.callbacks.sideband_progress = &progress_cb;
fetch_opts.callbacks.credentials = cred_acquire_cb;
data.remote = remote;
data.fetch_opts = &fetch_opts;
data.ret = 0;
data.finished = 0;
stats = git_remote_stats(remote);
download(&data);
if (stats->local_objects > 0) {
printf("\rReceived %d/%d objects in % bytes (used %d local objects)\n",
stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects);
} else{
printf("\rReceived %d/%d objects in %bytes\n",
stats->indexed_objects, stats->total_objects, stats->received_bytes);
}
git_remote_disconnect(remote);
if (git_remote_update_tips(remote, &fetch_opts.callbacks, 1, fetch_opts.download_tags, NULL) < 0)
return -1;
const git_remote_head **head = NULL;
size_t size = 0;
(git_remote_ls(&head, &size, remote));
git_oid oid = head[0]->oid;
char * commit_id1 = new char[41]; //Commit ID
qDebug()<<"oid:"<<git_oid_tostr(commit_id1, 41, &oid);
git_annotated_commit *anno_out ;
git_annotated_commit_lookup(&anno_out,repo,&oid);
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
const git_annotated_commit **their_heads = const_cast<const git_annotated_commit**>(&anno_out);
git_merge_options merge_opt = GIT_MERGE_OPTIONS_INIT;
merge_opt.file_favor = GIT_MERGE_FILE_FAVOR_UNION;
error = git_merge(repo,their_heads,1,&merge_opt,&checkout_opts);
if(error!=0){
//Error handling
}
else{
qDebug()<<"Merge successfully";
}
git_repository_state_cleanup(repo);
/* Create signature */
git_signature *me = NULL;
(git_signature_now(&me, "username", "username#gmail.com"));
//Tree Lookup
git_tree *tree;
git_object *tree_obj;
(git_revparse_single(&tree_obj, repo, "HEAD^{tree}"));
// Get parent commit
git_oid parentCommitId;
git_commit *parent;
git_oid remoteParentCommitId;
git_commit *remoteParent;
int nparents;
int err;
(git_reference_name_to_id( &parentCommitId, repo, "ORIG_HEAD" ));
(git_commit_lookup( &parent, repo, &parentCommitId ));
(git_reference_name_to_id( &remoteParentCommitId, repo, "MERGE_HEAD" ));
(git_commit_lookup( &remoteParent, repo, &remoteParentCommitId ));
const git_commit *parents [1] = {remoteParent };
git_oid new_commit_id;
err = (git_commit_create(
&new_commit_id,
repo,
"HEAD", /* name of ref to update */
me, /* author */
me, /* committer */
"UTF-8", /* message encoding */
"pull fetch", /* message */
(git_tree *) tree_obj, /* root tree */
1, /* parent count */
parents)); /* parents */
if(err !=0){
//I am getting error here
}
git_remote_free(remote);
return 0;
}
Please suggest me what I have to do in order to resolve this issue ?
Generally, you're seeing this error because you are building a new commit on a branch whose parent is not the current tip of the branch. Indeed, you're building a new commit whose parent is the remote commit not the local one.
There are a few problems:
Some error checking on all the functions is recommended. I see some functions that are likely failing but there is no check for that. For example:
Don't call git_repository_state_cleanup in the middle of your operation. That will abort the merge and cleanup the state files that you're trying to read later. Like MERGE_HEAD.
You're doing a merge. You should have two parent commits (the two commits you're merging) to the new commit. You should pass { parent, remoteParent } as the parents.

libgit2 - need help to understand how git_diff_foreach works

I'm using libgit2 0.22.2, odb/refdb backed by mysql. I was testing git_diff_foreach function, but not sure how it works, please advise!
Below is my testing data and code, I put a blob 'README.md' into odb and commit, the content was 'commit' and later I put another blob with same name into odb and commit, this time the content was 'commit test'
I was trying to diff the 2 tree to print diff deltas in 3 callbacks (file, hunk, line). But only the file callback printed something
update:
i debugged into the foreach function, there's error in diff.h git_diff_file__resolve_zero_size function. after return statement error pops up:
Run-Time Check Failure #2 - Stack around the variable 'len' was corrupted.
code:
get diff:
int error;
git_object *commit;
git_commit *parent = NULL;
error = git_revparse_single(&commit, repo, "HEAD^{commit}");
error = git_commit_parent(&parent, (git_commit *)commit, 0);
git_tree *commit_tree = NULL, *parent_tree = NULL;
error = git_commit_tree(&commit_tree, (git_commit *)commit);
error = git_commit_tree(&parent_tree, parent);
git_diff *diff = NULL;
error = git_diff_tree_to_tree(&diff, repo, commit_tree, parent_tree, NULL);
git_mysql_tree_diff(mysql, repo, diff);
git_object_free(commit);
git_commit_free(parent);
git_tree_free(commit_tree);
git_tree_free(parent_tree);
git_diff_free(diff);
iterate diff:
int each_file_cb(const git_diff_delta *delta,float progress,void *payload)
{
printf("new file:%s \n", delta->new_file.path);
printf("old file:%s \n", delta->old_file.path);
return 0;
}
int each_hunk_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
void *payload)
{
printf("hunk header:%s \n", hunk->header);
return 0;
}
int each_line_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *payload)
{
printf("line content:%s \n", line->content);
return 0;
}
int git_mysql_tree_diff(git_diff *diff){
int error;
error = git_diff_foreach(diff,
each_file_cb,
each_hunk_cb,
each_line_cb,
NULL);
return error;
}
Data:
# oid, type, size, data
'92168310a2a8e8d6577028d97931fd104965f6c5', '2', '37', 100644 README.md �����؊�J��B���f�N
'9f4ee02f459f6a28fa7a129fde6653d971429336', '1', '248', tree 92168310a2a8e8d6577028d97931fd104965f6c5 parent ad6aa3d0fcecefa5e9ea250a069da4d3be1f8e3d author Jerry Jin <jerry.yang.jin#gmail.com> 1430376253 +0800 committer Jerry Jin <jerry.yang.jin#gmail.com> 1430376253 +0800 encoding UTF-8 update readme
'ad6aa3d0fcecefa5e9ea250a069da4d3be1f8e3d', '1', '201', tree af0f65dba4ddecf5f4faf889cbd2852c341958c7 author Jerry Jin <jerry.yang.jin#gmail.com> 1430376223 +0800 committer Jerry Jin <jerry.yang.jin#gmail.com> 1430376223 +0800 encoding UTF-8 Initial Commit
'af0f65dba4ddecf5f4faf889cbd2852c341958c7', '2', '37', 100644 README.md ��vX"��}���lW�`#�#
'f8a612e3f687d88a984acaf41142ba85a3668d4e', '3', '11', commit test
'fcad765822a69c7de41bb1db186c57b160409923', '3', '6', commit
Output:
file progress:0.000000
after fixing memory issue, diff each printed below
new file:README.md
old file:README.md
hunk header:## -1 +1 ##
line content:commit testd
line content:
\ No newline at end of file
line content:commit;
line content:
\ No newline at end of file

Un-stage file with libgit2

Using objective-git and libgit2 it has been fairly easy to stage a file ready for commit:
GTIndex *repoIndex = [self.repository indexWithError:&error];
[repoIndex removeFile:path error:&error];
if (status != GTFileStatusIgnored && status != GTFileStatusWorkingDeleted) {
// Now add the file to the index
[repoIndex addFile:path error:&error];
}
[repoIndex write:&error];
However un-staging a file is proving to be a tad more tricky. Simply removing it from the repository's index doesn't work as git then thinks the file has been deleted which makes sense. It seems what I need to do is change the index entry in the index to the one it was before it was staged.
I have tried doing the following, using diff to get the old diff delta and constructing a git_index_entry from that and inserting it:
GTIndex *repoIndex = [self.repository indexWithError:&error];
GTBranch *localBranch = [self.repository currentBranchWithError:&error];
GTCommit *localCommit = [localBranch targetCommitAndReturnError:&error];
GTDiff *indexCommitDiff = [GTDiff diffIndexFromTree:localCommit.tree inRepository:self.repository options:nil error:&error];
// Enumerate through the diff deltas until we get to the file we want to unstage
[indexCommitDiff enumerateDeltasUsingBlock:^(GTDiffDelta *delta, BOOL *stop) {
NSString *deltaPath = delta.newFile.path;
// Check if it matches the path we want to usntage
if ([deltaPath isEqualToString:path]) {
GTDiffFile *oldFile = delta.oldFile;
NSString *oldFileSHA = oldFile.OID.SHA;
git_oid oldFileOID;
int status = git_oid_fromstr(&oldFileOID, oldFileSHA.fileSystemRepresentation);
git_index_entry entry;
entry.mode = oldFile.mode;
entry.oid = oldFileOID;
entry.path = oldFile.path.fileSystemRepresentation;
[repoIndex removeFile:path error:&error];
status = git_index_add(repoIndex.git_index, &entry);
[repoIndex write:&error];
}
}];
However this leaves the git index in a corrupt state resulting in any git command logging a fatal error:
fatal: Unknown index entry format bfff0000
fatal: Unknown index entry format bfff0000
What is the correct way to un-stage a file using libgit2?
Remember that Git is snapshot-based, so whatever is in the index at commit time will be what's committed.
There is no unstage action by itself, as it's context-dependent. If the file is new, then removing it is what you do to unstage it. Otherwise, unstaging and staging are the same operation, with the difference that the blob you use in the entry is the old version of the file.
Since you want to move files back to their state in HEAD, there shouldn't be a need to use diff, but take the tree of HEAD and look up the entries you want there (though from a quick glance, I don't see objective-git wrapping git_tree_entry_bypath())
If we write out a bad version of the index, that's definitely a bug in the library, could you open an issue so we can try to reproduce it?