summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@microsoft.com>2013-07-17 09:49:56 -0500
committerEdward Thomson <ethomson@microsoft.com>2013-10-16 16:20:06 -0400
commit629b661caab321a3aa5ffd7274ea4ed441d2805e (patch)
tree52e5236b88ec970a513caf1a35844e230ccfa91e /src
parent3acf44d6cb5e56fea379504c07ae7ac8dd0e586d (diff)
downloadlibgit2-629b661caab321a3aa5ffd7274ea4ed441d2805e.tar.gz
checkout (from index) can write conflicts
Diffstat (limited to 'src')
-rw-r--r--src/checkout.c91
-rw-r--r--src/checkout.h29
-rw-r--r--src/checkout_conflicts.c593
-rw-r--r--src/merge_file.c2
-rw-r--r--src/reset.c2
5 files changed, 677 insertions, 40 deletions
diff --git a/src/checkout.c b/src/checkout.c
index d3f673d40..fa0609fe3 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -41,24 +41,6 @@ enum {
(CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
};
-typedef struct {
- git_repository *repo;
- git_diff_list *diff;
- git_checkout_opts opts;
- bool opts_free_baseline;
- char *pfx;
- git_index *index;
- git_pool pool;
- git_vector removes;
- git_buf path;
- size_t workdir_len;
- unsigned int strategy;
- int can_symlink;
- bool reload_submodules;
- size_t total_steps;
- size_t completed_steps;
-} checkout_data;
-
static int checkout_notify(
checkout_data *data,
git_checkout_notify_t why,
@@ -707,6 +689,7 @@ static int blob_content_to_file(
struct stat *st,
git_blob *blob,
const char *path,
+ const char * hint_path,
mode_t entry_filemode,
git_checkout_opts *opts)
{
@@ -715,9 +698,12 @@ static int blob_content_to_file(
git_buf out = GIT_BUF_INIT;
git_filter_list *fl = NULL;
+ if (hint_path == NULL)
+ hint_path = path;
+
if (!opts->disable_filters)
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)
error = git_filter_list_apply_to_blob(&out, fl, blob);
@@ -886,34 +872,26 @@ static int checkout_safe_for_update_only(const char *path, mode_t expected_mode)
return 0;
}
-static int checkout_blob(
+int git_checkout__write_content(
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;
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;
- if (S_ISLNK(file->mode))
+ if (S_ISLNK(mode))
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
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);
@@ -928,6 +906,30 @@ static int checkout_blob(
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 = git_checkout__write_content(
+ data, &file->oid, git_buf_cstr(&data->path), NULL, file->mode, &st);
+
/* update the index unless prevented */
if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
error = checkout_update_index(data, file, &st);
@@ -1172,7 +1174,17 @@ static int checkout_data_init(
(error = git_index_read(data->index)) < 0)
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);
}
}
@@ -1312,6 +1324,9 @@ int git_checkout_iterator(
assert(data.completed_steps == data.total_steps);
+ /* Write conflict data to disk */
+ error = git_checkout__conflicts(&data);
+
cleanup:
if (error == GIT_EUSER)
giterr_clear();
diff --git a/src/checkout.h b/src/checkout.h
index 6d7186860..27e682fe1 100644
--- a/src/checkout.h
+++ b/src/checkout.h
@@ -9,9 +9,28 @@
#include "git2/checkout.h"
#include "iterator.h"
+#include "pool.h"
#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ git_checkout_opts opts;
+ bool opts_free_baseline;
+ char *pfx;
+ git_index *index;
+ git_pool pool;
+ git_vector removes;
+ git_buf path;
+ size_t workdir_len;
+ unsigned int strategy;
+ int can_symlink;
+ bool reload_submodules;
+ size_t total_steps;
+ size_t completed_steps;
+} checkout_data;
+
/**
* Update the working directory to match the target iterator. The
* expected baseline value can be passed in via the checkout options
@@ -21,4 +40,14 @@ extern int git_checkout_iterator(
git_iterator *target,
const git_checkout_opts *opts);
+int git_checkout__write_content(
+ checkout_data *data,
+ const git_oid *oid,
+ const char *path,
+ const char *hint_path,
+ unsigned int mode,
+ struct stat *st);
+
+int git_checkout__conflicts(checkout_data *data);
+
#endif
diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c
new file mode 100644
index 000000000..0c9c768df
--- /dev/null
+++ b/src/checkout_conflicts.c
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "checkout.h"
+
+#include "vector.h"
+#include "index.h"
+#include "merge_file.h"
+#include "git2/repository.h"
+#include "git2/types.h"
+#include "git2/index.h"
+#include "git2/sys/index.h"
+
+typedef struct {
+ const git_index_entry *ancestor;
+ const git_index_entry *ours;
+ const git_index_entry *theirs;
+
+ int name_collision:1,
+ directoryfile:1;
+} checkout_conflictdata;
+
+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)
+{
+ const checkout_conflictdata *conflict;
+
+ if ((conflict = git_vector_get(conflicts, idx)) == NULL)
+ return -1;
+
+ return (conflict->ancestor == NULL &&
+ conflict->ours == NULL &&
+ conflict->theirs == NULL);
+}
+
+static int checkout_conflicts_load(checkout_data *data, git_vector *conflicts)
+{
+ 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;
+
+ conflicts->_cmp = checkout_conflictdata_cmp;
+
+ /* Collect the conflicts */
+ while ((error = git_index_conflict_next(
+ &ancestor, &ours, &theirs, iterator)) == 0) {
+
+ conflict = git__calloc(1, sizeof(checkout_conflictdata));
+ GITERR_CHECK_ALLOC(conflict);
+
+ conflict->ancestor = ancestor;
+ conflict->ours = ours;
+ conflict->theirs = theirs;
+
+ git_vector_insert(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)
+{
+ /* TODO: is strcmp right here? should we use index->strcomp ? */
+ 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(
+ git_vector *conflicts,
+ const char *path)
+{
+ size_t pos;
+
+ if (git_vector_bsearch2(&pos, conflicts, checkout_conflicts_cmp_ancestor, path) < 0)
+ return NULL;
+
+ return git_vector_get(conflicts, pos);
+}
+
+static checkout_conflictdata *checkout_conflicts_search_branch(
+ git_vector *conflicts,
+ const char *path)
+{
+ checkout_conflictdata *conflict;
+ size_t i;
+
+ git_vector_foreach(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,
+ git_vector *conflicts,
+ 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(conflicts,
+ 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(conflicts, 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 ((theirs = checkout_conflicts_search_branch(conflicts, 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,
+ git_vector *conflicts)
+{
+ 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 */
+ for (i = 0, names = git_index_name_entrycount(data->index);
+ 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,
+ conflicts, 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;
+ }
+ }
+
+ git_vector_remove_matching(conflicts, checkout_conflictdata_empty);
+
+done:
+ return error;
+}
+
+/* TODO: does this exist elsewhere? */
+GIT_INLINE(void) path_equal_or_prefixed(
+ bool *path_eq,
+ bool *path_prefixed,
+ const char *parent,
+ const char *child)
+{
+ size_t child_len = strlen(child);
+ size_t parent_len = strlen(parent);
+
+ if (child_len == parent_len) {
+ *path_eq = (strcmp(parent, child) == 0);
+ *path_prefixed = 0;
+ return;
+ }
+
+ *path_eq = 0;
+
+ if (child_len < parent_len ||
+ strncmp(parent, child, parent_len) != 0)
+ *path_prefixed = 0;
+ else
+ *path_prefixed = (child[parent_len] == '/');
+}
+
+static int checkout_conflicts_mark_directoryfile(
+ checkout_data *data,
+ git_vector *conflicts)
+{
+ checkout_conflictdata *conflict;
+ const git_index_entry *entry;
+ size_t i, j, len;
+ const char *path;
+ bool eq, prefixed;
+ int error = 0;
+
+ len = git_index_entrycount(data->index);
+
+ /* Find d/f conflicts */
+ git_vector_foreach(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_MERGE,
+ "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_MERGE,
+ "Index inconsistency, truncated index while loading expected conflict '%s'", path);
+ error = -1;
+ goto done;
+ }
+
+ path_equal_or_prefixed(&eq, &prefixed, path, entry->path);
+
+ if (eq)
+ continue;
+
+ if (prefixed)
+ conflict->directoryfile = 1;
+
+ break;
+ }
+ }
+
+done:
+ 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 conflict_path_suffixed(
+ git_buf *out,
+ const char *path,
+ const char *side_name)
+{
+ if (git_buf_puts(out, path) < 0 ||
+ git_buf_putc(out, '~') < 0 ||
+ git_buf_puts(out, side_name) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int checkout_write_entry(
+ checkout_data *data,
+ checkout_conflictdata *conflict,
+ const git_index_entry *side)
+{
+ const char *hint_path = NULL, *side_label;
+ struct stat st;
+
+ 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) {
+ if (side == conflict->ours)
+ side_label = data->opts.our_label ? data->opts.our_label :
+ "ours";
+ else if (side == conflict->theirs)
+ side_label = data->opts.their_label ? data->opts.their_label :
+ "theirs";
+
+ if (git_buf_putc(&data->path, '~') < 0 ||
+ git_buf_puts(&data->path, side_label) < 0)
+ return -1;
+
+ hint_path = side->path;
+ }
+
+ return git_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_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;
+ const char *our_label_raw, *their_label_raw, *path;
+ 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 = our_label_raw = data->opts.our_label ? data->opts.our_label : "ours";
+ theirs.label = their_label_raw = 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;
+ }
+
+ /* Rename 2->1 conflicts need the branch name appended */
+ if (conflict->name_collision) {
+ /* TODO: strcmp? */
+ if ((error = conflict_path_suffixed(&path_suffixed, result.path,
+ (strcmp(result.path, conflict->ours->path) == 0 ?
+ our_label_raw : their_label_raw))) < 0)
+ goto done;
+
+ path = git_buf_cstr(&path_suffixed);
+ } else
+ path = result.path;
+
+ if ((error = git_buf_joinpath(&path_workdir, git_repository_workdir(data->repo), path)) < 0 ||
+ (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;
+}
+
+GIT_INLINE(bool) conflict_is_1_to_2(checkout_conflictdata *conflict)
+{
+ /* TODO: can't we detect these when we coalesce? */
+ return conflict->ancestor && conflict->ours && conflict->theirs &&
+ (strcmp(conflict->ancestor->path, conflict->ours->path) != 0 &&
+ strcmp(conflict->ancestor->path, conflict->theirs->path) != 0 &&
+ strcmp(conflict->ours->path, conflict->theirs->path) != 0);
+}
+
+int git_checkout__conflicts(checkout_data *data)
+{
+ git_vector conflicts = GIT_VECTOR_INIT;
+ checkout_conflictdata *conflict;
+ size_t i;
+ int error = 0;
+
+ if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED)
+ return 0;
+
+ if ((error = checkout_conflicts_load(data, &conflicts)) < 0 ||
+ (error = checkout_conflicts_coalesce_renames(data, &conflicts)) < 0 ||
+ (error = checkout_conflicts_mark_directoryfile(data, &conflicts)) < 0)
+ goto done;
+
+ git_vector_foreach(&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_is_1_to_2(conflict))
+ 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);
+ }
+
+done:
+ git_vector_foreach(&conflicts, i, conflict)
+ git__free(conflict);
+
+ git_vector_free(&conflicts);
+
+ return error;
+}
diff --git a/src/merge_file.c b/src/merge_file.c
index c3477ccb9..48fc46e57 100644
--- a/src/merge_file.c
+++ b/src/merge_file.c
@@ -47,7 +47,7 @@ GIT_INLINE(int) merge_file_best_mode(
* assume executable. Otherwise, if any mode changed from the ancestor,
* use that one.
*/
- if (GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
+ if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE ||
theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE)
return GIT_FILEMODE_BLOB_EXECUTABLE;
diff --git a/src/reset.c b/src/reset.c
index cea212a93..87dc45a3f 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -135,7 +135,7 @@ int git_reset(
if (reset_type == GIT_RESET_HARD) {
/* 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)
goto cleanup;