Merge pull request #3513 from ethomson/merge_recursive

Recursive Merge
This commit is contained in:
Carlos Martín Nieto 2015-11-30 04:49:31 +01:00
commit a27f31d8f5
175 changed files with 1054 additions and 192 deletions

View File

@ -34,6 +34,14 @@ v0.23 + 1
### Breaking API changes ### Breaking API changes
* 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
`git_merge_options` structure is now named `flags`.
* The `git_merge_file_flags_t` enum is now `git_merge_file_flag_t` for
consistency with other enum type names.
* `git_cert` descendent types now have a proper `parent` member * `git_cert` descendent types now have a proper `parent` member
* It is the responsibility of the refdb backend to decide what to do * It is the responsibility of the refdb backend to decide what to do

View File

@ -62,8 +62,8 @@ GIT_EXTERN(int) git_merge_file_init_input(
unsigned int version); unsigned int version);
/** /**
* Flags for `git_merge_tree` options. A combination of these flags can be * Flags for `git_merge` options. A combination of these flags can be
* passed in via the `tree_flags` value in the `git_merge_options`. * passed in via the `flags` value in the `git_merge_options`.
*/ */
typedef enum { typedef enum {
/** /**
@ -71,20 +71,28 @@ typedef enum {
* side or the common ancestor and the "theirs" side. This will enable * side or the common ancestor and the "theirs" side. This will enable
* the ability to merge between a modified and renamed file. * the ability to merge between a modified and renamed file.
*/ */
GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), GIT_MERGE_FIND_RENAMES = (1 << 0),
/** /**
* If a conflict occurs, exit immediately instead of attempting to * If a conflict occurs, exit immediately instead of attempting to
* continue resolving conflicts. The merge operation will fail with * continue resolving conflicts. The merge operation will fail with
* GIT_EMERGECONFLICT and no index will be returned. * GIT_EMERGECONFLICT and no index will be returned.
*/ */
GIT_MERGE_TREE_FAIL_ON_CONFLICT = (1 << 1), GIT_MERGE_FAIL_ON_CONFLICT = (1 << 1),
/** /**
* Do not write the REUC extension on the generated index * Do not write the REUC extension on the generated index
*/ */
GIT_MERGE_TREE_SKIP_REUC = (1 << 2), GIT_MERGE_SKIP_REUC = (1 << 2),
} git_merge_tree_flag_t;
/**
* If the commits being merged have multiple merge bases, do not build
* a recursive merge base (by merging the multiple merge bases),
* instead simply use the first base. This flag provides a similar
* merge base to `git-merge-resolve`.
*/
GIT_MERGE_NO_RECURSIVE = (1 << 3),
} git_merge_flag_t;
/** /**
* Merge file favor options for `git_merge_options` instruct the file-level * Merge file favor options for `git_merge_options` instruct the file-level
@ -152,7 +160,7 @@ typedef enum {
/** Take extra time to find minimal diff */ /** Take extra time to find minimal diff */
GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7), GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7),
} git_merge_file_flags_t; } git_merge_file_flag_t;
/** /**
* Options for merging a file * Options for merging a file
@ -181,8 +189,8 @@ typedef struct {
/** The file to favor in region conflicts. */ /** The file to favor in region conflicts. */
git_merge_file_favor_t favor; git_merge_file_favor_t favor;
/** see `git_merge_file_flags_t` above */ /** see `git_merge_file_flag_t` above */
unsigned int flags; git_merge_file_flag_t flags;
} git_merge_file_options; } git_merge_file_options;
#define GIT_MERGE_FILE_OPTIONS_VERSION 1 #define GIT_MERGE_FILE_OPTIONS_VERSION 1
@ -232,11 +240,13 @@ typedef struct {
*/ */
typedef struct { typedef struct {
unsigned int version; unsigned int version;
git_merge_tree_flag_t tree_flags;
/** See `git_merge_flag_t` above */
git_merge_flag_t flags;
/** /**
* Similarity to consider a file renamed (default 50). If * Similarity to consider a file renamed (default 50). If
* `GIT_MERGE_TREE_FIND_RENAMES` is enabled, added files will be compared * `GIT_MERGE_FIND_RENAMES` is enabled, added files will be compared
* with deleted files to determine their similarity. Files that are * with deleted files to determine their similarity. Files that are
* more similar than the rename threshold (percentage-wise) will be * more similar than the rename threshold (percentage-wise) will be
* treated as a rename. * treated as a rename.
@ -255,11 +265,19 @@ typedef struct {
/** Pluggable similarity metric; pass NULL to use internal metric */ /** Pluggable similarity metric; pass NULL to use internal metric */
git_diff_similarity_metric *metric; git_diff_similarity_metric *metric;
/**
* Maximum number of times to merge common ancestors to build a
* virtual merge base when faced with criss-cross merges. When this
* limit is reached, the next ancestor will simply be used instead of
* attempting to merge it. The default is unlimited.
*/
unsigned int recursion_limit;
/** Flags for handling conflicting content. */ /** Flags for handling conflicting content. */
git_merge_file_favor_t file_favor; git_merge_file_favor_t file_favor;
/** see `git_merge_file_flags_t` above */ /** see `git_merge_file_flag_t` above */
unsigned int file_flags; git_merge_file_flag_t file_flags;
} git_merge_options; } git_merge_options;
#define GIT_MERGE_OPTIONS_VERSION 1 #define GIT_MERGE_OPTIONS_VERSION 1

View File

@ -7,12 +7,16 @@
#include "common.h" #include "common.h"
#include "annotated_commit.h" #include "annotated_commit.h"
#include "refs.h"
#include "cache.h"
#include "git2/commit.h" #include "git2/commit.h"
#include "git2/refs.h" #include "git2/refs.h"
#include "git2/repository.h" #include "git2/repository.h"
#include "git2/annotated_commit.h" #include "git2/annotated_commit.h"
#include "git2/revparse.h" #include "git2/revparse.h"
#include "git2/tree.h"
#include "git2/index.h"
static int annotated_commit_init( static int annotated_commit_init(
git_annotated_commit **out, git_annotated_commit **out,
@ -22,14 +26,17 @@ static int annotated_commit_init(
const char *remote_url) const char *remote_url)
{ {
git_annotated_commit *annotated_commit; git_annotated_commit *annotated_commit;
git_commit *commit = NULL;
int error = 0; int error = 0;
assert(out && id); assert(out && id);
*out = NULL; *out = NULL;
annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); if ((error = git_commit_lookup(&commit, repo, id)) < 0 ||
GITERR_CHECK_ALLOC(annotated_commit); (error = git_annotated_commit_from_commit(&annotated_commit,
commit)) < 0)
goto done;
if (ref_name) { if (ref_name) {
annotated_commit->ref_name = git__strdup(ref_name); annotated_commit->ref_name = git__strdup(ref_name);
@ -41,15 +48,10 @@ static int annotated_commit_init(
GITERR_CHECK_ALLOC(annotated_commit->remote_url); GITERR_CHECK_ALLOC(annotated_commit->remote_url);
} }
git_oid_fmt(annotated_commit->id_str, id);
annotated_commit->id_str[GIT_OID_HEXSZ] = '\0';
if ((error = git_commit_lookup(&annotated_commit->commit, repo, id)) < 0) {
git_annotated_commit_free(annotated_commit);
return error;
}
*out = annotated_commit; *out = annotated_commit;
done:
git_commit_free(commit);
return error; return error;
} }
@ -75,6 +77,51 @@ int git_annotated_commit_from_ref(
return error; return error;
} }
int git_annotated_commit_from_head(
git_annotated_commit **out,
git_repository *repo)
{
git_reference *head;
int error;
assert(out && repo);
*out = NULL;
if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
return -1;
error = git_annotated_commit_from_ref(out, repo, head);
git_reference_free(head);
return error;
}
int git_annotated_commit_from_commit(
git_annotated_commit **out,
git_commit *commit)
{
git_annotated_commit *annotated_commit;
assert(out && commit);
*out = NULL;
annotated_commit = git__calloc(1, sizeof(git_annotated_commit));
GITERR_CHECK_ALLOC(annotated_commit);
annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL;
git_cached_obj_incref(commit);
annotated_commit->commit = commit;
git_oid_fmt(annotated_commit->id_str, git_commit_id(commit));
annotated_commit->id_str[GIT_OID_HEXSZ] = '\0';
*out = annotated_commit;
return 0;
}
int git_annotated_commit_lookup( int git_annotated_commit_lookup(
git_annotated_commit **out, git_annotated_commit **out,
git_repository *repo, git_repository *repo,
@ -136,14 +183,20 @@ void git_annotated_commit_free(git_annotated_commit *annotated_commit)
if (annotated_commit == NULL) if (annotated_commit == NULL)
return; return;
if (annotated_commit->commit != NULL) switch (annotated_commit->type) {
git_commit_free(annotated_commit->commit); case GIT_ANNOTATED_COMMIT_REAL:
git_commit_free(annotated_commit->commit);
if (annotated_commit->ref_name != NULL) git_tree_free(annotated_commit->tree);
git__free(annotated_commit->ref_name); git__free(annotated_commit->ref_name);
git__free(annotated_commit->remote_url);
if (annotated_commit->remote_url != NULL) break;
git__free(annotated_commit->remote_url); case GIT_ANNOTATED_COMMIT_VIRTUAL:
git_index_free(annotated_commit->index);
git_array_clear(annotated_commit->parents);
break;
default:
abort();
}
git__free(annotated_commit); git__free(annotated_commit);
} }

View File

@ -7,11 +7,31 @@
#ifndef INCLUDE_annotated_commit_h__ #ifndef INCLUDE_annotated_commit_h__
#define INCLUDE_annotated_commit_h__ #define INCLUDE_annotated_commit_h__
#include "oidarray.h"
#include "git2/oid.h" #include "git2/oid.h"
/** Internal structure for merge inputs */ typedef enum {
GIT_ANNOTATED_COMMIT_REAL = 1,
GIT_ANNOTATED_COMMIT_VIRTUAL = 2,
} git_annotated_commit_t;
/**
* Internal structure for merge inputs. An annotated commit is generally
* "real" and backed by an actual commit in the repository, but merge will
* internally create "virtual" commits that are in-memory intermediate
* commits backed by an index.
*/
struct git_annotated_commit { struct git_annotated_commit {
git_annotated_commit_t type;
/* real commit */
git_commit *commit; git_commit *commit;
git_tree *tree;
/* virtual commit structure */
git_index *index;
git_array_oid_t parents;
char *ref_name; char *ref_name;
char *remote_url; char *remote_url;
@ -19,4 +39,9 @@ struct git_annotated_commit {
char id_str[GIT_OID_HEXSZ+1]; char id_str[GIT_OID_HEXSZ+1];
}; };
extern int git_annotated_commit_from_head(git_annotated_commit **out,
git_repository *repo);
extern int git_annotated_commit_from_commit(git_annotated_commit **out,
git_commit *commit);
#endif #endif

View File

@ -27,6 +27,8 @@
#include "config.h" #include "config.h"
#include "oidarray.h" #include "oidarray.h"
#include "annotated_commit.h" #include "annotated_commit.h"
#include "commit.h"
#include "oidarray.h"
#include "git2/types.h" #include "git2/types.h"
#include "git2/repository.h" #include "git2/repository.h"
@ -47,6 +49,19 @@
#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) #define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0)
#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) #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 { typedef enum {
TREE_IDX_ANCESTOR = 0, TREE_IDX_ANCESTOR = 0,
TREE_IDX_OURS = 1, TREE_IDX_OURS = 1,
@ -799,11 +814,9 @@ static int merge_conflict_resolve_automerge(
int *resolved, int *resolved,
git_merge_diff_list *diff_list, git_merge_diff_list *diff_list,
const git_merge_diff *conflict, const git_merge_diff *conflict,
unsigned int merge_file_favor, const git_merge_file_options *file_opts)
unsigned int file_flags)
{ {
const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL; const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL;
git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
git_merge_file_result result = {0}; git_merge_file_result result = {0};
git_index_entry *index_entry; git_index_entry *index_entry;
git_odb *odb = NULL; git_odb *odb = NULL;
@ -850,12 +863,9 @@ static int merge_conflict_resolve_automerge(
theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
&conflict->their_entry : NULL; &conflict->their_entry : NULL;
opts.favor = merge_file_favor;
opts.flags = file_flags;
if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 || if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 ||
(error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, &opts)) < 0 || (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, file_opts)) < 0 ||
!result.automergeable || (!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) (error = git_odb_write(&automerge_oid, odb, result.ptr, result.len, GIT_OBJ_BLOB)) < 0)
goto done; goto done;
@ -885,8 +895,7 @@ static int merge_conflict_resolve(
int *out, int *out,
git_merge_diff_list *diff_list, git_merge_diff_list *diff_list,
const git_merge_diff *conflict, const git_merge_diff *conflict,
unsigned int merge_file_favor, const git_merge_file_options *file_opts)
unsigned int file_flags)
{ {
int resolved = 0; int resolved = 0;
int error = 0; int error = 0;
@ -902,8 +911,7 @@ static int merge_conflict_resolve(
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; goto done;
if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, file_opts)) < 0)
merge_file_favor, file_flags)) < 0)
goto done; goto done;
*out = resolved; *out = resolved;
@ -1296,7 +1304,7 @@ int git_merge_diff_list__find_renames(
assert(diff_list && opts); assert(diff_list && opts);
if ((opts->tree_flags & GIT_MERGE_TREE_FIND_RENAMES) == 0) if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0)
return 0; return 0;
similarity_ours = git__calloc(diff_list->conflicts.length, similarity_ours = git__calloc(diff_list->conflicts.length,
@ -1632,8 +1640,8 @@ static int merge_normalize_opts(
git_merge_options init = GIT_MERGE_OPTIONS_INIT; git_merge_options init = GIT_MERGE_OPTIONS_INIT;
memcpy(opts, &init, sizeof(init)); memcpy(opts, &init, sizeof(init));
opts->tree_flags = GIT_MERGE_TREE_FIND_RENAMES; opts->flags = GIT_MERGE_FIND_RENAMES;
opts->rename_threshold = GIT_MERGE_TREE_RENAME_THRESHOLD; opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD;
} }
if (!opts->target_limit) { if (!opts->target_limit) {
@ -1643,7 +1651,7 @@ static int merge_normalize_opts(
limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); limit = git_config__get_int_force(cfg, "diff.renamelimit", 0);
opts->target_limit = (limit <= 0) ? opts->target_limit = (limit <= 0) ?
GIT_MERGE_TREE_TARGET_LIMIT : (unsigned int)limit; GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit;
} }
/* assign the internal metric with whitespace flag as payload */ /* assign the internal metric with whitespace flag as payload */
@ -1827,6 +1835,7 @@ int git_merge__iterators(
*empty_theirs = NULL; *empty_theirs = NULL;
git_merge_diff_list *diff_list; git_merge_diff_list *diff_list;
git_merge_options opts; git_merge_options opts;
git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT;
git_merge_diff *conflict; git_merge_diff *conflict;
git_vector changes; git_vector changes;
size_t i; size_t i;
@ -1842,6 +1851,17 @@ int git_merge__iterators(
if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0)
return error; return error;
file_opts.favor = opts.file_favor;
file_opts.flags = opts.file_flags;
/* use the git-inspired labels when virtual base building */
if (opts.flags & GIT_MERGE__VIRTUAL_BASE) {
file_opts.ancestor_label = "merged common ancestors";
file_opts.our_label = "Temporary merge branch 1";
file_opts.their_label = "Temporary merge branch 2";
file_opts.flags |= GIT_MERGE_FILE_FAVOR__CONFLICTED;
}
diff_list = git_merge_diff_list__alloc(repo); diff_list = git_merge_diff_list__alloc(repo);
GITERR_CHECK_ALLOC(diff_list); GITERR_CHECK_ALLOC(diff_list);
@ -1860,11 +1880,12 @@ int git_merge__iterators(
git_vector_foreach(&changes, i, conflict) { git_vector_foreach(&changes, i, conflict) {
int resolved = 0; int resolved = 0;
if ((error = merge_conflict_resolve(&resolved, diff_list, conflict, opts.file_favor, opts.file_flags)) < 0) if ((error = merge_conflict_resolve(
&resolved, diff_list, conflict, &file_opts)) < 0)
goto done; goto done;
if (!resolved) { if (!resolved) {
if ((opts.tree_flags & GIT_MERGE_TREE_FAIL_ON_CONFLICT)) { if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) {
giterr_set(GITERR_MERGE, "merge conflicts exist"); giterr_set(GITERR_MERGE, "merge conflicts exist");
error = GIT_EMERGECONFLICT; error = GIT_EMERGECONFLICT;
goto done; goto done;
@ -1875,7 +1896,7 @@ int git_merge__iterators(
} }
error = index_from_diff_list(out, diff_list, error = index_from_diff_list(out, diff_list,
(opts.tree_flags & GIT_MERGE_TREE_SKIP_REUC)); (opts.flags & GIT_MERGE_SKIP_REUC));
done: done:
if (!given_opts || !given_opts->metric) if (!given_opts || !given_opts->metric)
@ -1922,6 +1943,207 @@ done:
return error; return error;
} }
static int merge_annotated_commits(
git_index **index_out,
git_annotated_commit **base_out,
git_repository *repo,
git_annotated_commit *our_commit,
git_annotated_commit *their_commit,
size_t recursion_level,
const git_merge_options *opts);
GIT_INLINE(int) insert_head_ids(
git_array_oid_t *ids,
const git_annotated_commit *annotated_commit)
{
git_oid *id;
size_t i;
if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) {
id = git_array_alloc(*ids);
GITERR_CHECK_ALLOC(id);
git_oid_cpy(id, git_commit_id(annotated_commit->commit));
} else {
for (i = 0; i < annotated_commit->parents.size; i++) {
id = git_array_alloc(*ids);
GITERR_CHECK_ALLOC(id);
git_oid_cpy(id, &annotated_commit->parents.ptr[i]);
}
}
return 0;
}
static int create_virtual_base(
git_annotated_commit **out,
git_repository *repo,
git_annotated_commit *one,
git_annotated_commit *two,
const git_merge_options *opts,
size_t recursion_level)
{
git_annotated_commit *result = NULL;
git_index *index = NULL;
git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT;
result = git__calloc(1, sizeof(git_annotated_commit));
GITERR_CHECK_ALLOC(result);
/* Conflicts in the merge base creation do not propagate to conflicts
* in the result; the conflicted base will act as the common ancestor.
*/
if (opts)
memcpy(&virtual_opts, opts, sizeof(git_merge_options));
virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT;
virtual_opts.flags |= GIT_MERGE__VIRTUAL_BASE;
if ((merge_annotated_commits(&index, NULL, repo, one, two,
recursion_level + 1, &virtual_opts)) < 0)
return -1;
result->type = GIT_ANNOTATED_COMMIT_VIRTUAL;
result->index = index;
insert_head_ids(&result->parents, one);
insert_head_ids(&result->parents, two);
*out = result;
return 0;
}
static int compute_base(
git_annotated_commit **out,
git_repository *repo,
const git_annotated_commit *one,
const git_annotated_commit *two,
const git_merge_options *given_opts,
size_t recursion_level)
{
git_array_oid_t head_ids = GIT_ARRAY_INIT;
git_oidarray bases = {0};
git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
size_t i;
int error;
*out = NULL;
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_merge_options));
if ((error = insert_head_ids(&head_ids, one)) < 0 ||
(error = insert_head_ids(&head_ids, two)) < 0)
goto done;
if ((error = git_merge_bases_many(&bases, repo,
head_ids.size, head_ids.ptr)) < 0 ||
(error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0 ||
(opts.flags & GIT_MERGE_NO_RECURSIVE))
goto done;
for (i = 1; i < bases.count; i++) {
recursion_level++;
if (opts.recursion_limit && recursion_level > opts.recursion_limit)
break;
if ((error = git_annotated_commit_lookup(&other, repo,
&bases.ids[i])) < 0 ||
(error = create_virtual_base(&new_base, repo, base, other, &opts,
recursion_level)) < 0)
goto done;
git_annotated_commit_free(base);
git_annotated_commit_free(other);
base = new_base;
new_base = NULL;
other = NULL;
}
done:
if (error == 0)
*out = base;
else
git_annotated_commit_free(base);
git_annotated_commit_free(other);
git_annotated_commit_free(new_base);
git_oidarray_free(&bases);
git_array_clear(head_ids);
return error;
}
static int iterator_for_annotated_commit(
git_iterator **out,
git_annotated_commit *commit)
{
git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT;
int error;
opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
if (commit == NULL) {
error = git_iterator_for_nothing(out, &opts);
} else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) {
error = git_iterator_for_index(out, commit->index, &opts);
} else {
if (!commit->tree &&
(error = git_commit_tree(&commit->tree, commit->commit)) < 0)
goto done;
error = git_iterator_for_tree(out, commit->tree, &opts);
}
done:
return error;
}
static int merge_annotated_commits(
git_index **index_out,
git_annotated_commit **base_out,
git_repository *repo,
git_annotated_commit *ours,
git_annotated_commit *theirs,
size_t recursion_level,
const git_merge_options *opts)
{
git_annotated_commit *base = NULL;
git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL;
int error;
if ((error = compute_base(&base, repo, ours, theirs, opts,
recursion_level)) < 0) {
if (error != GIT_ENOTFOUND)
goto done;
giterr_clear();
}
if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 ||
(error = iterator_for_annotated_commit(&our_iter, ours)) < 0 ||
(error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 ||
(error = git_merge__iterators(index_out, repo, base_iter, our_iter,
their_iter, opts)) < 0)
goto done;
if (base_out) {
*base_out = base;
base = NULL;
}
done:
git_annotated_commit_free(base);
git_iterator_free(base_iter);
git_iterator_free(our_iter);
git_iterator_free(their_iter);
return error;
}
int git_merge_commits( int git_merge_commits(
git_index **out, git_index **out,
@ -1930,30 +2152,19 @@ int git_merge_commits(
const git_commit *their_commit, const git_commit *their_commit,
const git_merge_options *opts) const git_merge_options *opts)
{ {
git_oid ancestor_oid; git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL;
git_commit *ancestor_commit = NULL;
git_tree *our_tree = NULL, *their_tree = NULL, *ancestor_tree = NULL;
int error = 0; int error = 0;
if ((error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit))) < 0 && if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 ||
error == GIT_ENOTFOUND) (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0)
giterr_clear();
else if (error < 0 ||
(error = git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)) < 0 ||
(error = git_commit_tree(&ancestor_tree, ancestor_commit)) < 0)
goto done; goto done;
if ((error = git_commit_tree(&our_tree, our_commit)) < 0 || error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts);
(error = git_commit_tree(&their_tree, their_commit)) < 0 ||
(error = git_merge_trees(out, repo, ancestor_tree, our_tree, their_tree, opts)) < 0)
goto done;
done: done:
git_commit_free(ancestor_commit); git_annotated_commit_free(ours);
git_tree_free(our_tree); git_annotated_commit_free(theirs);
git_tree_free(their_tree); git_annotated_commit_free(base);
git_tree_free(ancestor_tree);
return error; return error;
} }
@ -2387,49 +2598,50 @@ const char *merge_their_label(const char *branchname)
} }
static int merge_normalize_checkout_opts( static int merge_normalize_checkout_opts(
git_checkout_options *out,
git_repository *repo, git_repository *repo,
git_checkout_options *checkout_opts,
const git_checkout_options *given_checkout_opts, const git_checkout_options *given_checkout_opts,
const git_annotated_commit *ancestor_head, unsigned int checkout_strategy,
git_annotated_commit *ancestor,
const git_annotated_commit *our_head, const git_annotated_commit *our_head,
size_t their_heads_len, const git_annotated_commit **their_heads,
const git_annotated_commit **their_heads) size_t their_heads_len)
{ {
git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
int error = 0; int error = 0;
GIT_UNUSED(repo); GIT_UNUSED(repo);
if (given_checkout_opts != NULL) if (given_checkout_opts != NULL)
memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options)); memcpy(out, given_checkout_opts, sizeof(git_checkout_options));
else { else
git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; memcpy(out, &default_checkout_opts, sizeof(git_checkout_options));
default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options)); out->checkout_strategy = checkout_strategy;
}
/* TODO: for multiple ancestors in merge-recursive, this is "merged common ancestors" */ if (!out->ancestor_label) {
if (!checkout_opts->ancestor_label) { if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL)
if (ancestor_head && ancestor_head->commit) out->ancestor_label = git_commit_summary(ancestor->commit);
checkout_opts->ancestor_label = git_commit_summary(ancestor_head->commit); else if (ancestor)
out->ancestor_label = "merged common ancestors";
else else
checkout_opts->ancestor_label = "ancestor"; out->ancestor_label = "empty base";
} }
if (!checkout_opts->our_label) { if (!out->our_label) {
if (our_head && our_head->ref_name) if (our_head && our_head->ref_name)
checkout_opts->our_label = our_head->ref_name; out->our_label = our_head->ref_name;
else else
checkout_opts->our_label = "ours"; out->our_label = "ours";
} }
if (!checkout_opts->their_label) { if (!out->their_label) {
if (their_heads_len == 1 && their_heads[0]->ref_name) if (their_heads_len == 1 && their_heads[0]->ref_name)
checkout_opts->their_label = merge_their_label(their_heads[0]->ref_name); out->their_label = merge_their_label(their_heads[0]->ref_name);
else if (their_heads_len == 1) else if (their_heads_len == 1)
checkout_opts->their_label = their_heads[0]->id_str; out->their_label = their_heads[0]->id_str;
else else
checkout_opts->their_label = "theirs"; out->their_label = "theirs";
} }
return error; return error;
@ -2782,11 +2994,10 @@ int git_merge(
{ {
git_reference *our_ref = NULL; git_reference *our_ref = NULL;
git_checkout_options checkout_opts; git_checkout_options checkout_opts;
git_annotated_commit *ancestor_head = NULL, *our_head = NULL; git_annotated_commit *our_head = NULL, *base = NULL;
git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL;
git_index *index = NULL; git_index *index = NULL;
git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
size_t i; unsigned int checkout_strategy;
int error = 0; int error = 0;
assert(repo && their_heads); assert(repo && their_heads);
@ -2796,61 +3007,49 @@ int git_merge(
return -1; return -1;
} }
their_trees = git__calloc(their_heads_len, sizeof(git_tree *)); if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0)
GITERR_CHECK_ALLOC(their_trees); goto done;
if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0 || checkout_strategy = given_checkout_opts ?
(error = merge_normalize_checkout_opts(repo, &checkout_opts, given_checkout_opts, given_checkout_opts->checkout_strategy :
ancestor_head, our_head, their_heads_len, their_heads)) < 0 || GIT_CHECKOUT_SAFE;
(error = git_indexwriter_init_for_operation(&indexwriter, repo, &checkout_opts.checkout_strategy)) < 0)
goto on_error;
/* Write the merge files to the repository. */ if ((error = git_indexwriter_init_for_operation(&indexwriter, repo,
if ((error = git_merge__setup(repo, our_head, their_heads, their_heads_len)) < 0) &checkout_strategy)) < 0)
goto on_error; goto done;
if (ancestor_head != NULL && /* Write the merge setup files to the repository. */
(error = git_commit_tree(&ancestor_tree, ancestor_head->commit)) < 0) if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 ||
goto on_error; (error = git_merge__setup(repo, our_head, their_heads,
their_heads_len)) < 0)
goto done;
if ((error = git_commit_tree(&our_tree, our_head->commit)) < 0) /* TODO: octopus */
goto on_error;
for (i = 0; i < their_heads_len; i++) { if ((error = merge_annotated_commits(&index, &base, repo, our_head,
if ((error = git_commit_tree(&their_trees[i], their_heads[i]->commit)) < 0) (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 ||
goto on_error;
}
/* TODO: recursive, octopus, etc... */
if ((error = git_merge_trees(&index, repo, ancestor_tree, our_tree, their_trees[0], merge_opts)) < 0 ||
(error = git_merge__check_result(repo, index)) < 0 || (error = git_merge__check_result(repo, index)) < 0 ||
(error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0)
(error = git_checkout_index(repo, index, &checkout_opts)) < 0 || goto done;
(error = git_indexwriter_commit(&indexwriter)) < 0)
goto on_error;
goto done; /* check out the merge results */
on_error: if ((error = merge_normalize_checkout_opts(&checkout_opts, repo,
merge_state_cleanup(repo); given_checkout_opts, checkout_strategy,
base, our_head, their_heads, their_heads_len)) < 0 ||
(error = git_checkout_index(repo, index, &checkout_opts)) < 0)
goto done;
error = git_indexwriter_commit(&indexwriter);
done: done:
if (error < 0)
merge_state_cleanup(repo);
git_indexwriter_cleanup(&indexwriter); git_indexwriter_cleanup(&indexwriter);
git_index_free(index); git_index_free(index);
git_tree_free(ancestor_tree);
git_tree_free(our_tree);
for (i = 0; i < their_heads_len; i++)
git_tree_free(their_trees[i]);
git__free(their_trees);
git_annotated_commit_free(our_head); git_annotated_commit_free(our_head);
git_annotated_commit_free(ancestor_head); git_annotated_commit_free(base);
git_reference_free(our_ref); git_reference_free(our_ref);
return error; return error;

View File

@ -19,8 +19,8 @@
#define GIT_MERGE_MODE_FILE "MERGE_MODE" #define GIT_MERGE_MODE_FILE "MERGE_MODE"
#define GIT_MERGE_FILE_MODE 0666 #define GIT_MERGE_FILE_MODE 0666
#define GIT_MERGE_TREE_RENAME_THRESHOLD 50 #define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50
#define GIT_MERGE_TREE_TARGET_LIMIT 1000 #define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000
/** Types of changes when files are merged from branch to branch. */ /** Types of changes when files are merged from branch to branch. */
typedef enum { typedef enum {

View File

@ -300,7 +300,7 @@ void test_cherrypick_workdir__rename(void)
{ 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt.renamed" }, { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt.renamed" },
}; };
opts.merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES;
opts.merge_opts.rename_threshold = 50; opts.merge_opts.rename_threshold = 50;
git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15");
@ -335,7 +335,7 @@ void test_cherrypick_workdir__both_renamed(void)
{ 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 2, "file3.txt.renamed_on_branch" }, { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 2, "file3.txt.renamed_on_branch" },
}; };
opts.merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES;
opts.merge_opts.rename_threshold = 50; opts.merge_opts.rename_threshold = 50;
git_oid_fromstr(&head_oid, "44cd2ed2052c9c68f9a439d208e9614dc2a55c70"); git_oid_fromstr(&head_oid, "44cd2ed2052c9c68f9a439d208e9614dc2a55c70");

103
tests/merge/conflict_data.h Normal file
View File

@ -0,0 +1,103 @@
#define AUTOMERGEABLE_MERGED_FILE \
"this file is changed in master\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is changed in branch\n"
#define AUTOMERGEABLE_MERGED_FILE_CRLF \
"this file is changed in master\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is changed in branch\r\n"
#define CONFLICTING_MERGE_FILE \
"<<<<<<< HEAD\n" \
"this file is changed in master and branch\n" \
"=======\n" \
"this file is changed in branch and master\n" \
">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
#define CONFLICTING_DIFF3_FILE \
"<<<<<<< HEAD\n" \
"this file is changed in master and branch\n" \
"||||||| initial\n" \
"this file is a conflict\n" \
"=======\n" \
"this file is changed in branch and master\n" \
">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
#define CONFLICTING_UNION_FILE \
"this file is changed in master and branch\n" \
"this file is changed in branch and master\n"
#define CONFLICTING_RECURSIVE_F1_TO_F2 \
"VEAL SOUP.\n" \
"\n" \
"<<<<<<< HEAD\n" \
"PUT INTO A POT THREE QUARTS OF WATER, three onions cut small, ONE\n" \
"=======\n" \
"PUT INTO A POT THREE QUARTS OF WATER, three onions cut not too small, one\n" \
">>>>>>> branchF-2\n" \
"spoonful of black pepper pounded, and two of salt, with two or three\n" \
"slices of lean ham; let it boil steadily two hours; skim it\n" \
"occasionally, then put into it a shin of veal, let it boil two hours\n" \
"longer; take out the slices of ham, and skim off the grease if any\n" \
"should rise, take a gill of good cream, mix with it two table-spoonsful\n" \
"of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \
"mixture, and add some chopped parsley; pour some soup on by degrees,\n" \
"stir it well, and pour it into the pot, continuing to stir until it has\n" \
"boiled two or three minutes to take off the raw taste of the eggs. If\n" \
"the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \
"will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \
"in, first taking off their skins, by letting them stand a few minutes in\n" \
"hot water, when they may be easily peeled. When made in this way you\n" \
"must thicken it with the flour only. Any part of the veal may be used,\n" \
"but the shin or knuckle is the nicest.\n" \
"\n" \
"<<<<<<< HEAD\n" \
"This certainly is a mighty fine recipe.\n" \
"=======\n" \
"This is a mighty fine recipe!\n" \
">>>>>>> branchF-2\n"
#define CONFLICTING_RECURSIVE_H1_TO_H2_WITH_DIFF3 \
"VEAL SOUP.\n" \
"\n" \
"<<<<<<< HEAD\n" \
"put into a pot three quarts of water, three onions cut small, one\n" \
"||||||| merged common ancestors\n" \
"<<<<<<< Temporary merge branch 1\n" \
"Put into a pot three quarts of water, THREE ONIONS CUT SMALL, one\n" \
"||||||| merged common ancestors\n" \
"Put into a pot three quarts of water, three onions cut small, one\n" \
"=======\n" \
"PUT INTO A POT three quarts of water, three onions cut small, one\n" \
">>>>>>> Temporary merge branch 2\n" \
"=======\n" \
"Put Into A Pot Three Quarts of Water, Three Onions Cut Small, One\n" \
">>>>>>> branchH-2\n" \
"spoonful of black pepper pounded, and two of salt, with two or three\n" \
"slices of lean ham; let it boil steadily two hours; skim it\n" \
"occasionally, then put into it a shin of veal, let it boil two hours\n" \
"longer; take out the slices of ham, and skim off the grease if any\n" \
"should rise, take a gill of good cream, mix with it two table-spoonsful\n" \
"of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \
"mixture, and add some chopped parsley; pour some soup on by degrees,\n" \
"stir it well, and pour it into the pot, continuing to stir until it has\n" \
"boiled two or three minutes to take off the raw taste of the eggs. If\n" \
"the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \
"will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \
"in, first taking off their skins, by letting them stand a few minutes in\n" \
"hot water, when they may be easily peeled. When made in this way you\n" \
"must thicken it with the flour only. Any part of the veal may be used,\n" \
"but the shin or knuckle is the nicest.\n"

View File

@ -4,6 +4,7 @@
#include "buffer.h" #include "buffer.h"
#include "merge.h" #include "merge.h"
#include "merge_helpers.h" #include "merge_helpers.h"
#include "conflict_data.h"
#include "refs.h" #include "refs.h"
#include "fileops.h" #include "fileops.h"
#include "diff_xdiff.h" #include "diff_xdiff.h"

View File

@ -4,6 +4,7 @@
#include "tree.h" #include "tree.h"
#include "merge_helpers.h" #include "merge_helpers.h"
#include "merge.h" #include "merge.h"
#include "index.h"
#include "git2/merge.h" #include "git2/merge.h"
#include "git2/sys/index.h" #include "git2/sys/index.h"
#include "git2/annotated_commit.h" #include "git2/annotated_commit.h"
@ -239,7 +240,7 @@ int merge_test_index(git_index *index, const struct merge_index_entry expected[]
const git_index_entry *index_entry; const git_index_entry *index_entry;
/* /*
dump_index_entries(&index->entries); merge__dump_index_entries(&index->entries);
*/ */
if (git_index_entrycount(index) != expected_len) if (git_index_entrycount(index) != expected_len)

View File

@ -4,49 +4,6 @@
#include "merge.h" #include "merge.h"
#include "git2/merge.h" #include "git2/merge.h"
#define AUTOMERGEABLE_MERGED_FILE \
"this file is changed in master\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is automergeable\n" \
"this file is changed in branch\n"
#define AUTOMERGEABLE_MERGED_FILE_CRLF \
"this file is changed in master\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is automergeable\r\n" \
"this file is changed in branch\r\n"
#define CONFLICTING_MERGE_FILE \
"<<<<<<< HEAD\n" \
"this file is changed in master and branch\n" \
"=======\n" \
"this file is changed in branch and master\n" \
">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
#define CONFLICTING_DIFF3_FILE \
"<<<<<<< HEAD\n" \
"this file is changed in master and branch\n" \
"||||||| initial\n" \
"this file is a conflict\n" \
"=======\n" \
"this file is changed in branch and master\n" \
">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
#define CONFLICTING_UNION_FILE \
"this file is changed in master and branch\n" \
"this file is changed in branch and master\n"
struct merge_index_entry { struct merge_index_entry {
uint16_t mode; uint16_t mode;
char oid_str[GIT_OID_HEXSZ+1]; char oid_str[GIT_OID_HEXSZ+1];

View File

@ -3,8 +3,9 @@
#include "git2/merge.h" #include "git2/merge.h"
#include "buffer.h" #include "buffer.h"
#include "merge.h" #include "merge.h"
#include "../merge_helpers.h"
#include "fileops.h" #include "fileops.h"
#include "../merge_helpers.h"
#include "../conflict_data.h"
static git_repository *repo; static git_repository *repo;

View File

@ -3,6 +3,7 @@
#include "git2/merge.h" #include "git2/merge.h"
#include "merge.h" #include "merge.h"
#include "../merge_helpers.h" #include "../merge_helpers.h"
#include "../conflict_data.h"
static git_repository *repo; static git_repository *repo;
@ -134,7 +135,7 @@ void test_merge_trees_commits__fail_on_conflict(void)
git_index *index; git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT; git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
opts.tree_flags |= GIT_MERGE_TREE_FAIL_ON_CONFLICT; opts.flags |= GIT_MERGE_FAIL_ON_CONFLICT;
cl_git_fail_with(GIT_EMERGECONFLICT, cl_git_fail_with(GIT_EMERGECONFLICT,
merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts)); merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts));

View File

@ -0,0 +1,410 @@
#include "clar_libgit2.h"
#include "git2/repository.h"
#include "git2/merge.h"
#include "merge.h"
#include "../merge_helpers.h"
static git_repository *repo;
#define TEST_REPO_PATH "merge-recursive"
void test_merge_trees_recursive__initialize(void)
{
repo = cl_git_sandbox_init(TEST_REPO_PATH);
}
void test_merge_trees_recursive__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_merge_trees_recursive__one_base_commit(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
void test_merge_trees_recursive__one_base_commit_norecursive(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" },
};
opts.flags |= GIT_MERGE_NO_RECURSIVE;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
void test_merge_trees_recursive__two_base_commits(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "666ffdfcf1eaa5641fa31064bf2607327e843c09", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
void test_merge_trees_recursive__two_base_commits_norecursive(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "cb49ad76147f5f9439cbd6133708b76142660660", 1, "veal.txt" },
{ 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 2, "veal.txt" },
{ 0100644, "4e21d2d63357bde5027d1625f5ec6b430cdeb143", 3, "veal.txt" },
};
opts.flags |= GIT_MERGE_NO_RECURSIVE;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
void test_merge_trees_recursive__two_levels_of_multiple_bases(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "15faa0c9991f2d65686e844651faa2ff9827887b", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
void test_merge_trees_recursive__two_levels_of_multiple_bases_norecursive(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 1, "veal.txt" },
{ 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" },
{ 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" },
};
opts.flags |= GIT_MERGE_NO_RECURSIVE;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
void test_merge_trees_recursive__three_levels_of_multiple_bases(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "d55e5dc038c52f1a36548625bcb666cbc06db9e6", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
void test_merge_trees_recursive__three_levels_of_multiple_bases_norecursive(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 1, "veal.txt" },
{ 0100644, "f1b44c04989a3a1c14b036cfadfa328d53a7bc5e", 2, "veal.txt" },
{ 0100644, "5e8747f5200fac0f945a07daf6163ca9cb1a8da9", 3, "veal.txt" },
};
opts.flags |= GIT_MERGE_NO_RECURSIVE;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
void test_merge_trees_recursive__three_base_commits(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4f7269b07c76d02755d75ccaf05c0b4c36cdc6c", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
void test_merge_trees_recursive__three_base_commits_norecursive(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "9e12bce04446d097ae1782967a5888c2e2a0d35b", 1, "gravy.txt" },
{ 0100644, "d8dd349b78f19a4ebe3357bacb8138f00bf5ed41", 2, "gravy.txt" },
{ 0100644, "e50fbbd701458757bdfe9815f58ed717c588d1b5", 3, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" },
};
opts.flags |= GIT_MERGE_NO_RECURSIVE;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
void test_merge_trees_recursive__conflict(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" },
{ 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" },
{ 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchF-1", "branchF-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
/*
* Branch G-1 and G-2 have three common ancestors (815b5a1, ad2ace9, 483065d).
* The merge-base of the first two has two common ancestors (723181f, a34e5a1)
* which themselves have two common ancestors (8f35f30, 3a3f5a6), which
* finally has a common ancestor of 7c7bf85. This virtual merge base will
* be computed and merged with 483065d which also has a common ancestor of
* 7c7bf85.
*/
void test_merge_trees_recursive__oh_so_many_levels_of_recursion(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" },
{ 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchG-1", "branchG-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two
* ancestors themselves conflict.
*/
void test_merge_trees_recursive__conflicting_merge_base(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "3a66812fed1e03ea4a6a7ee28d8a57aec1ca6537", 1, "veal.txt" },
{ 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" },
{ 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two
* ancestors themselves conflict. The generated common ancestor file will
* have diff3 style conflicts inside it.
*/
void test_merge_trees_recursive__conflicting_merge_base_with_diff3(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "cd17a91513f3aee9e44114d1ede67932dd41d2fc", 1, "veal.txt" },
{ 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" },
{ 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" },
};
opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}
/* Branch I-1 and I-2 have two common ancestors (aa9e263, 6ef31d3). The two
* ancestors themselves conflict, but when each was merged, the conflicts were
* resolved identically, thus merging I-1 into I-2 does not conflict.
*/
void test_merge_trees_recursive__conflicting_merge_base_since_resolved(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "a02d4fd126e0cc8fb46ee48cf38bad36d44f2dbc", 0, "veal.txt" },
};
cl_git_pass(merge_commits_from_branches(&index, repo, "branchI-1", "branchI-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 6));
git_index_free(index);
}
/* There are multiple levels of criss-cross merges, and multiple recursive
* merges would create a common ancestor that allows the merge to complete
* successfully. Test that we can build a single virtual base, then stop,
* which will produce a conflicting merge.
*/
void test_merge_trees_recursive__recursionlimit(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "ce7e553c6feb6e5f3bd67e3c3be04182fe3094b4", 1, "gravy.txt" },
{ 0100644, "d8dd349b78f19a4ebe3357bacb8138f00bf5ed41", 2, "gravy.txt" },
{ 0100644, "e50fbbd701458757bdfe9815f58ed717c588d1b5", 3, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" },
};
opts.recursion_limit = 1;
cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts));
cl_assert(merge_test_index(index, merge_index_entries, 8));
git_index_free(index);
}

View File

@ -47,7 +47,7 @@ static void test_find_differences(
git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT; git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; opts.flags |= GIT_MERGE_FIND_RENAMES;
opts.target_limit = 1000; opts.target_limit = 1000;
opts.rename_threshold = 50; opts.rename_threshold = 50;

View File

@ -0,0 +1,84 @@
#include "clar_libgit2.h"
#include "git2/repository.h"
#include "git2/merge.h"
#include "merge.h"
#include "../merge_helpers.h"
#include "../conflict_data.h"
static git_repository *repo;
#define TEST_REPO_PATH "merge-recursive"
void test_merge_workdir_recursive__initialize(void)
{
repo = cl_git_sandbox_init(TEST_REPO_PATH);
}
void test_merge_workdir_recursive__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_merge_workdir_recursive__writes_conflict_with_virtual_base(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
git_buf conflicting_buf = GIT_BUF_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" },
{ 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" },
{ 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" },
};
cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchF-1", GIT_REFS_HEADS_DIR "branchF-2", &opts, NULL));
cl_git_pass(git_repository_index(&index, repo));
cl_assert(merge_test_index(index, merge_index_entries, 8));
cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt"));
cl_assert_equal_s(CONFLICTING_RECURSIVE_F1_TO_F2, conflicting_buf.ptr);
git_index_free(index);
git_buf_free(&conflicting_buf);
}
void test_merge_workdir_recursive__conflicting_merge_base_with_diff3(void)
{
git_index *index;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
git_buf conflicting_buf = GIT_BUF_INIT;
struct merge_index_entry merge_index_entries[] = {
{ 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" },
{ 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" },
{ 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
{ 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
{ 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
{ 0100644, "cd17a91513f3aee9e44114d1ede67932dd41d2fc", 1, "veal.txt" },
{ 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" },
{ 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" },
};
opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3;
checkout_opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3;
cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchH-1", GIT_REFS_HEADS_DIR "branchH-2", &opts, &checkout_opts));
cl_git_pass(git_repository_index(&index, repo));
cl_assert(merge_test_index(index, merge_index_entries, 8));
cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt"));
cl_assert_equal_s(CONFLICTING_RECURSIVE_H1_TO_H2_WITH_DIFF3, conflicting_buf.ptr);
git_index_free(index);
git_buf_free(&conflicting_buf);
}

View File

@ -63,7 +63,7 @@ void test_merge_workdir_renames__renames(void)
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" },
}; };
merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; merge_opts.flags |= GIT_MERGE_FIND_RENAMES;
merge_opts.rename_threshold = 50; merge_opts.rename_threshold = 50;
cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL));
@ -99,7 +99,7 @@ void test_merge_workdir_renames__ours(void)
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" }, { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" },
}; };
merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; merge_opts.flags |= GIT_MERGE_FIND_RENAMES;
merge_opts.rename_threshold = 50; merge_opts.rename_threshold = 50;
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
@ -147,7 +147,7 @@ void test_merge_workdir_renames__similar(void)
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" },
}; };
merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; merge_opts.flags |= GIT_MERGE_FIND_RENAMES;
merge_opts.rename_threshold = 50; merge_opts.rename_threshold = 50;
cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL));

View File

@ -4,6 +4,7 @@
#include "buffer.h" #include "buffer.h"
#include "merge.h" #include "merge.h"
#include "../merge_helpers.h" #include "../merge_helpers.h"
#include "../conflict_data.h"
#include "refs.h" #include "refs.h"
#include "fileops.h" #include "fileops.h"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More