diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/global.c | 2 | ||||
-rw-r--r-- | src/merge.c | 223 | ||||
-rw-r--r-- | src/merge.h | 63 | ||||
-rw-r--r-- | src/merge_driver.c | 396 | ||||
-rw-r--r-- | src/merge_driver.h | 60 | ||||
-rw-r--r-- | src/merge_file.c | 60 |
6 files changed, 690 insertions, 114 deletions
diff --git a/src/global.c b/src/global.c index c725b5184..cbd12ddda 100644 --- a/src/global.c +++ b/src/global.c @@ -9,6 +9,7 @@ #include "hash.h" #include "sysdir.h" #include "filter.h" +#include "merge_driver.h" #include "openssl_stream.h" #include "thread-utils.h" #include "git2/global.h" @@ -59,6 +60,7 @@ static int init_common(void) if ((ret = git_hash_global_init()) == 0 && (ret = git_sysdir_global_init()) == 0 && (ret = git_filter_global_init()) == 0 && + (ret = git_merge_driver_global_init()) == 0 && (ret = git_transport_ssh_global_init()) == 0) ret = git_openssl_stream_global_init(); diff --git a/src/merge.c b/src/merge.c index d2f92ccce..a0f2405ff 100644 --- a/src/merge.c +++ b/src/merge.c @@ -29,6 +29,7 @@ #include "annotated_commit.h" #include "commit.h" #include "oidarray.h" +#include "merge_driver.h" #include "git2/types.h" #include "git2/repository.h" @@ -50,18 +51,6 @@ #define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) -/** Internal merge flags. */ -enum { - /** The merge is for a virtual base in a recursive merge. */ - GIT_MERGE__VIRTUAL_BASE = (1 << 31), -}; - -enum { - /** Accept the conflict file, staging it as the merge result. */ - GIT_MERGE_FILE_FAVOR__CONFLICTED = 4, -}; - - typedef enum { TREE_IDX_ANCESTOR = 0, TREE_IDX_OURS = 1, @@ -273,7 +262,7 @@ int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two) { int error; - git_revwalk *walk; + git_revwalk *walk; git_commit_list *result, *list; git_array_oid_t array; @@ -810,76 +799,158 @@ static int merge_conflict_resolve_one_renamed( return error; } -static int merge_conflict_resolve_automerge( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict, - const git_merge_file_options *file_opts) +static bool merge_conflict_can_resolve_contents( + const git_merge_diff *conflict) { - const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_merge_file_result result = {0}; - git_index_entry *index_entry; - git_odb *odb = NULL; - git_oid automerge_oid; - int error = 0; - - assert(resolved && diff_list && conflict); - - *resolved = 0; - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return 0; + return false; /* Reject D/F conflicts */ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) - return 0; + return false; /* Reject submodules. */ if (S_ISGITLINK(conflict->ancestor_entry.mode) || S_ISGITLINK(conflict->our_entry.mode) || S_ISGITLINK(conflict->their_entry.mode)) - return 0; + return false; /* Reject link/file conflicts. */ - if ((S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->our_entry.mode)) || - (S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->their_entry.mode))) - return 0; + if ((S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->our_entry.mode)) || + (S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->their_entry.mode))) + return false; /* Reject name conflicts */ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; + return false; if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) + return false; + + return true; +} + +static int merge_conflict_invoke_driver( + git_index_entry **out, + const char *name, + git_merge_driver *driver, + git_merge_diff_list *diff_list, + git_merge_driver_source *src) +{ + git_index_entry *result; + git_buf buf = GIT_BUF_INIT; + const char *path; + uint32_t mode; + git_odb *odb = NULL; + git_oid oid; + int error; + + *out = NULL; + + if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || + (error = git_repository_odb(&odb, src->repo)) < 0 || + (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJ_BLOB)) < 0) + goto done; + + result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); + GITERR_CHECK_ALLOC(result); + + git_oid_cpy(&result->id, &oid); + result->mode = mode; + result->file_size = buf.size; + + result->path = git_pool_strdup(&diff_list->pool, path); + GITERR_CHECK_ALLOC(result->path); + + *out = result; + +done: + git_buf_free(&buf); + git_odb_free(odb); + + return error; +} + +static int merge_conflict_resolve_contents( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict, + const git_merge_options *merge_opts, + const git_merge_file_options *file_opts) +{ + git_merge_driver_source source = {0}; + git_merge_file_result result = {0}; + git_merge_driver *driver; + git_merge_driver__builtin builtin = {{0}}; + git_index_entry *merge_result; + git_odb *odb = NULL; + const char *name; + bool fallback = false; + int error; + + assert(resolved && diff_list && conflict); + + *resolved = 0; + + if (!merge_conflict_can_resolve_contents(conflict)) return 0; - ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + source.repo = diff_list->repo; + source.default_driver = merge_opts->default_driver; + source.file_opts = file_opts; + source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? &conflict->ancestor_entry : NULL; - ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? &conflict->our_entry : NULL; - theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? &conflict->their_entry : NULL; - if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 || - (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, file_opts)) < 0 || - (!result.automergeable && !(file_opts->flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) || - (error = git_odb_write(&automerge_oid, odb, result.ptr, result.len, GIT_OBJ_BLOB)) < 0) - goto done; + if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { + /* if the user requested a particular type of resolution (via the + * favor flag) then let that override the gitattributes and use + * the builtin driver. + */ + name = "text"; + builtin.base.apply = git_merge_driver__builtin_apply; + builtin.favor = file_opts->favor; + + driver = &builtin.base; + } else { + /* find the merge driver for this file */ + if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) + goto done; + + if (driver == NULL) + fallback = true; + } - if ((index_entry = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry))) == NULL) - GITERR_CHECK_ALLOC(index_entry); + if (driver) { + error = merge_conflict_invoke_driver(&merge_result, name, driver, + diff_list, &source); - index_entry->path = git_pool_strdup(&diff_list->pool, result.path); - GITERR_CHECK_ALLOC(index_entry->path); + if (error == GIT_PASSTHROUGH) + fallback = true; + } - index_entry->file_size = result.len; - index_entry->mode = result.mode; - git_oid_cpy(&index_entry->id, &automerge_oid); + if (fallback) { + error = merge_conflict_invoke_driver(&merge_result, "text", + &git_merge_driver__text.base, diff_list, &source); + } - git_vector_insert(&diff_list->staged, index_entry); + if (error < 0) { + if (error == GIT_EMERGECONFLICT) + error = 0; + + goto done; + } + + git_vector_insert(&diff_list->staged, merge_result); git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); *resolved = 1; @@ -895,6 +966,7 @@ static int merge_conflict_resolve( int *out, git_merge_diff_list *diff_list, const git_merge_diff *conflict, + const git_merge_options *merge_opts, const git_merge_file_options *file_opts) { int resolved = 0; @@ -902,16 +974,20 @@ static int merge_conflict_resolve( *out = 0; - if ((error = merge_conflict_resolve_trivial(&resolved, diff_list, conflict)) < 0) + if ((error = merge_conflict_resolve_trivial( + &resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_one_removed(&resolved, diff_list, conflict)) < 0) + if (!resolved && (error = merge_conflict_resolve_one_removed( + &resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_one_renamed(&resolved, diff_list, conflict)) < 0) + if (!resolved && (error = merge_conflict_resolve_one_renamed( + &resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, file_opts)) < 0) + if (!resolved && (error = merge_conflict_resolve_contents( + &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) goto done; *out = resolved; @@ -1627,6 +1703,7 @@ static int merge_normalize_opts( const git_merge_options *given) { git_config *cfg = NULL; + git_config_entry *entry = NULL; int error = 0; assert(repo && opts); @@ -1644,6 +1721,22 @@ static int merge_normalize_opts( opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; } + if (given && given->default_driver) { + opts->default_driver = git__strdup(given->default_driver); + GITERR_CHECK_ALLOC(opts->default_driver); + } else { + error = git_config_get_entry(&entry, cfg, "merge.default"); + + if (error == 0) { + opts->default_driver = git__strdup(entry->value); + GITERR_CHECK_ALLOC(opts->default_driver); + } else if (error == GIT_ENOTFOUND) { + error = 0; + } else { + goto done; + } + } + if (!opts->target_limit) { int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); @@ -1666,7 +1759,9 @@ static int merge_normalize_opts( opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; } - return 0; +done: + git_config_entry_free(entry); + return error; } @@ -1878,7 +1973,7 @@ int git_merge__iterators( int resolved = 0; if ((error = merge_conflict_resolve( - &resolved, diff_list, conflict, &file_opts)) < 0) + &resolved, diff_list, conflict, &opts, &file_opts)) < 0) goto done; if (!resolved) { @@ -1899,6 +1994,8 @@ done: if (!given_opts || !given_opts->metric) git__free(opts.metric); + git__free((char *)opts.default_driver); + git_merge_diff_list__free(diff_list); git_iterator_free(empty_ancestor); git_iterator_free(empty_ours); @@ -2111,14 +2208,14 @@ static int merge_annotated_commits( git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; int error; - if ((error = compute_base(&base, repo, ours, theirs, opts, + if ((error = compute_base(&base, repo, ours, theirs, opts, recursion_level)) < 0) { - if (error != GIT_ENOTFOUND) - goto done; + if (error != GIT_ENOTFOUND) + goto done; - giterr_clear(); - } + giterr_clear(); + } if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || diff --git a/src/merge.h b/src/merge.h index bd839be49..f8cac161f 100644 --- a/src/merge.h +++ b/src/merge.h @@ -12,8 +12,9 @@ #include "pool.h" #include "iterator.h" -#include "git2/merge.h" #include "git2/types.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" #define GIT_MERGE_MSG_FILE "MERGE_MSG" #define GIT_MERGE_MODE_FILE "MERGE_MODE" @@ -22,6 +23,19 @@ #define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 #define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 + +/** Internal merge flags. */ +enum { + /** The merge is for a virtual base in a recursive merge. */ + GIT_MERGE__VIRTUAL_BASE = (1 << 31), +}; + +enum { + /** Accept the conflict file, staging it as the merge result. */ + GIT_MERGE_FILE_FAVOR__CONFLICTED = 4, +}; + + /** Types of changes when files are merged from branch to branch. */ typedef enum { /* No conflict - a change only occurs in one branch. */ @@ -70,7 +84,6 @@ typedef enum { GIT_MERGE_DIFF_DF_CHILD = (1 << 11), } git_merge_diff_type_t; - typedef struct { git_repository *repo; git_pool pool; @@ -152,4 +165,50 @@ int git_merge__check_result(git_repository *repo, git_index *index_new); int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); +/* Merge files */ + +GIT_INLINE(const char *) git_merge_file__best_path( + const char *ancestor, + const char *ours, + const char *theirs) +{ + if (!ancestor) { + if (ours && theirs && strcmp(ours, theirs) == 0) + return ours; + + return NULL; + } + + if (ours && strcmp(ancestor, ours) == 0) + return theirs; + else if(theirs && strcmp(ancestor, theirs) == 0) + return ours; + + return NULL; +} + +GIT_INLINE(uint32_t) git_merge_file__best_mode( + uint32_t ancestor, uint32_t ours, uint32_t theirs) +{ + /* + * If ancestor didn't exist and either ours or theirs is executable, + * assume executable. Otherwise, if any mode changed from the ancestor, + * use that one. + */ + if (!ancestor) { + if (ours == GIT_FILEMODE_BLOB_EXECUTABLE || + theirs == GIT_FILEMODE_BLOB_EXECUTABLE) + return GIT_FILEMODE_BLOB_EXECUTABLE; + + return GIT_FILEMODE_BLOB; + } else if (ours && theirs) { + if (ancestor == ours) + return theirs; + + return ours; + } + + return 0; +} + #endif diff --git a/src/merge_driver.c b/src/merge_driver.c new file mode 100644 index 000000000..cc039dbb5 --- /dev/null +++ b/src/merge_driver.c @@ -0,0 +1,396 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "vector.h" +#include "global.h" +#include "merge.h" +#include "merge_driver.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" + +static const char *merge_driver_name__text = "text"; +static const char *merge_driver_name__union = "union"; +static const char *merge_driver_name__binary = "binary"; + +struct merge_driver_registry { + git_rwlock lock; + git_vector drivers; +}; + +typedef struct { + git_merge_driver *driver; + int initialized; + char name[GIT_FLEX_ARRAY]; +} git_merge_driver_entry; + +static struct merge_driver_registry merge_driver_registry; + +static void git_merge_driver_global_shutdown(void); + + +int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + int error; + + GIT_UNUSED(filter_name); + + if (src->file_opts) + memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); + + if (driver->favor) + file_opts.favor = driver->favor; + + if ((error = git_merge_file_from_index(&result, src->repo, + src->ancestor, src->ours, src->theirs, &file_opts)) < 0) + goto done; + + if (!result.automergeable && + !(file_opts.flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) { + error = GIT_EMERGECONFLICT; + goto done; + } + + *path_out = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + *mode_out = git_merge_file__best_mode( + src->ancestor ? src->ancestor->mode : 0, + src->ours ? src->ours->mode : 0, + src->theirs ? src->theirs->mode : 0); + + merged_out->ptr = (char *)result.ptr; + merged_out->size = result.len; + merged_out->asize = result.len; + result.ptr = NULL; + +done: + git_merge_file_result_free(&result); + return error; +} + +static int merge_driver_binary_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(self); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_EMERGECONFLICT; +} + +static int merge_driver_entry_cmp(const void *a, const void *b) +{ + const git_merge_driver_entry *entry_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(entry_a->name, entry_b->name); +} + +static int merge_driver_entry_search(const void *a, const void *b) +{ + const char *name_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(name_a, entry_b->name); +} + +git_merge_driver__builtin git_merge_driver__text = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_NORMAL +}; + +git_merge_driver__builtin git_merge_driver__union = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_UNION +}; + +git_merge_driver git_merge_driver__binary = { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + merge_driver_binary_apply +}; + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_insert( + const char *name, git_merge_driver *driver) +{ + git_merge_driver_entry *entry; + + entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); + GITERR_CHECK_ALLOC(entry); + + strcpy(entry->name, name); + entry->driver = driver; + + return git_vector_insert_sorted( + &merge_driver_registry.drivers, entry, NULL); +} + +int git_merge_driver_global_init(void) +{ + int error; + + if (git_rwlock_init(&merge_driver_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&merge_driver_registry.drivers, 3, + merge_driver_entry_cmp)) < 0) + goto done; + + if ((error = merge_driver_registry_insert( + merge_driver_name__text, &git_merge_driver__text.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__union, &git_merge_driver__union.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__binary, &git_merge_driver__binary)) < 0) + + git__on_shutdown(git_merge_driver_global_shutdown); + +done: + if (error < 0) + git_vector_free_deep(&merge_driver_registry.drivers); + + return error; +} + +static void git_merge_driver_global_shutdown(void) +{ + git_merge_driver_entry *entry; + size_t i; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) + return; + + git_vector_foreach(&merge_driver_registry.drivers, i, entry) { + if (entry->driver->shutdown) + entry->driver->shutdown(entry->driver); + + git__free(entry); + } + + git_vector_free(&merge_driver_registry.drivers); + + git_rwlock_wrunlock(&merge_driver_registry.lock); + git_rwlock_free(&merge_driver_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2(pos, &merge_driver_registry.drivers, + merge_driver_entry_search, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_merge_driver_entry *merge_driver_registry_lookup( + size_t *pos, const char *name) +{ + git_merge_driver_entry *entry = NULL; + + if (!merge_driver_registry_find(pos, name)) + entry = git_vector_get(&merge_driver_registry.drivers, *pos); + + return entry; +} + +int git_merge_driver_register(const char *name, git_merge_driver *driver) +{ + int error; + + assert(name && driver); + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock merge driver registry"); + return -1; + } + + if (!merge_driver_registry_find(NULL, name)) { + giterr_set(GITERR_MERGE, "attempt to reregister existing driver '%s'", + name); + error = GIT_EEXISTS; + goto done; + } + + error = merge_driver_registry_insert(name, driver); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +int git_merge_driver_unregister(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error = 0; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock merge driver registry"); + return -1; + } + + if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { + giterr_set(GITERR_MERGE, "cannot find merge driver '%s' to unregister", + name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&merge_driver_registry.drivers, pos); + + if (entry->initialized && entry->driver->shutdown) { + entry->driver->shutdown(entry->driver); + entry->initialized = false; + } + + git__free(entry); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +git_merge_driver *git_merge_driver_lookup(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error; + + /* If we've decided the merge driver to use internally - and not + * based on user configuration (in merge_driver_name_for_path) + * then we can use a hardcoded name to compare instead of bothering + * to take a lock and look it up in the vector. + */ + if (name == merge_driver_name__text) + return &git_merge_driver__text.base; + else if (name == merge_driver_name__binary) + return &git_merge_driver__binary; + + if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock merge driver registry"); + return NULL; + } + + entry = merge_driver_registry_lookup(&pos, name); + + git_rwlock_rdunlock(&merge_driver_registry.lock); + + if (entry == NULL) { + giterr_set(GITERR_MERGE, "cannot use an unregistered filter"); + return NULL; + } + + if (!entry->initialized) { + if (entry->driver->initialize && + (error = entry->driver->initialize(entry->driver)) < 0) + return NULL; + + entry->initialized = 1; + } + + return entry->driver; +} + +static int merge_driver_name_for_path( + const char **out, + git_repository *repo, + const char *path, + const char *default_driver) +{ + const char *value; + int error; + + *out = NULL; + + if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) + return error; + + /* set: use the built-in 3-way merge driver ("text") */ + if (GIT_ATTR_TRUE(value)) + *out = merge_driver_name__text; + + /* unset: do not merge ("binary") */ + else if (GIT_ATTR_FALSE(value)) + *out = merge_driver_name__binary; + + else if (GIT_ATTR_UNSPECIFIED(value) && default_driver) + *out = default_driver; + + else if (GIT_ATTR_UNSPECIFIED(value)) + *out = merge_driver_name__text; + + else + *out = value; + + return 0; +} + + +GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( + const char *name) +{ + git_merge_driver *driver = git_merge_driver_lookup(name); + + if (driver == NULL) + driver = git_merge_driver_lookup("*"); + + return driver; +} + +int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src) +{ + const char *path, *driver_name; + int error = 0; + + path = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + if ((error = merge_driver_name_for_path( + &driver_name, src->repo, path, src->default_driver)) < 0) + return error; + + *name_out = driver_name; + *driver_out = merge_driver_lookup_with_wildcard(driver_name); + return error; +} + diff --git a/src/merge_driver.h b/src/merge_driver.h new file mode 100644 index 000000000..bde27502c --- /dev/null +++ b/src/merge_driver.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_merge_driver_h__ +#define INCLUDE_merge_driver_h__ + +#include "git2/merge.h" +#include "git2/index.h" +#include "git2/sys/merge.h" + +struct git_merge_driver_source { + git_repository *repo; + const char *default_driver; + const git_merge_file_options *file_opts; + + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; +}; + +typedef struct git_merge_driver__builtin { + git_merge_driver base; + git_merge_file_favor_t favor; +} git_merge_driver__builtin; + +extern int git_merge_driver_global_init(void); + +extern int git_merge_driver_for_path( + char **name_out, + git_merge_driver **driver_out, + git_repository *repo, + const char *path); + +/* Merge driver configuration */ +extern int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src); + +extern int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src); + +/* Merge driver for text files, performs a standard three-way merge */ +extern git_merge_driver__builtin git_merge_driver__text; + +/* Merge driver for union-style merging */ +extern git_merge_driver__builtin git_merge_driver__union; + +/* Merge driver for unmergeable (binary) files: always produces conflicts */ +extern git_merge_driver git_merge_driver__binary; + +#endif diff --git a/src/merge_file.c b/src/merge_file.c index 6d4738065..731f4b724 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -11,6 +11,7 @@ #include "fileops.h" #include "index.h" #include "diff_xdiff.h" +#include "merge.h" #include "git2/repository.h" #include "git2/object.h" @@ -26,52 +27,6 @@ #define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) -GIT_INLINE(const char *) merge_file_best_path( - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs) -{ - if (!ancestor) { - if (ours && theirs && strcmp(ours->path, theirs->path) == 0) - return ours->path; - - return NULL; - } - - 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; -} - -GIT_INLINE(int) merge_file_best_mode( - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs) -{ - /* - * If ancestor didn't exist and either ours or theirs is executable, - * assume executable. Otherwise, if any mode changed from the ancestor, - * use that one. - */ - 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; - - return ours->mode; - } - - return 0; -} - int git_merge_file__input_from_index( git_merge_file_input *input_out, git_odb_object **odb_object_out, @@ -177,8 +132,12 @@ static int merge_file__xdiff( goto done; } - if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL && - (out->path = strdup(path)) == NULL) { + path = git_merge_file__best_path( + ancestor ? ancestor->path : NULL, + ours ? ours->path : NULL, + theirs ? theirs->path : NULL); + + if (path != NULL && (out->path = git__strdup(path)) == NULL) { error = -1; goto done; } @@ -186,7 +145,10 @@ static int merge_file__xdiff( out->automergeable = (xdl_result == 0); out->ptr = (const char *)mmbuffer.ptr; out->len = mmbuffer.size; - out->mode = merge_file_best_mode(ancestor, ours, theirs); + out->mode = git_merge_file__best_mode( + ancestor ? ancestor->mode : 0, + ours ? ours->mode : 0, + theirs ? theirs->mode : 0); done: if (error < 0) |