diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f972d2e..0e9ca156a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ v0.24 ### Changes or improvements +* Custom merge drivers can now be registered, which allows callers to + configure callbacks to honor `merge=driver` configuration in + `.gitattributes`. + * Custom filters can now be registered with wildcard attributes, for example `filter=*`. Consumers should examine the attributes parameter of the `check` function for details. @@ -83,6 +87,10 @@ v0.24 ### Breaking API changes +* `git_merge_options` now provides a `default_driver` that can be used + to provide the name of a merge driver to be used to handle files changed + during a merge. + * The `git_merge_tree_flag_t` is now `git_merge_flag_t`. Subsequently, its members are no longer prefixed with `GIT_MERGE_TREE_FLAG` but are now prefixed with `GIT_MERGE_FLAG`, and the `tree_flags` field of the diff --git a/include/git2/merge.h b/include/git2/merge.h index 560797a0c..c6f6cba6c 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -273,7 +273,16 @@ typedef struct { */ unsigned int recursion_limit; - /** Flags for handling conflicting content. */ + /** + * Default merge driver to be used when both sides of a merge have + * changed. The default is the `text` driver. + */ + const char *default_driver; + + /** + * Flags for handling conflicting content, to be used with the standard + * (`text`) merge driver. + */ git_merge_file_favor_t file_favor; /** see `git_merge_file_flag_t` above */ diff --git a/src/merge.c b/src/merge.c index 0bc64ac13..9a6442a91 100644 --- a/src/merge.c +++ b/src/merge.c @@ -885,6 +885,7 @@ 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}; @@ -903,6 +904,7 @@ static int merge_conflict_resolve_contents( return 0; 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; @@ -955,6 +957,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; @@ -962,16 +965,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_contents(&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; @@ -1687,6 +1694,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); @@ -1704,6 +1712,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); @@ -1726,7 +1750,9 @@ static int merge_normalize_opts( opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; } - return 0; +done: + git_config_entry_free(entry); + return error; } @@ -1938,7 +1964,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) { @@ -1959,6 +1985,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); diff --git a/src/merge.h b/src/merge.h index 10d77f3d8..8eaa1ad92 100644 --- a/src/merge.h +++ b/src/merge.h @@ -40,6 +40,7 @@ enum { struct git_merge_driver_source { git_repository *repo; + const char *default_driver; const git_merge_file_options *file_opts; const git_index_entry *ancestor; diff --git a/src/merge_driver.c b/src/merge_driver.c index 5866e013e..791afe0b7 100644 --- a/src/merge_driver.c +++ b/src/merge_driver.c @@ -307,23 +307,11 @@ git_merge_driver *git_merge_driver_lookup(const char *name) return entry->driver; } -static git_merge_driver *merge_driver_lookup_with_default(const char *name) -{ - git_merge_driver *driver = git_merge_driver_lookup(name); - - if (driver == NULL) - driver = git_merge_driver_lookup("*"); - - if (driver == NULL) - driver = &git_merge_driver__text; - - return driver; -} - static int merge_driver_name_for_path( const char **out, git_repository *repo, - const char *path) + const char *path, + const char *default_driver) { const char *value; int error; @@ -334,28 +322,37 @@ static int merge_driver_name_for_path( return error; /* set: use the built-in 3-way merge driver ("text") */ - if (GIT_ATTR_TRUE(value)) { + if (GIT_ATTR_TRUE(value)) *out = merge_driver_name__text; - return 0; - } /* unset: do not merge ("binary") */ - if (GIT_ATTR_FALSE(value)) { + else if (GIT_ATTR_FALSE(value)) *out = merge_driver_name__binary; - return 0; - } - if (GIT_ATTR_UNSPECIFIED(value)) { - /* TODO */ - /* if there's a merge.default configuration value, use it */ + else if (GIT_ATTR_UNSPECIFIED(value) && default_driver) + *out = default_driver; + + else if (GIT_ATTR_UNSPECIFIED(value)) *out = merge_driver_name__text; - return 0; - } - - *out = value; + + 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( git_merge_driver **driver_out, void **data_out, @@ -371,20 +368,22 @@ int git_merge_driver_for_source( src->ours ? src->ours->path : NULL, src->theirs ? src->theirs->path : NULL); - if ((error = merge_driver_name_for_path(&driver_name, src->repo, path)) < 0) + if ((error = merge_driver_name_for_path( + &driver_name, src->repo, path, src->default_driver)) < 0) return error; - driver = merge_driver_lookup_with_default(driver_name); + driver = merge_driver_lookup_with_wildcard(driver_name); - if (driver->check) + if (driver && driver->check) { error = driver->check(driver, &data, driver_name, src); - if (error == GIT_PASSTHROUGH) - driver = &git_merge_driver__text; - else if (error == GIT_EMERGECONFLICT) - driver = &git_merge_driver__binary; - else - goto done; + if (error == GIT_PASSTHROUGH) + driver = &git_merge_driver__text; + else if (error == GIT_EMERGECONFLICT) + driver = &git_merge_driver__binary; + else + goto done; + } error = 0; data = NULL;