libgit2: Resolve merge conflict automatically - libgit2

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);
}

Related

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);

libgit2 how to get merge conflict preview in buffer?

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

Read 'hidden' input for CLI Dart app

What's the best way to receive 'hidden' input from a command-line Dart application? For example, in Bash, this is accomplished with:
read -s SOME_VAR
Set io.stdin.echoMode to false:
import 'dart:io' as io;
void main() {
io.stdin.echoMode = false;
String input = io.stdin.readLineSync();
// or
var input;
while(input != 32) {
input = io.stdin.readByteSync();
if(input != 10) print(input);
}
// restore echoMode
io.stdin.echoMode = true;
}
This is a slightly extended version, key differences are that it uses a finally block to ensure the mode is reset if an exception is thrown whilst the code is executing.
The code also uses a waitFor call (only available in dart cli apps) to turn this code into a synchronous call. Given this is a cli command there is no need for the complications that futures bring to the table.
The code also does the classic output of '*' as you type.
If you are doing much cli work the below code is from the dart package I'm working on called dcli. Have a look at the 'ask' method.
https://pub.dev/packages/dcli
String readHidden() {
var line = <int>[];
try {
stdin.echoMode = false;
stdin.lineMode = false;
int char;
do {
char = stdin.readByteSync();
if (char != 10) {
stdout.write('*');
// we must wait for flush as only one flush can be outstanding at a time.
waitFor<void>(stdout.flush());
line.add(char);
}
} while (char != 10);
} finally {
stdin.echoMode = true;
stdin.lineMode = true;
}
// output a newline as we have suppressed it.
print('');
return Encoding.getByName('utf-8').decode(line);
}

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.

How to use ngx_write_chain_to_temp_file correctly?

I am writing nginx module which construct nginx chain then write this chain buffer to nginx temporary file to use it later (just after write happen). I've been searching every page and the only solution come up is the one bellow:
// Create temp file to test
ngx_temp_file_t *tf;
tf = ngx_pcalloc(r->pool, sizeof (ngx_temp_file_t));
if (tf == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
tf->file.fd = NGX_INVALID_FILE;
tf->file.log = nlog;
tf->path = clcf->client_body_temp_path;
tf->pool = r->pool;
tf->log_level = r->request_body_file_log_level;
tf->persistent = r->request_body_in_persistent_file;
tf->clean = r->request_body_in_clean_file;
// if (r->request_body_file_group_access) {
// tf->access = 0660;
// }
if (ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent, tf->clean, tf->access) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (ngx_write_chain_to_temp_file(tf, bucket->first) == NGX_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
This code does not return NGX_ERROR, is this meant nginx successful write temporary file into client_body_temporay_path? It the answer is yes, after that, I use fopen to open file, the file is not exist?
Can anyone please give me the right solution to handle ngx_write_chain_to_temp_file?
I find myself the solution
ngx_temp_file_t *tf;
tf = ngx_pcalloc(r->pool, sizeof (ngx_temp_file_t));
tf->file.fd = NGX_INVALID_FILE;
tf->file.log = nlog;
tf->path = clcf->client_body_temp_path;
tf->pool = r->pool;
tf->persistent = 1;
rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent, tf->clean, tf->access);
//ngx_write_chain_to_file(&tf->file, bucket->first, bucket->content_length, r->pool);
ngx_write_chain_to_temp_file(tf, bucket->first);
The only thing I cannot understand is if I set tf->persistentto false (0), after the file created, I cannot read from it even if I've not passed response to output_filter yet.