Commit b2c9e41a by Vicent Martí

Merge pull request #1702 from ethomson/checkout_merge

Checkout merge
parents 2c2b0ebb c929d6b7
...@@ -131,6 +131,13 @@ typedef enum { ...@@ -131,6 +131,13 @@ typedef enum {
/** Don't refresh index/config/etc before doing checkout */ /** Don't refresh index/config/etc before doing checkout */
GIT_CHECKOUT_NO_REFRESH = (1u << 9), GIT_CHECKOUT_NO_REFRESH = (1u << 9),
/** Allow checkout to skip unmerged files */
GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
/** For unmerged files, checkout stage 2 from index */
GIT_CHECKOUT_USE_OURS = (1u << 11),
/** For unmerged files, checkout stage 3 from index */
GIT_CHECKOUT_USE_THEIRS = (1u << 12),
/** Treat pathspec as simple list of exact match file paths */ /** Treat pathspec as simple list of exact match file paths */
GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13), GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13),
...@@ -141,13 +148,6 @@ typedef enum { ...@@ -141,13 +148,6 @@ typedef enum {
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
*/ */
/** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */
GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
/** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */
GIT_CHECKOUT_USE_OURS = (1u << 11),
/** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */
GIT_CHECKOUT_USE_THEIRS = (1u << 12),
/** Recursively checkout submodules with same options (NOT IMPLEMENTED) */ /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16), GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
/** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */ /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
...@@ -238,6 +238,9 @@ typedef struct git_checkout_opts { ...@@ -238,6 +238,9 @@ typedef struct git_checkout_opts {
git_tree *baseline; /** expected content of workdir, defaults to HEAD */ git_tree *baseline; /** expected content of workdir, defaults to HEAD */
const char *target_directory; /** alternative checkout path to workdir */ const char *target_directory; /** alternative checkout path to workdir */
const char *our_label; /** the name of the "our" side of conflicts */
const char *their_label; /** the name of the "their" side of conflicts */
} git_checkout_opts; } git_checkout_opts;
#define GIT_CHECKOUT_OPTS_VERSION 1 #define GIT_CHECKOUT_OPTS_VERSION 1
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
#include "diff.h" #include "diff.h"
#include "pathspec.h" #include "pathspec.h"
#include "buf_text.h" #include "buf_text.h"
#include "merge_file.h"
#include "path.h"
/* See docs/checkout-internals.md for more information */ /* See docs/checkout-internals.md for more information */
...@@ -35,8 +37,9 @@ enum { ...@@ -35,8 +37,9 @@ enum {
CHECKOUT_ACTION__UPDATE_BLOB = 2, CHECKOUT_ACTION__UPDATE_BLOB = 2,
CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
CHECKOUT_ACTION__CONFLICT = 8, CHECKOUT_ACTION__CONFLICT = 8,
CHECKOUT_ACTION__MAX = 8, CHECKOUT_ACTION__UPDATE_CONFLICT = 16,
CHECKOUT_ACTION__DEFER_REMOVE = 16, CHECKOUT_ACTION__MAX = 16,
CHECKOUT_ACTION__DEFER_REMOVE = 32,
CHECKOUT_ACTION__REMOVE_AND_UPDATE = CHECKOUT_ACTION__REMOVE_AND_UPDATE =
(CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
}; };
...@@ -50,6 +53,7 @@ typedef struct { ...@@ -50,6 +53,7 @@ typedef struct {
git_index *index; git_index *index;
git_pool pool; git_pool pool;
git_vector removes; git_vector removes;
git_vector conflicts;
git_buf path; git_buf path;
size_t workdir_len; size_t workdir_len;
unsigned int strategy; unsigned int strategy;
...@@ -59,6 +63,16 @@ typedef struct { ...@@ -59,6 +63,16 @@ typedef struct {
size_t completed_steps; size_t completed_steps;
} checkout_data; } checkout_data;
typedef struct {
const git_index_entry *ancestor;
const git_index_entry *ours;
const git_index_entry *theirs;
int name_collision:1,
directoryfile:1,
one_to_two:1;
} checkout_conflictdata;
static int checkout_notify( static int checkout_notify(
checkout_data *data, checkout_data *data,
git_checkout_notify_t why, git_checkout_notify_t why,
...@@ -592,6 +606,359 @@ static int checkout_remaining_wd_items( ...@@ -592,6 +606,359 @@ static int checkout_remaining_wd_items(
return error; return error;
} }
GIT_INLINE(int) checkout_idxentry_cmp(
const git_index_entry *a,
const git_index_entry *b)
{
if (!a && !b)
return 0;
else if (!a && b)
return -1;
else if(a && !b)
return 1;
else
return strcmp(a->path, b->path);
}
static int checkout_conflictdata_cmp(const void *a, const void *b)
{
const checkout_conflictdata *ca = a;
const checkout_conflictdata *cb = b;
int diff;
if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 &&
(diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0)
diff = checkout_idxentry_cmp(ca->theirs, cb->theirs);
return diff;
}
int checkout_conflictdata_empty(const git_vector *conflicts, size_t idx)
{
checkout_conflictdata *conflict;
if ((conflict = git_vector_get(conflicts, idx)) == NULL)
return -1;
if (conflict->ancestor || conflict->ours || conflict->theirs)
return 0;
git__free(conflict);
return 1;
}
GIT_INLINE(bool) conflict_pathspec_match(
checkout_data *data,
git_iterator *workdir,
git_vector *pathspec,
const git_index_entry *ancestor,
const git_index_entry *ours,
const git_index_entry *theirs)
{
/* if the pathspec matches ours *or* theirs, proceed */
if (ours && git_pathspec__match(pathspec, ours->path,
(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
git_iterator_ignore_case(workdir), NULL, NULL))
return true;
if (theirs && git_pathspec__match(pathspec, theirs->path,
(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
git_iterator_ignore_case(workdir), NULL, NULL))
return true;
if (ancestor && git_pathspec__match(pathspec, ancestor->path,
(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
git_iterator_ignore_case(workdir), NULL, NULL))
return true;
return false;
}
static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec)
{
git_index_conflict_iterator *iterator = NULL;
const git_index_entry *ancestor, *ours, *theirs;
checkout_conflictdata *conflict;
int error = 0;
if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0)
goto done;
data->conflicts._cmp = checkout_conflictdata_cmp;
/* Collect the conflicts */
while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) {
if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs))
continue;
conflict = git__calloc(1, sizeof(checkout_conflictdata));
GITERR_CHECK_ALLOC(conflict);
conflict->ancestor = ancestor;
conflict->ours = ours;
conflict->theirs = theirs;
git_vector_insert(&data->conflicts, conflict);
}
if (error == GIT_ITEROVER)
error = 0;
done:
git_index_conflict_iterator_free(iterator);
return error;
}
GIT_INLINE(int) checkout_conflicts_cmp_entry(
const char *path,
const git_index_entry *entry)
{
return strcmp((const char *)path, entry->path);
}
static int checkout_conflicts_cmp_ancestor(const void *p, const void *c)
{
const char *path = p;
const checkout_conflictdata *conflict = c;
if (!conflict->ancestor)
return 1;
return checkout_conflicts_cmp_entry(path, conflict->ancestor);
}
static checkout_conflictdata *checkout_conflicts_search_ancestor(
checkout_data *data,
const char *path)
{
size_t pos;
if (git_vector_bsearch2(&pos, &data->conflicts, checkout_conflicts_cmp_ancestor, path) < 0)
return NULL;
return git_vector_get(&data->conflicts, pos);
}
static checkout_conflictdata *checkout_conflicts_search_branch(
checkout_data *data,
const char *path)
{
checkout_conflictdata *conflict;
size_t i;
git_vector_foreach(&data->conflicts, i, conflict) {
int cmp = -1;
if (conflict->ancestor)
break;
if (conflict->ours)
cmp = checkout_conflicts_cmp_entry(path, conflict->ours);
else if (conflict->theirs)
cmp = checkout_conflicts_cmp_entry(path, conflict->theirs);
if (cmp == 0)
return conflict;
}
return NULL;
}
static int checkout_conflicts_load_byname_entry(
checkout_conflictdata **ancestor_out,
checkout_conflictdata **ours_out,
checkout_conflictdata **theirs_out,
checkout_data *data,
const git_index_name_entry *name_entry)
{
checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL;
int error = 0;
*ancestor_out = NULL;
*ours_out = NULL;
*theirs_out = NULL;
if (!name_entry->ancestor) {
giterr_set(GITERR_INDEX, "A NAME entry exists without an ancestor");
error = -1;
goto done;
}
if (!name_entry->ours && !name_entry->theirs) {
giterr_set(GITERR_INDEX, "A NAME entry exists without an ours or theirs");
error = -1;
goto done;
}
if ((ancestor = checkout_conflicts_search_ancestor(data,
name_entry->ancestor)) == NULL) {
giterr_set(GITERR_INDEX,
"A NAME entry referenced ancestor entry '%s' which does not exist in the main index",
name_entry->ancestor);
error = -1;
goto done;
}
if (name_entry->ours) {
if (strcmp(name_entry->ancestor, name_entry->ours) == 0)
ours = ancestor;
else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL ||
ours->ours == NULL) {
giterr_set(GITERR_INDEX,
"A NAME entry referenced our entry '%s' which does not exist in the main index",
name_entry->ours);
error = -1;
goto done;
}
}
if (name_entry->theirs) {
if (strcmp(name_entry->ancestor, name_entry->theirs) == 0)
theirs = ancestor;
else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0)
theirs = ours;
else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL ||
theirs->theirs == NULL) {
giterr_set(GITERR_INDEX,
"A NAME entry referenced their entry '%s' which does not exist in the main index",
name_entry->theirs);
error = -1;
goto done;
}
}
*ancestor_out = ancestor;
*ours_out = ours;
*theirs_out = theirs;
done:
return error;
}
static int checkout_conflicts_coalesce_renames(
checkout_data *data)
{
const git_index_name_entry *name_entry;
checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict;
size_t i, names;
int error = 0;
/* Juggle entries based on renames */
names = git_index_name_entrycount(data->index);
for (i = 0; i < names; i++) {
name_entry = git_index_name_get_byindex(data->index, i);
if ((error = checkout_conflicts_load_byname_entry(
&ancestor_conflict, &our_conflict, &their_conflict,
data, name_entry)) < 0)
goto done;
if (our_conflict && our_conflict != ancestor_conflict) {
ancestor_conflict->ours = our_conflict->ours;
our_conflict->ours = NULL;
if (our_conflict->theirs)
our_conflict->name_collision = 1;
if (our_conflict->name_collision)
ancestor_conflict->name_collision = 1;
}
if (their_conflict && their_conflict != ancestor_conflict) {
ancestor_conflict->theirs = their_conflict->theirs;
their_conflict->theirs = NULL;
if (their_conflict->ours)
their_conflict->name_collision = 1;
if (their_conflict->name_collision)
ancestor_conflict->name_collision = 1;
}
if (our_conflict && our_conflict != ancestor_conflict &&
their_conflict && their_conflict != ancestor_conflict)
ancestor_conflict->one_to_two = 1;
}
git_vector_remove_matching(&data->conflicts, checkout_conflictdata_empty);
done:
return error;
}
static int checkout_conflicts_mark_directoryfile(
checkout_data *data)
{
checkout_conflictdata *conflict;
const git_index_entry *entry;
size_t i, j, len;
const char *path;
int prefixed, error = 0;
len = git_index_entrycount(data->index);
/* Find d/f conflicts */
git_vector_foreach(&data->conflicts, i, conflict) {
if ((conflict->ours && conflict->theirs) ||
(!conflict->ours && !conflict->theirs))
continue;
path = conflict->ours ?
conflict->ours->path : conflict->theirs->path;
if ((error = git_index_find(&j, data->index, path)) < 0) {
if (error == GIT_ENOTFOUND)
giterr_set(GITERR_INDEX,
"Index inconsistency, could not find entry for expected conflict '%s'", path);
goto done;
}
for (; j < len; j++) {
if ((entry = git_index_get_byindex(data->index, j)) == NULL) {
giterr_set(GITERR_INDEX,
"Index inconsistency, truncated index while loading expected conflict '%s'", path);
error = -1;
goto done;
}
prefixed = git_path_equal_or_prefixed(path, entry->path);
if (prefixed == GIT_PATH_EQUAL)
continue;
if (prefixed == GIT_PATH_PREFIX)
conflict->directoryfile = 1;
break;
}
}
done:
return error;
}
static int checkout_get_conflicts(
checkout_data *data,
git_iterator *workdir,
git_vector *pathspec)
{
int error = 0;
if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED)
return 0;
if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 ||
(error = checkout_conflicts_coalesce_renames(data)) < 0 ||
(error = checkout_conflicts_mark_directoryfile(data)) < 0)
goto done;
done:
return error;
}
static int checkout_get_actions( static int checkout_get_actions(
uint32_t **actions_ptr, uint32_t **actions_ptr,
size_t **counts_ptr, size_t **counts_ptr,
...@@ -659,6 +1026,12 @@ static int checkout_get_actions( ...@@ -659,6 +1026,12 @@ static int checkout_get_actions(
goto fail; goto fail;
} }
if ((error = checkout_get_conflicts(data, workdir, &pathspec)) < 0)
goto fail;
counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts);
git_pathspec__vfree(&pathspec); git_pathspec__vfree(&pathspec);
git_pool_clear(&pathpool); git_pool_clear(&pathpool);
...@@ -707,6 +1080,7 @@ static int blob_content_to_file( ...@@ -707,6 +1080,7 @@ static int blob_content_to_file(
struct stat *st, struct stat *st,
git_blob *blob, git_blob *blob,
const char *path, const char *path,
const char * hint_path,
mode_t entry_filemode, mode_t entry_filemode,
git_checkout_opts *opts) git_checkout_opts *opts)
{ {
...@@ -715,9 +1089,12 @@ static int blob_content_to_file( ...@@ -715,9 +1089,12 @@ static int blob_content_to_file(
git_buf out = GIT_BUF_INIT; git_buf out = GIT_BUF_INIT;
git_filter_list *fl = NULL; git_filter_list *fl = NULL;
if (hint_path == NULL)
hint_path = path;
if (!opts->disable_filters) if (!opts->disable_filters)
error = git_filter_list_load( error = git_filter_list_load(
&fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE); &fl, git_blob_owner(blob), blob, hint_path, GIT_FILTER_TO_WORKTREE);
if (!error) if (!error)
error = git_filter_list_apply_to_blob(&out, fl, blob); error = git_filter_list_apply_to_blob(&out, fl, blob);
...@@ -886,34 +1263,26 @@ static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) ...@@ -886,34 +1263,26 @@ static int checkout_safe_for_update_only(const char *path, mode_t expected_mode)
return 0; return 0;
} }
static int checkout_blob( static int checkout_write_content(
checkout_data *data, checkout_data *data,
const git_diff_file *file) const git_oid *oid,
const char *full_path,
const char *hint_path,
unsigned int mode,
struct stat *st)
{ {
int error = 0; int error = 0;
git_blob *blob; git_blob *blob;
struct stat st;
git_buf_truncate(&data->path, data->workdir_len);
if (git_buf_puts(&data->path, file->path) < 0)
return -1;
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
int rval = checkout_safe_for_update_only(
git_buf_cstr(&data->path), file->mode);
if (rval <= 0)
return rval;
}
if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0) if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0)
return error; return error;
if (S_ISLNK(file->mode)) if (S_ISLNK(mode))
error = blob_content_to_link( error = blob_content_to_link(
&st, blob, git_buf_cstr(&data->path), data->opts.dir_mode, data->can_symlink); st, blob, full_path, data->opts.dir_mode, data->can_symlink);
else else
error = blob_content_to_file( error = blob_content_to_file(
&st, blob, git_buf_cstr(&data->path), file->mode, &data->opts); st, blob, full_path, hint_path, mode, &data->opts);
git_blob_free(blob); git_blob_free(blob);
...@@ -928,6 +1297,30 @@ static int checkout_blob( ...@@ -928,6 +1297,30 @@ static int checkout_blob(
error = 0; error = 0;
} }
return error;
}
static int checkout_blob(
checkout_data *data,
const git_diff_file *file)
{
int error = 0;
struct stat st;
git_buf_truncate(&data->path, data->workdir_len);
if (git_buf_puts(&data->path, file->path) < 0)
return -1;
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
int rval = checkout_safe_for_update_only(
git_buf_cstr(&data->path), file->mode);
if (rval <= 0)
return rval;
}
error = checkout_write_content(
data, &file->oid, git_buf_cstr(&data->path), NULL, file->mode, &st);
/* update the index unless prevented */ /* update the index unless prevented */
if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
error = checkout_update_index(data, file, &st); error = checkout_update_index(data, file, &st);
...@@ -1098,8 +1491,279 @@ static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) ...@@ -1098,8 +1491,279 @@ static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
return error; return error;
} }
static int conflict_entry_name(
git_buf *out,
const char *side_name,
const char *filename)
{
if (git_buf_puts(out, side_name) < 0 ||
git_buf_putc(out, ':') < 0 ||
git_buf_puts(out, filename) < 0)
return -1;
return 0;
}
static int checkout_path_suffixed(git_buf *path, const char *suffix)
{
size_t path_len;
int i = 0, error = 0;
if ((error = git_buf_putc(path, '~')) < 0 || (error = git_buf_puts(path, suffix)) < 0)
return -1;
path_len = git_buf_len(path);
while (git_path_exists(git_buf_cstr(path)) && i < INT_MAX) {
git_buf_truncate(path, path_len);
if ((error = git_buf_putc(path, '_')) < 0 ||
(error = git_buf_printf(path, "%d", i)) < 0)
return error;
i++;
}
if (i == INT_MAX) {
git_buf_truncate(path, path_len);
giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path);
return GIT_EEXISTS;
}
return 0;
}
static int checkout_write_entry(
checkout_data *data,
checkout_conflictdata *conflict,
const git_index_entry *side)
{
const char *hint_path = NULL, *suffix;
struct stat st;
int error;
assert (side == conflict->ours || side == conflict->theirs);
git_buf_truncate(&data->path, data->workdir_len);
if (git_buf_puts(&data->path, side->path) < 0)
return -1;
if ((conflict->name_collision || conflict->directoryfile) &&
(data->strategy & GIT_CHECKOUT_USE_OURS) == 0 &&
(data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) {
if (side == conflict->ours)
suffix = data->opts.our_label ? data->opts.our_label :
"ours";
else
suffix = data->opts.their_label ? data->opts.their_label :
"theirs";
if (checkout_path_suffixed(&data->path, suffix) < 0)
return -1;
hint_path = side->path;
}
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
(error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0)
return error;
return checkout_write_content(data,
&side->oid, git_buf_cstr(&data->path), hint_path, side->mode, &st);
}
static int checkout_write_entries(
checkout_data *data,
checkout_conflictdata *conflict)
{
int error = 0;
if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0)
error = checkout_write_entry(data, conflict, conflict->theirs);
return error;
}
static int checkout_merge_path(
git_buf *out,
checkout_data *data,
checkout_conflictdata *conflict,
git_merge_file_result *result)
{
const char *our_label_raw, *their_label_raw, *suffix;
int error = 0;
if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0)
return error;
/* Most conflicts simply use the filename in the index */
if (!conflict->name_collision)
return 0;
/* Rename 2->1 conflicts need the branch name appended */
our_label_raw = data->opts.our_label ? data->opts.our_label : "ours";
their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs";
suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw;
if ((error = checkout_path_suffixed(out, suffix)) < 0)
return error;
return 0;
}
static int checkout_write_merge(
checkout_data *data,
checkout_conflictdata *conflict)
{
git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT,
path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT;
git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,
ours = GIT_MERGE_FILE_INPUT_INIT,
theirs = GIT_MERGE_FILE_INPUT_INIT;
git_merge_file_result result = GIT_MERGE_FILE_RESULT_INIT;
git_filebuf output = GIT_FILEBUF_INIT;
int error = 0;
if ((conflict->ancestor &&
(error = git_merge_file_input_from_index_entry(
&ancestor, data->repo, conflict->ancestor)) < 0) ||
(error = git_merge_file_input_from_index_entry(
&ours, data->repo, conflict->ours)) < 0 ||
(error = git_merge_file_input_from_index_entry(
&theirs, data->repo, conflict->theirs)) < 0)
goto done;
ancestor.label = NULL;
ours.label = data->opts.our_label ? data->opts.our_label : "ours";
theirs.label = data->opts.their_label ? data->opts.their_label : "theirs";
/* If all the paths are identical, decorate the diff3 file with the branch
* names. Otherwise, append branch_name:path.
*/
if (conflict->ours && conflict->theirs &&
strcmp(conflict->ours->path, conflict->theirs->path) != 0) {
if ((error = conflict_entry_name(
&our_label, ours.label, conflict->ours->path)) < 0 ||
(error = conflict_entry_name(
&their_label, theirs.label, conflict->theirs->path)) < 0)
goto done;
ours.label = git_buf_cstr(&our_label);
theirs.label = git_buf_cstr(&their_label);
}
if ((error = git_merge_files(&result, &ancestor, &ours, &theirs, 0)) < 0)
goto done;
if (result.path == NULL || result.mode == 0) {
giterr_set(GITERR_CHECKOUT, "Could not merge contents of file");
error = GIT_EMERGECONFLICT;
goto done;
}
if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0)
goto done;
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
(error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0)
goto done;
if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 ||
(error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER)) < 0 ||
(error = git_filebuf_write(&output, result.data, result.len)) < 0 ||
(error = git_filebuf_commit(&output, result.mode)) < 0)
goto done;
done:
git_buf_free(&our_label);
git_buf_free(&their_label);
git_merge_file_input_free(&ancestor);
git_merge_file_input_free(&ours);
git_merge_file_input_free(&theirs);
git_merge_file_result_free(&result);
git_buf_free(&path_workdir);
git_buf_free(&path_suffixed);
return error;
}
static int checkout_create_conflicts(checkout_data *data)
{
git_vector conflicts = GIT_VECTOR_INIT;
checkout_conflictdata *conflict;
size_t i;
int error = 0;
git_vector_foreach(&data->conflicts, i, conflict) {
/* Both deleted: nothing to do */
if (conflict->ours == NULL && conflict->theirs == NULL)
error = 0;
else if ((data->strategy & GIT_CHECKOUT_USE_OURS) &&
conflict->ours)
error = checkout_write_entry(data, conflict, conflict->ours);
else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) &&
conflict->theirs)
error = checkout_write_entry(data, conflict, conflict->theirs);
/* Ignore the other side of name collisions. */
else if ((data->strategy & GIT_CHECKOUT_USE_OURS) &&
!conflict->ours && conflict->name_collision)
error = 0;
else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) &&
!conflict->theirs && conflict->name_collision)
error = 0;
/* For modify/delete, name collisions and d/f conflicts, write
* the file (potentially with the name mangled.
*/
else if (conflict->ours != NULL && conflict->theirs == NULL)
error = checkout_write_entry(data, conflict, conflict->ours);
else if (conflict->ours == NULL && conflict->theirs != NULL)
error = checkout_write_entry(data, conflict, conflict->theirs);
/* Add/add conflicts and rename 1->2 conflicts, write the
* ours/theirs sides (potentially name mangled).
*/
else if (conflict->one_to_two)
error = checkout_write_entries(data, conflict);
/* If all sides are links, write the ours side */
else if (S_ISLNK(conflict->ours->mode) &&
S_ISLNK(conflict->theirs->mode))
error = checkout_write_entry(data, conflict, conflict->ours);
/* Link/file conflicts, write the file side */
else if (S_ISLNK(conflict->ours->mode))
error = checkout_write_entry(data, conflict, conflict->theirs);
else if (S_ISLNK(conflict->theirs->mode))
error = checkout_write_entry(data, conflict, conflict->ours);
else
error = checkout_write_merge(data, conflict);
if (error)
break;
data->completed_steps++;
report_progress(data,
conflict->ours ? conflict->ours->path :
(conflict->theirs ? conflict->theirs->path : conflict->ancestor->path));
}
return error;
}
static void checkout_data_clear(checkout_data *data) static void checkout_data_clear(checkout_data *data)
{ {
checkout_conflictdata *conflict;
size_t i;
if (data->opts_free_baseline) { if (data->opts_free_baseline) {
git_tree_free(data->opts.baseline); git_tree_free(data->opts.baseline);
data->opts.baseline = NULL; data->opts.baseline = NULL;
...@@ -1108,6 +1772,11 @@ static void checkout_data_clear(checkout_data *data) ...@@ -1108,6 +1772,11 @@ static void checkout_data_clear(checkout_data *data)
git_vector_free(&data->removes); git_vector_free(&data->removes);
git_pool_clear(&data->pool); git_pool_clear(&data->pool);
git_vector_foreach(&data->conflicts, i, conflict)
git__free(conflict);
git_vector_free(&data->conflicts);
git__free(data->pfx); git__free(data->pfx);
data->pfx = NULL; data->pfx = NULL;
...@@ -1172,7 +1841,17 @@ static int checkout_data_init( ...@@ -1172,7 +1841,17 @@ static int checkout_data_init(
(error = git_index_read(data->index)) < 0) (error = git_index_read(data->index)) < 0)
goto cleanup; goto cleanup;
/* clear the REUC when doing a tree or commit checkout */ /* cannot checkout if unresolved conflicts exist */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 &&
git_index_has_conflicts(data->index)) {
error = GIT_EMERGECONFLICT;
giterr_set(GITERR_CHECKOUT,
"unresolved conflicts exist in the index");
goto cleanup;
}
/* clean conflict data when doing a tree or commit checkout */
git_index_name_clear(data->index);
git_index_reuc_clear(data->index); git_index_reuc_clear(data->index);
} }
} }
...@@ -1214,6 +1893,7 @@ static int checkout_data_init( ...@@ -1214,6 +1893,7 @@ static int checkout_data_init(
} }
if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
(error = git_vector_init(&data->conflicts, 0, NULL)) < 0 ||
(error = git_pool_init(&data->pool, 1, 0)) < 0 || (error = git_pool_init(&data->pool, 1, 0)) < 0 ||
(error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 ||
(error = git_path_to_dir(&data->path)) < 0) (error = git_path_to_dir(&data->path)) < 0)
...@@ -1284,14 +1964,16 @@ int git_checkout_iterator( ...@@ -1284,14 +1964,16 @@ int git_checkout_iterator(
goto cleanup; goto cleanup;
/* Loop through diff (and working directory iterator) building a list of /* Loop through diff (and working directory iterator) building a list of
* actions to be taken, plus look for conflicts and send notifications. * actions to be taken, plus look for conflicts and send notifications,
* then loop through conflicts.
*/ */
if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0) if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
goto cleanup; goto cleanup;
data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
counts[CHECKOUT_ACTION__UPDATE_BLOB] + counts[CHECKOUT_ACTION__UPDATE_BLOB] +
counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]; counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] +
counts[CHECKOUT_ACTION__UPDATE_CONFLICT];
report_progress(&data, NULL); /* establish 0 baseline */ report_progress(&data, NULL); /* establish 0 baseline */
...@@ -1310,6 +1992,10 @@ int git_checkout_iterator( ...@@ -1310,6 +1992,10 @@ int git_checkout_iterator(
(error = checkout_create_submodules(actions, &data)) < 0) (error = checkout_create_submodules(actions, &data)) < 0)
goto cleanup; goto cleanup;
if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 &&
(error = checkout_create_conflicts(&data)) < 0)
goto cleanup;
assert(data.completed_steps == data.total_steps); assert(data.completed_steps == data.total_steps);
cleanup: cleanup:
......
...@@ -47,7 +47,7 @@ GIT_INLINE(int) merge_file_best_mode( ...@@ -47,7 +47,7 @@ GIT_INLINE(int) merge_file_best_mode(
* assume executable. Otherwise, if any mode changed from the ancestor, * assume executable. Otherwise, if any mode changed from the ancestor,
* use that one. * use that one.
*/ */
if (GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) { if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE || if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE ||
theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE) theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE)
return GIT_FILEMODE_BLOB_EXECUTABLE; return GIT_FILEMODE_BLOB_EXECUTABLE;
......
...@@ -358,6 +358,34 @@ extern int git_path_dirload_with_stat( ...@@ -358,6 +358,34 @@ extern int git_path_dirload_with_stat(
const char *end_stat, const char *end_stat,
git_vector *contents); git_vector *contents);
enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 };
/*
* Determines if a path is equal to or potentially a child of another.
* @param parent The possible parent
* @param child The possible child
*/
GIT_INLINE(int) git_path_equal_or_prefixed(
const char *parent,
const char *child)
{
const char *p = parent, *c = child;
while (*p && *c) {
if (*p++ != *c++)
return GIT_PATH_NOTEQUAL;
}
if (*p != '\0')
return GIT_PATH_NOTEQUAL;
if (*c == '\0')
return GIT_PATH_EQUAL;
if (*c == '/')
return GIT_PATH_PREFIX;
return GIT_PATH_NOTEQUAL;
}
/* translate errno to libgit2 error code and set error message */ /* translate errno to libgit2 error code and set error message */
extern int git_path_set_error( extern int git_path_set_error(
int errno_value, const char *path, const char *action); int errno_value, const char *path, const char *action);
......
...@@ -135,7 +135,7 @@ int git_reset( ...@@ -135,7 +135,7 @@ int git_reset(
if (reset_type == GIT_RESET_HARD) { if (reset_type == GIT_RESET_HARD) {
/* overwrite working directory with HEAD */ /* overwrite working directory with HEAD */
opts.checkout_strategy = GIT_CHECKOUT_FORCE; opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_SKIP_UNMERGED;
if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)
goto cleanup; goto cleanup;
......
#include "clar_libgit2.h"
#include "git2/repository.h"
#include "git2/sys/index.h"
#include "fileops.h"
static git_repository *g_repo;
static git_index *g_index;
#define TEST_REPO_PATH "merge-resolve"
#define CONFLICTING_ANCESTOR_OID "d427e0b2e138501a3d15cc376077a3631e15bd46"
#define CONFLICTING_OURS_OID "4e886e602529caa9ab11d71f86634bd1b6e0de10"
#define CONFLICTING_THEIRS_OID "2bd0a343aeef7a2cf0d158478966a6e587ff3863"
#define AUTOMERGEABLE_ANCESTOR_OID "6212c31dab5e482247d7977e4f0dd3601decf13b"
#define AUTOMERGEABLE_OURS_OID "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"
#define AUTOMERGEABLE_THEIRS_OID "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe"
#define LINK_ANCESTOR_OID "1a010b1c0f081b2e8901d55307a15c29ff30af0e"
#define LINK_OURS_OID "72ea499e108df5ff0a4a913e7655bbeeb1fb69f2"
#define LINK_THEIRS_OID "8bfb012a6d809e499bd8d3e194a3929bc8995b93"
#define LINK_ANCESTOR_TARGET "file"
#define LINK_OURS_TARGET "other-file"
#define LINK_THEIRS_TARGET "still-another-file"
#define CONFLICTING_OURS_FILE \
"this file is changed in master and branch\n"
#define CONFLICTING_THEIRS_FILE \
"this file is changed in branch and master\n"
#define CONFLICTING_DIFF3_FILE \
"<<<<<<< ours\n" \
"this file is changed in master and branch\n" \
"=======\n" \
"this file is changed in branch and master\n" \
">>>>>>> theirs\n"
#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"
struct checkout_index_entry {
uint16_t mode;
char oid_str[41];
int stage;
char path[128];
};
struct checkout_name_entry {
char ancestor[64];
char ours[64];
char theirs[64];
};
void test_checkout_conflict__initialize(void)
{
g_repo = cl_git_sandbox_init(TEST_REPO_PATH);
git_repository_index(&g_index, g_repo);
cl_git_rewritefile(
TEST_REPO_PATH "/.gitattributes",
"* text eol=lf\n");
}
void test_checkout_conflict__cleanup(void)
{
git_index_free(g_index);
cl_git_sandbox_cleanup();
}
static void create_index(struct checkout_index_entry *entries, size_t entries_len)
{
git_buf path = GIT_BUF_INIT;
size_t i;
for (i = 0; i < entries_len; i++) {
git_buf_joinpath(&path, TEST_REPO_PATH, entries[i].path);
if (entries[i].stage == 3 && (i == 0 || strcmp(entries[i-1].path, entries[i].path) != 0 || entries[i-1].stage != 2))
p_unlink(git_buf_cstr(&path));
git_index_remove_bypath(g_index, entries[i].path);
}
for (i = 0; i < entries_len; i++) {
git_index_entry entry;
memset(&entry, 0x0, sizeof(git_index_entry));
entry.mode = entries[i].mode;
entry.flags = entries[i].stage << GIT_IDXENTRY_STAGESHIFT;
git_oid_fromstr(&entry.oid, entries[i].oid_str);
entry.path = entries[i].path;
cl_git_pass(git_index_add(g_index, &entry));
}
git_buf_free(&path);
}
static void create_index_names(struct checkout_name_entry *entries, size_t entries_len)
{
size_t i;
for (i = 0; i < entries_len; i++) {
cl_git_pass(git_index_name_add(g_index,
strlen(entries[i].ancestor) == 0 ? NULL : entries[i].ancestor,
strlen(entries[i].ours) == 0 ? NULL : entries[i].ours,
strlen(entries[i].theirs) == 0 ? NULL : entries[i].theirs));
}
}
static void create_conflicting_index(void)
{
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting.txt" },
{ 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" },
};
create_index(checkout_index_entries, 3);
git_index_write(g_index);
}
static void ensure_workdir_contents(const char *path, const char *contents)
{
git_buf fullpath = GIT_BUF_INIT, data_buf = GIT_BUF_INIT;
cl_git_pass(
git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
cl_git_pass(git_futils_readbuffer(&data_buf, git_buf_cstr(&fullpath)));
cl_assert(strcmp(git_buf_cstr(&data_buf), contents) == 0);
git_buf_free(&fullpath);
git_buf_free(&data_buf);
}
static void ensure_workdir_oid(const char *path, const char *oid_str)
{
git_oid expected, actual;
cl_git_pass(git_oid_fromstr(&expected, oid_str));
cl_git_pass(git_repository_hashfile(&actual, g_repo, path, GIT_OBJ_BLOB, NULL));
cl_assert(git_oid_cmp(&expected, &actual) == 0);
}
static void ensure_workdir_mode(const char *path, int mode)
{
#ifndef GIT_WIN32
git_buf fullpath = GIT_BUF_INIT;
struct stat st;
cl_git_pass(
git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
cl_git_pass(p_stat(git_buf_cstr(&fullpath), &st));
cl_assert_equal_i(mode, st.st_mode);
git_buf_free(&fullpath);
#endif
}
static void ensure_workdir(const char *path, int mode, const char *oid_str)
{
ensure_workdir_mode(path, mode);
ensure_workdir_oid(path, oid_str);
}
static void ensure_workdir_link(const char *path, const char *target)
{
#ifdef GIT_WIN32
ensure_workdir_contents(path, target);
#else
git_buf fullpath = GIT_BUF_INIT;
char actual[1024];
struct stat st;
int len;
cl_git_pass(
git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
cl_git_pass(p_lstat(git_buf_cstr(&fullpath), &st));
cl_assert(S_ISLNK(st.st_mode));
cl_assert((len = p_readlink(git_buf_cstr(&fullpath), actual, 1024)) > 0);
actual[len] = '\0';
cl_assert(strcmp(actual, target) == 0);
git_buf_free(&fullpath);
#endif
}
void test_checkout_conflict__ignored(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
opts.checkout_strategy |= GIT_CHECKOUT_SKIP_UNMERGED;
create_conflicting_index();
cl_git_pass(p_unlink(TEST_REPO_PATH "/conflicting.txt"));
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
cl_assert(!git_path_exists(TEST_REPO_PATH "/conflicting.txt"));
}
void test_checkout_conflict__ours(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
opts.checkout_strategy |= GIT_CHECKOUT_USE_OURS;
create_conflicting_index();
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_contents("conflicting.txt", CONFLICTING_OURS_FILE);
}
void test_checkout_conflict__theirs(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
opts.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS;
create_conflicting_index();
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_contents("conflicting.txt", CONFLICTING_THEIRS_FILE);
}
void test_checkout_conflict__diff3(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
create_conflicting_index();
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE);
}
void test_checkout_conflict__automerge(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" },
{ 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" },
{ 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" },
};
create_index(checkout_index_entries, 3);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE);
}
void test_checkout_conflict__directory_file(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" },
{ 0100644, CONFLICTING_OURS_OID, 2, "df-1" },
{ 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" },
{ 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" },
{ 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" },
{ 0100644, CONFLICTING_OURS_OID, 2, "df-4" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" },
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 12);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("df-1~ours", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-2~theirs", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-3~theirs", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("df-4~ours", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID);
}
void test_checkout_conflict__directory_file_with_custom_labels(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" },
{ 0100644, CONFLICTING_OURS_OID, 2, "df-1" },
{ 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" },
{ 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" },
{ 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" },
{ 0100644, CONFLICTING_OURS_OID, 2, "df-4" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" },
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
opts.our_label = "HEAD";
opts.their_label = "branch";
create_index(checkout_index_entries, 12);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("df-1~HEAD", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-2~branch", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-3~branch", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("df-4~HEAD", CONFLICTING_OURS_OID);
ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID);
}
void test_checkout_conflict__link_file(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-1" },
{ 0100644, CONFLICTING_OURS_OID, 2, "link-1" },
{ 0120000, LINK_THEIRS_OID, 3, "link-1" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-2" },
{ 0120000, LINK_OURS_OID, 2, "link-2" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "link-2" },
{ 0120000, LINK_ANCESTOR_OID, 1, "link-3" },
{ 0100644, CONFLICTING_OURS_OID, 2, "link-3" },
{ 0120000, LINK_THEIRS_OID, 3, "link-3" },
{ 0120000, LINK_ANCESTOR_OID, 1, "link-4" },
{ 0120000, LINK_OURS_OID, 2, "link-4" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "link-4" },
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 12);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
/* Typechange conflicts always keep the file in the workdir */
ensure_workdir_oid("link-1", CONFLICTING_OURS_OID);
ensure_workdir_oid("link-2", CONFLICTING_THEIRS_OID);
ensure_workdir_oid("link-3", CONFLICTING_OURS_OID);
ensure_workdir_oid("link-4", CONFLICTING_THEIRS_OID);
}
void test_checkout_conflict__links(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0120000, LINK_ANCESTOR_OID, 1, "link-1" },
{ 0120000, LINK_OURS_OID, 2, "link-1" },
{ 0120000, LINK_THEIRS_OID, 3, "link-1" },
{ 0120000, LINK_OURS_OID, 2, "link-2" },
{ 0120000, LINK_THEIRS_OID, 3, "link-2" },
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 5);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
/* Conflicts with links always keep the ours side (even with -Xtheirs) */
ensure_workdir_link("link-1", LINK_OURS_TARGET);
ensure_workdir_link("link-2", LINK_OURS_TARGET);
}
void test_checkout_conflict__add_add(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" },
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 2);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
/* Add/add writes diff3 files */
ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE);
}
void test_checkout_conflict__mode_change(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-1" },
{ 0100755, CONFLICTING_ANCESTOR_OID, 2, "executable-1" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "executable-1" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-2" },
{ 0100644, CONFLICTING_OURS_OID, 2, "executable-2" },
{ 0100755, CONFLICTING_ANCESTOR_OID, 3, "executable-2" },
{ 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-3" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 2, "executable-3" },
{ 0100755, CONFLICTING_THEIRS_OID, 3, "executable-3" },
{ 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-4" },
{ 0100755, CONFLICTING_OURS_OID, 2, "executable-4" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 3, "executable-4" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-5" },
{ 0100755, CONFLICTING_OURS_OID, 2, "executable-5" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "executable-5" },
{ 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-6" },
{ 0100644, CONFLICTING_OURS_OID, 2, "executable-6" },
{ 0100755, CONFLICTING_THEIRS_OID, 3, "executable-6" },
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 18);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
/* Keep the modified mode */
ensure_workdir_oid("executable-1", CONFLICTING_THEIRS_OID);
ensure_workdir_mode("executable-1", 0100755);
ensure_workdir_oid("executable-2", CONFLICTING_OURS_OID);
ensure_workdir_mode("executable-2", 0100755);
ensure_workdir_oid("executable-3", CONFLICTING_THEIRS_OID);
ensure_workdir_mode("executable-3", 0100644);
ensure_workdir_oid("executable-4", CONFLICTING_OURS_OID);
ensure_workdir_mode("executable-4", 0100644);
ensure_workdir_contents("executable-5", CONFLICTING_DIFF3_FILE);
ensure_workdir_mode("executable-5", 0100755);
ensure_workdir_contents("executable-6", CONFLICTING_DIFF3_FILE);
ensure_workdir_mode("executable-6", 0100644);
}
void test_checkout_conflict__renames(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
{ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
{ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" },
{ 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" },
{ 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" },
{ 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
{ 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" },
{ 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" },
{ 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" },
{ 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
{ 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
{ 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
{ 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
{ 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
{ 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" },
{ 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" },
{ 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" },
{ 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" },
{ 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" },
{ 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" },
{ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" },
{ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" },
{ 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" },
{ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" },
{ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" },
{ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" },
{ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }
};
struct checkout_name_entry checkout_name_entries[] = {
{
"3a-renamed-in-ours-deleted-in-theirs.txt",
"3a-newname-in-ours-deleted-in-theirs.txt",
""
},
{
"3b-renamed-in-theirs-deleted-in-ours.txt",
"",
"3b-newname-in-theirs-deleted-in-ours.txt"
},
{
"4a-renamed-in-ours-added-in-theirs.txt",
"4a-newname-in-ours-added-in-theirs.txt",
""
},
{
"4b-renamed-in-theirs-added-in-ours.txt",
"",
"4b-newname-in-theirs-added-in-ours.txt"
},
{
"5a-renamed-in-ours-added-in-theirs.txt",
"5a-newname-in-ours-added-in-theirs.txt",
"5a-renamed-in-ours-added-in-theirs.txt"
},
{
"5b-renamed-in-theirs-added-in-ours.txt",
"5b-renamed-in-theirs-added-in-ours.txt",
"5b-newname-in-theirs-added-in-ours.txt"
},
{
"6-both-renamed-1-to-2.txt",
"6-both-renamed-1-to-2-ours.txt",
"6-both-renamed-1-to-2-theirs.txt"
},
{
"7-both-renamed-side-1.txt",
"7-both-renamed.txt",
"7-both-renamed-side-1.txt"
},
{
"7-both-renamed-side-2.txt",
"7-both-renamed-side-2.txt",
"7-both-renamed.txt"
}
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 41);
create_index_names(checkout_name_entries, 9);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir("0a-no-change.txt",
0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e");
ensure_workdir("0b-duplicated-in-ours.txt",
0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6");
ensure_workdir("0b-rewritten-in-ours.txt",
0100644, "4c7e515d6d52d820496858f2f059ece69e99e2e3");
ensure_workdir("0c-duplicated-in-theirs.txt",
0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31");
ensure_workdir("0c-rewritten-in-theirs.txt",
0100644, "4648d658682d1155c2a3db5b0c53305e26884ea5");
ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt",
0100644, "0d872f8e871a30208305978ecbf9e66d864f1638");
ensure_workdir("1a-newname-in-ours.txt",
0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb");
ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt",
0100644, "ed9523e62e453e50dd9be1606af19399b96e397a");
ensure_workdir("1b-newname-in-theirs.txt",
0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136");
ensure_workdir("2-newname-in-both.txt",
0100644, "178940b450f238a56c0d75b7955cb57b38191982");
ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt",
0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9");
ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt",
0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495");
ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~ours",
0100644, "227792b52aaa0b238bea00ec7e509b02623f168c");
ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~theirs",
0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a");
ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~ours",
0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9");
ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~theirs",
0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db");
ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~ours",
0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436");
ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~theirs",
0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714");
ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~ours",
0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced");
ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~theirs",
0100644, "63247125386de9ec90a27ad36169307bf8a11a38");
ensure_workdir("6-both-renamed-1-to-2-ours.txt",
0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
ensure_workdir("6-both-renamed-1-to-2-theirs.txt",
0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
ensure_workdir("7-both-renamed.txt~ours",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
ensure_workdir("7-both-renamed.txt~theirs",
0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
}
void test_checkout_conflict__rename_keep_ours(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
{ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
{ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" },
{ 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" },
{ 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" },
{ 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
{ 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" },
{ 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" },
{ 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" },
{ 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
{ 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
{ 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
{ 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
{ 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
{ 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" },
{ 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" },
{ 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" },
{ 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" },
{ 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" },
{ 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" },
{ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" },
{ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" },
{ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" },
{ 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" },
{ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" },
{ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" },
{ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" },
{ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" },
{ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }
};
struct checkout_name_entry checkout_name_entries[] = {
{
"3a-renamed-in-ours-deleted-in-theirs.txt",
"3a-newname-in-ours-deleted-in-theirs.txt",
""
},
{
"3b-renamed-in-theirs-deleted-in-ours.txt",
"",
"3b-newname-in-theirs-deleted-in-ours.txt"
},
{
"4a-renamed-in-ours-added-in-theirs.txt",
"4a-newname-in-ours-added-in-theirs.txt",
""
},
{
"4b-renamed-in-theirs-added-in-ours.txt",
"",
"4b-newname-in-theirs-added-in-ours.txt"
},
{
"5a-renamed-in-ours-added-in-theirs.txt",
"5a-newname-in-ours-added-in-theirs.txt",
"5a-renamed-in-ours-added-in-theirs.txt"
},
{
"5b-renamed-in-theirs-added-in-ours.txt",
"5b-renamed-in-theirs-added-in-ours.txt",
"5b-newname-in-theirs-added-in-ours.txt"
},
{
"6-both-renamed-1-to-2.txt",
"6-both-renamed-1-to-2-ours.txt",
"6-both-renamed-1-to-2-theirs.txt"
},
{
"7-both-renamed-side-1.txt",
"7-both-renamed.txt",
"7-both-renamed-side-1.txt"
},
{
"7-both-renamed-side-2.txt",
"7-both-renamed-side-2.txt",
"7-both-renamed.txt"
}
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
create_index(checkout_index_entries, 41);
create_index_names(checkout_name_entries, 9);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir("0a-no-change.txt",
0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e");
ensure_workdir("0b-duplicated-in-ours.txt",
0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6");
ensure_workdir("0b-rewritten-in-ours.txt",
0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e");
ensure_workdir("0c-duplicated-in-theirs.txt",
0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31");
ensure_workdir("0c-rewritten-in-theirs.txt",
0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09");
ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt",
0100644, "0d872f8e871a30208305978ecbf9e66d864f1638");
ensure_workdir("1a-newname-in-ours.txt",
0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb");
ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt",
0100644, "ed9523e62e453e50dd9be1606af19399b96e397a");
ensure_workdir("1b-newname-in-theirs.txt",
0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136");
ensure_workdir("2-newname-in-both.txt",
0100644, "178940b450f238a56c0d75b7955cb57b38191982");
ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt",
0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9");
ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt",
0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495");
ensure_workdir("4a-newname-in-ours-added-in-theirs.txt",
0100644, "227792b52aaa0b238bea00ec7e509b02623f168c");
ensure_workdir("4b-newname-in-theirs-added-in-ours.txt",
0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9");
ensure_workdir("5a-newname-in-ours-added-in-theirs.txt",
0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436");
ensure_workdir("5b-newname-in-theirs-added-in-ours.txt",
0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced");
ensure_workdir("6-both-renamed-1-to-2-ours.txt",
0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
ensure_workdir("7-both-renamed.txt",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
}
void test_checkout_conflict__name_mangled_file_exists_in_workdir(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-one-side-one.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-one-side-one.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-one-side-two.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-one-side-two.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-one.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-one.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-two-side-one.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-two-side-one.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-two-side-two.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-two-side-two.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-two.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-two.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-three-side-one.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-three-side-one.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-three-side-two.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-three-side-two.txt" },
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-three.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-three.txt" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" },
{ 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" },
{ 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" },
{ 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" },
};
struct checkout_name_entry checkout_name_entries[] = {
{
"test-one-side-one.txt",
"test-one.txt",
"test-one-side-one.txt"
},
{
"test-one-side-two.txt",
"test-one-side-two.txt",
"test-one.txt"
},
{
"test-two-side-one.txt",
"test-two.txt",
"test-two-side-one.txt"
},
{
"test-two-side-two.txt",
"test-two-side-two.txt",
"test-two.txt"
},
{
"test-three-side-one.txt",
"test-three.txt",
"test-three-side-one.txt"
},
{
"test-three-side-two.txt",
"test-three-side-two.txt",
"test-three.txt"
}
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
create_index(checkout_index_entries, 24);
create_index_names(checkout_name_entries, 6);
git_index_write(g_index);
/* Add some files on disk that conflict with the names that would be chosen
* for the files written for each side. */
cl_git_rewritefile("merge-resolve/test-one.txt~ours",
"Expect index contents to be written to ~ours_0");
cl_git_rewritefile("merge-resolve/test-one.txt~theirs",
"Expect index contents to be written to ~theirs_0");
cl_git_rewritefile("merge-resolve/test-two.txt~ours",
"Expect index contents to be written to ~ours_3");
cl_git_rewritefile("merge-resolve/test-two.txt~theirs",
"Expect index contents to be written to ~theirs_3");
cl_git_rewritefile("merge-resolve/test-two.txt~ours_0",
"Expect index contents to be written to ~ours_3");
cl_git_rewritefile("merge-resolve/test-two.txt~theirs_0",
"Expect index contents to be written to ~theirs_3");
cl_git_rewritefile("merge-resolve/test-two.txt~ours_1",
"Expect index contents to be written to ~ours_3");
cl_git_rewritefile("merge-resolve/test-two.txt~theirs_1",
"Expect index contents to be written to ~theirs_3");
cl_git_rewritefile("merge-resolve/test-two.txt~ours_2",
"Expect index contents to be written to ~ours_3");
cl_git_rewritefile("merge-resolve/test-two.txt~theirs_2",
"Expect index contents to be written to ~theirs_3");
cl_git_rewritefile("merge-resolve/test-three.txt~Ours",
"Expect case insensitive filesystems to create ~ours_0");
cl_git_rewritefile("merge-resolve/test-three.txt~THEIRS",
"Expect case insensitive filesystems to create ~theirs_0");
cl_git_rewritefile("merge-resolve/directory_file-one~ours",
"Index contents written to ~ours_0 in this D/F conflict");
cl_git_rewritefile("merge-resolve/directory_file-two~theirs",
"Index contents written to ~theirs_0 in this D/F conflict");
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir("test-one.txt~ours_0",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
ensure_workdir("test-one.txt~theirs_0",
0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
ensure_workdir("test-two.txt~ours_3",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
ensure_workdir("test-two.txt~theirs_3",
0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
/* Name is mangled on case insensitive only */
#if defined(GIT_WIN32) || defined(__APPLE__)
ensure_workdir("test-three.txt~ours_0",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
ensure_workdir("test-three.txt~theirs_0",
0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
#else
ensure_workdir("test-three.txt~ours",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
ensure_workdir("test-three.txt~theirs",
0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
#endif
ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID);
ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID);
}
void test_checkout_conflict__update_only(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" },
{ 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" },
{ 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "modify-delete" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "modify-delete" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" },
{ 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" },
{ 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" },
{ 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" },
};
opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_ONLY;
create_index(checkout_index_entries, 3);
git_index_write(g_index);
cl_git_pass(p_mkdir("merge-resolve/directory_file-two", 0777));
cl_git_rewritefile("merge-resolve/directory_file-two/file", CONFLICTING_OURS_FILE);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE);
ensure_workdir("directory_file-two/file", 0100644, CONFLICTING_OURS_OID);
cl_assert(!git_path_exists("merge-resolve/modify-delete"));
cl_assert(!git_path_exists("merge-resolve/test-one.txt"));
cl_assert(!git_path_exists("merge-resolve/test-one-side-one.txt"));
cl_assert(!git_path_exists("merge-resolve/test-one-side-two.txt"));
cl_assert(!git_path_exists("merge-resolve/test-one.txt~ours"));
cl_assert(!git_path_exists("merge-resolve/test-one.txt~theirs"));
cl_assert(!git_path_exists("merge-resolve/directory_file-one/file"));
cl_assert(!git_path_exists("merge-resolve/directory_file-one~ours"));
cl_assert(!git_path_exists("merge-resolve/directory_file-two~theirs"));
}
void test_checkout_conflict__path_filters(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
char *paths[] = { "conflicting-1.txt", "conflicting-3.txt" };
git_strarray patharray = {0};
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" },
{ 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" },
{ 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" },
{ 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" },
{ 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" },
{ 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" },
{ 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" },
{ 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" },
{ 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" },
};
patharray.count = 2;
patharray.strings = paths;
opts.paths = patharray;
create_index(checkout_index_entries, 12);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir_contents("conflicting-1.txt", CONFLICTING_DIFF3_FILE);
cl_assert(!git_path_exists("merge-resolve/conflicting-2.txt"));
ensure_workdir_contents("conflicting-3.txt", AUTOMERGEABLE_MERGED_FILE);
cl_assert(!git_path_exists("merge-resolve/conflicting-4.txt"));
}
static void collect_progress(
const char *path,
size_t completed_steps,
size_t total_steps,
void *payload)
{
git_vector *paths = payload;
if (path == NULL)
return;
git_vector_insert(paths, strdup(path));
}
void test_checkout_conflict__report_progress(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
git_vector paths = GIT_VECTOR_INIT;
char *path;
size_t i;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" },
{ 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" },
{ 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" },
{ 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" },
{ 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" },
{ 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" },
{ 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" },
{ 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" },
{ 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" },
{ 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" },
{ 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" },
};
opts.progress_cb = collect_progress;
opts.progress_payload = &paths;
create_index(checkout_index_entries, 12);
git_index_write(g_index);
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
cl_assert_equal_i(4, git_vector_length(&paths));
cl_assert_equal_s("conflicting-1.txt", git_vector_get(&paths, 0));
cl_assert_equal_s("conflicting-2.txt", git_vector_get(&paths, 1));
cl_assert_equal_s("conflicting-3.txt", git_vector_get(&paths, 2));
cl_assert_equal_s("conflicting-4.txt", git_vector_get(&paths, 3));
git_vector_foreach(&paths, i, path)
git__free(path);
git_vector_free(&paths);
}
...@@ -696,3 +696,47 @@ void test_checkout_tree__extremely_long_file_name(void) ...@@ -696,3 +696,47 @@ void test_checkout_tree__extremely_long_file_name(void)
cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
cl_assert(!git_path_exists(path)); cl_assert(!git_path_exists(path));
} }
static void create_conflict(void)
{
git_index *index;
git_index_entry entry;
cl_git_pass(git_repository_index(&index, g_repo));
memset(&entry, 0x0, sizeof(git_index_entry));
entry.mode = 0100644;
entry.flags = 1 << GIT_IDXENTRY_STAGESHIFT;
git_oid_fromstr(&entry.oid, "d427e0b2e138501a3d15cc376077a3631e15bd46");
entry.path = "conflicts.txt";
cl_git_pass(git_index_add(index, &entry));
entry.flags = 2 << GIT_IDXENTRY_STAGESHIFT;
git_oid_fromstr(&entry.oid, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
cl_git_pass(git_index_add(index, &entry));
entry.flags = 3 << GIT_IDXENTRY_STAGESHIFT;
git_oid_fromstr(&entry.oid, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
cl_git_pass(git_index_add(index, &entry));
git_index_write(index);
git_index_free(index);
}
void test_checkout_tree__fails_when_conflicts_exist_in_index(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
git_oid oid;
git_object *obj = NULL;
opts.checkout_strategy = GIT_CHECKOUT_SAFE;
cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
create_conflict();
cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
git_object_free(obj);
}
...@@ -80,5 +80,69 @@ void test_index_names__roundtrip(void) ...@@ -80,5 +80,69 @@ void test_index_names__roundtrip(void)
cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0);
cl_assert(conflict_name->ours == NULL); cl_assert(conflict_name->ours == NULL);
cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0); cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0);
}
void test_index_names__cleaned_on_reset_hard(void)
{
git_object *target;
retrieve_target_from_oid(&target, repo, "3a34580a35add43a4cf361e8e9a30060a905c876");
test_index_names__add();
cl_git_pass(git_reset(repo, target, GIT_RESET_HARD));
cl_assert(git_index_name_entrycount(repo_index) == 0);
git_object_free(target);
}
void test_index_names__cleaned_on_reset_mixed(void)
{
git_object *target;
retrieve_target_from_oid(&target, repo, "3a34580a35add43a4cf361e8e9a30060a905c876");
test_index_names__add();
cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED));
cl_assert(git_index_name_entrycount(repo_index) == 0);
git_object_free(target);
}
void test_index_names__cleaned_on_checkout_tree(void)
{
git_oid oid;
git_object *obj;
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
test_index_names__add();
git_reference_name_to_id(&oid, repo, "refs/heads/master");
git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY);
git_checkout_tree(repo, obj, &opts);
cl_assert(git_index_name_entrycount(repo_index) == 0);
git_object_free(obj);
}
void test_index_names__cleaned_on_checkout_head(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
test_index_names__add();
git_checkout_head(repo, &opts);
cl_assert(git_index_name_entrycount(repo_index) == 0);
}
void test_index_names__retained_on_checkout_index(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
test_index_names__add();
git_checkout_index(repo, repo_index, &opts);
cl_assert(git_index_name_entrycount(repo_index) > 0);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment