]> git.proxmox.com Git - libgit2.git/blobdiff - src/merge_file.c
push: remove own copy of callbacks
[libgit2.git] / src / merge_file.c
index fb0e299604a23631ea96c9121eb28e4948f5338c..6d89b089dfc942e397aea92c796b74bbed607238 100644 (file)
@@ -8,6 +8,9 @@
 #include "common.h"
 #include "repository.h"
 #include "merge_file.h"
+#include "posix.h"
+#include "fileops.h"
+#include "index.h"
 
 #include "git2/repository.h"
 #include "git2/object.h"
@@ -22,17 +25,17 @@ GIT_INLINE(const char *) merge_file_best_path(
        const git_merge_file_input *ours,
        const git_merge_file_input *theirs)
 {
-       if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
-               if (strcmp(ours->path, theirs->path) == 0)
+       if (!ancestor) {
+               if (ours && theirs && strcmp(ours->path, theirs->path) == 0)
                        return ours->path;
 
                return NULL;
        }
 
-       if (strcmp(ancestor->path, ours->path) == 0)
-               return theirs->path;
-       else if(strcmp(ancestor->path, theirs->path) == 0)
-               return ours->path;
+       if (ours && strcmp(ancestor->path, ours->path) == 0)
+               return theirs ? theirs->path : NULL;
+       else if(theirs && strcmp(ancestor->path, theirs->path) == 0)
+               return ours ? ours->path : NULL;
 
        return NULL;
 }
@@ -47,134 +50,243 @@ GIT_INLINE(int) merge_file_best_mode(
         * assume executable.  Otherwise, if any mode changed from the ancestor,
         * use that one.
         */
-       if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
-               if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE ||
-                       theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE)
+       if (!ancestor) {
+               if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) ||
+                       (theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE))
                        return GIT_FILEMODE_BLOB_EXECUTABLE;
 
                return GIT_FILEMODE_BLOB;
-       }
+       } else if (ours && theirs) {
+               if (ancestor->mode == ours->mode)
+                       return theirs->mode;
 
-       if (ancestor->mode == ours->mode)
-               return theirs->mode;
-       else if(ancestor->mode == theirs->mode)
                return ours->mode;
+       }
 
        return 0;
 }
 
-int git_merge_file_input_from_index_entry(
-       git_merge_file_input *input,
-       git_repository *repo,
+int git_merge_file__input_from_index(
+       git_merge_file_input *input_out,
+       git_odb_object **odb_object_out,
+       git_odb *odb,
        const git_index_entry *entry)
 {
-       git_odb *odb = NULL;
        int error = 0;
 
-       assert(input && repo && entry);
-
-       if (entry->mode == 0)
-               return 0;
+       assert(input_out && odb_object_out && odb && entry);
 
-       if ((error = git_repository_odb(&odb, repo)) < 0 ||
-               (error = git_odb_read(&input->odb_object, odb, &entry->oid)) < 0)
+       if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
                goto done;
 
-       input->mode = entry->mode;
-       input->path = git__strdup(entry->path);
-       input->mmfile.size = git_odb_object_size(input->odb_object);
-       input->mmfile.ptr = (char *)git_odb_object_data(input->odb_object);
-
-       if (input->label == NULL)
-               input->label = entry->path;
+       input_out->path = entry->path;
+       input_out->mode = entry->mode;
+       input_out->ptr = (char *)git_odb_object_data(*odb_object_out);
+       input_out->size = git_odb_object_size(*odb_object_out);
 
 done:
-       git_odb_free(odb);
-
        return error;
 }
 
-int git_merge_file_input_from_diff_file(
-       git_merge_file_input *input,
-       git_repository *repo,
-       const git_diff_file *file)
+static void merge_file_normalize_opts(
+       git_merge_file_options *out,
+       const git_merge_file_options *given_opts)
 {
-       git_odb *odb = NULL;
-       int error = 0;
-
-       assert(input && repo && file);
-
-       if (file->mode == 0)
-               return 0;
-
-       if ((error = git_repository_odb(&odb, repo)) < 0 ||
-               (error = git_odb_read(&input->odb_object, odb, &file->oid)) < 0)
-               goto done;
-
-       input->mode = file->mode;
-       input->path = git__strdup(file->path);
-       input->mmfile.size = git_odb_object_size(input->odb_object);
-       input->mmfile.ptr = (char *)git_odb_object_data(input->odb_object);
-
-       if (input->label == NULL)
-               input->label = file->path;
-
-done:
-       git_odb_free(odb);
-
-       return error;
+       if (given_opts)
+               memcpy(out, given_opts, sizeof(git_merge_file_options));
+       else {
+               git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
+               memcpy(out, &default_opts, sizeof(git_merge_file_options));
+       }
 }
 
-int git_merge_files(
+static int git_merge_file__from_inputs(
        git_merge_file_result *out,
-       git_merge_file_input *ancestor,
-       git_merge_file_input *ours,
-       git_merge_file_input *theirs,
-       git_merge_file_options *opts)
+       const git_merge_file_input *ancestor,
+       const git_merge_file_input *ours,
+       const git_merge_file_input *theirs,
+       const git_merge_file_options *given_opts)
 {
        xmparam_t xmparam;
+       mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
        mmbuffer_t mmbuffer;
+       git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
+       const char *path;
        int xdl_result;
        int error = 0;
 
-       assert(out && ancestor && ours && theirs);
-
        memset(out, 0x0, sizeof(git_merge_file_result));
 
-       if (!GIT_MERGE_FILE_SIDE_EXISTS(ours) || !GIT_MERGE_FILE_SIDE_EXISTS(theirs))
-               return 0;
+       merge_file_normalize_opts(&options, given_opts);
 
        memset(&xmparam, 0x0, sizeof(xmparam_t));
-       xmparam.ancestor = ancestor->label;
-       xmparam.file1 = ours->label;
-       xmparam.file2 = theirs->label;
 
-       out->path = merge_file_best_path(ancestor, ours, theirs);
-       out->mode = merge_file_best_mode(ancestor, ours, theirs);
+       if (ancestor) {
+               xmparam.ancestor = (options.ancestor_label) ?
+                       options.ancestor_label : ancestor->path;
+               ancestor_mmfile.ptr = (char *)ancestor->ptr;
+               ancestor_mmfile.size = ancestor->size;
+       }
+
+       xmparam.file1 = (options.our_label) ?
+               options.our_label : ours->path;
+       our_mmfile.ptr = (char *)ours->ptr;
+       our_mmfile.size = ours->size;
 
-       if (opts && opts->favor == GIT_MERGE_FILE_FAVOR_OURS)
+       xmparam.file2 = (options.their_label) ?
+               options.their_label : theirs->path;
+       their_mmfile.ptr = (char *)theirs->ptr;
+       their_mmfile.size = theirs->size;
+
+       if (options.favor == GIT_MERGE_FILE_FAVOR_OURS)
                xmparam.favor = XDL_MERGE_FAVOR_OURS;
-       else if (opts && opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS)
+       else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
                xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
+       else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
+               xmparam.favor = XDL_MERGE_FAVOR_UNION;
 
-       xmparam.level = 
-               (opts && (opts->flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM)) ?
+       xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
                XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;
 
-       if (opts && opts->style == GIT_MERGE_FILE_STYLE_DIFF3)
+       if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
                xmparam.style = XDL_MERGE_DIFF3;
 
-       if ((xdl_result = xdl_merge(&ancestor->mmfile, &ours->mmfile,
-               &theirs->mmfile, &xmparam, &mmbuffer)) < 0) {
+       if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
+               xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;
+       if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE)
+               xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
+       if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL)
+               xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
+
+       if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE)
+               xmparam.xpp.flags |= XDF_PATIENCE_DIFF;
+
+       if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL)
+               xmparam.xpp.flags |= XDF_NEED_MINIMAL;
+
+       if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
+               &their_mmfile, &xmparam, &mmbuffer)) < 0) {
                giterr_set(GITERR_MERGE, "Failed to merge files.");
                error = -1;
                goto done;
        }
 
+       if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL &&
+               (out->path = strdup(path)) == NULL) {
+               error = -1;
+               goto done;
+       }
+
        out->automergeable = (xdl_result == 0);
-       out->data = (unsigned char *)mmbuffer.ptr;
+       out->ptr = (const char *)mmbuffer.ptr;
        out->len = mmbuffer.size;
+       out->mode = merge_file_best_mode(ancestor, ours, theirs);
+
+done:
+       if (error < 0)
+               git_merge_file_result_free(out);
+
+       return error;
+}
+
+static git_merge_file_input *git_merge_file__normalize_inputs(
+       git_merge_file_input *out,
+       const git_merge_file_input *given)
+{
+       memcpy(out, given, sizeof(git_merge_file_input));
+
+       if (!out->path)
+               out->path = "file.txt";
+
+       if (!out->mode)
+               out->mode = 0100644;
+
+       return out;
+}
+
+int git_merge_file(
+       git_merge_file_result *out,
+       const git_merge_file_input *ancestor,
+       const git_merge_file_input *ours,
+       const git_merge_file_input *theirs,
+       const git_merge_file_options *options)
+{
+       git_merge_file_input inputs[3] = { {0} };
+
+       assert(out && ours && theirs);
+
+       memset(out, 0x0, sizeof(git_merge_file_result));
+
+       if (ancestor)
+               ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);
+
+       ours = git_merge_file__normalize_inputs(&inputs[1], ours);
+       theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);
+
+       return git_merge_file__from_inputs(out, ancestor, ours, theirs, options);
+}
+
+int git_merge_file_from_index(
+       git_merge_file_result *out,
+       git_repository *repo,
+       const git_index_entry *ancestor,
+       const git_index_entry *ours,
+       const git_index_entry *theirs,
+       const git_merge_file_options *options)
+{
+       git_merge_file_input inputs[3] = { {0} },
+               *ancestor_input = NULL, *our_input = NULL, *their_input = NULL;
+       git_odb *odb = NULL;
+       git_odb_object *odb_object[3] = { 0 };
+       int error = 0;
+
+       assert(out && repo && ours && theirs);
+
+       memset(out, 0x0, sizeof(git_merge_file_result));
+
+       if ((error = git_repository_odb(&odb, repo)) < 0)
+               goto done;
+
+       if (ancestor) {
+               if ((error = git_merge_file__input_from_index(
+                       &inputs[0], &odb_object[0], odb, ancestor)) < 0)
+                       goto done;
+
+               ancestor_input = &inputs[0];
+       }
+
+       if ((error = git_merge_file__input_from_index(
+               &inputs[1], &odb_object[1], odb, ours)) < 0)
+               goto done;
+
+       our_input = &inputs[1];
+
+       if ((error = git_merge_file__input_from_index(
+               &inputs[2], &odb_object[2], odb, theirs)) < 0)
+               goto done;
+
+       their_input = &inputs[2];
+
+       if ((error = git_merge_file__from_inputs(out,
+               ancestor_input, our_input, their_input, options)) < 0)
+               goto done;
 
 done:
+       git_odb_object_free(odb_object[0]);
+       git_odb_object_free(odb_object[1]);
+       git_odb_object_free(odb_object[2]);
+       git_odb_free(odb);
+
        return error;
 }
+
+void git_merge_file_result_free(git_merge_file_result *result)
+{
+       if (result == NULL)
+               return;
+
+       git__free((char *)result->path);
+
+       /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */
+       free((char *)result->ptr);
+}