From 3acf44d6cb5e56fea379504c07ae7ac8dd0e586d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 17 Jul 2013 09:43:32 -0500 Subject: [PATCH 01/11] tests for checkout index with conflicts --- tests-clar/checkout/conflict.c | 460 ++++++++++++++++++ .../1a/010b1c0f081b2e8901d55307a15c29ff30af0e | Bin 0 -> 19 bytes .../72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2 | Bin 0 -> 26 bytes .../8b/fb012a6d809e499bd8d3e194a3929bc8995b93 | Bin 0 -> 34 bytes 4 files changed, 460 insertions(+) create mode 100644 tests-clar/checkout/conflict.c create mode 100644 tests-clar/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e create mode 100644 tests-clar/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2 create mode 100644 tests-clar/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 diff --git a/tests-clar/checkout/conflict.c b/tests-clar/checkout/conflict.c new file mode 100644 index 000000000..836495fef --- /dev/null +++ b/tests-clar/checkout/conflict.c @@ -0,0 +1,460 @@ +#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]; +}; + +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); + 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; + + git_index_add(g_index, &entry); + } + + git_buf_free(&path); +} + +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_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(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); +} diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e b/tests-clar/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e new file mode 100644 index 0000000000000000000000000000000000000000..6039df00ebea6884c4b22809ca2772b3a01695af GIT binary patch literal 19 acmb<|&`U#o< literal 0 HcmV?d00001 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 b/tests-clar/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 new file mode 100644 index 0000000000000000000000000000000000000000..a90ee08ce132010f28ed711bdad9338958303f0f GIT binary patch literal 34 qcmb@98t1tPJHUoHhU!%MSMd literal 0 HcmV?d00001 From 629b661caab321a3aa5ffd7274ea4ed441d2805e Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 17 Jul 2013 09:49:56 -0500 Subject: [PATCH 02/11] checkout (from index) can write conflicts --- include/git2/checkout.h | 17 +- src/checkout.c | 91 +++--- src/checkout.h | 29 ++ src/checkout_conflicts.c | 593 +++++++++++++++++++++++++++++++++++++++ src/merge_file.c | 2 +- src/reset.c | 2 +- 6 files changed, 687 insertions(+), 47 deletions(-) create mode 100644 src/checkout_conflicts.c diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 844f0a9e2..3793a4f18 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -131,6 +131,13 @@ typedef enum { /** Don't refresh index/config/etc before doing checkout */ 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 */ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13), @@ -141,13 +148,6 @@ typedef enum { * 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) */ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16), /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */ @@ -238,6 +238,9 @@ typedef struct git_checkout_opts { git_tree *baseline; /** expected content of workdir, defaults to HEAD */ 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; #define GIT_CHECKOUT_OPTS_VERSION 1 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 + +#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; From 4f7897ab19daf6593b35c7f5b4f35403d00530ef Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 22 Jul 2013 08:51:29 -0700 Subject: [PATCH 03/11] Prevent checkout_tree when conflicts exist, clear NAME on checkout tree Prevent checkout tree when unresolved changes exist (unless FORCE flag is specified). Clear NAME table when checking out, to avoid checkout_conflicts from attempting to manipulate it. Ensure that NAME is also cleared at reset. --- tests-clar/checkout/tree.c | 44 +++++++++++++++++++++++++ tests-clar/index/names.c | 66 +++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index fff530cdd..66b01bc7f 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -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_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); +} diff --git a/tests-clar/index/names.c b/tests-clar/index/names.c index 95a560ee4..87453ecbf 100644 --- a/tests-clar/index/names.c +++ b/tests-clar/index/names.c @@ -80,5 +80,69 @@ void test_index_names__roundtrip(void) cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); cl_assert(conflict_name->ours == NULL); 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); } From 96d799aa1830f5fcbacdfd87eca728727561d8e6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 23 Jul 2013 15:32:42 -0700 Subject: [PATCH 04/11] checkout rename conflicts rename conflict tests for checkout conflicts, don't suffix filenames when checking out with USE_OURS or USE_THEIRS --- src/checkout_conflicts.c | 5 +- tests-clar/checkout/conflict.c | 378 ++++++++++++++++++++++++++++++++- 2 files changed, 381 insertions(+), 2 deletions(-) diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index 0c9c768df..45e43a324 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -395,7 +395,10 @@ static int checkout_write_entry( if (git_buf_puts(&data->path, side->path) < 0) return -1; - if (conflict->name_collision || conflict->directoryfile) { + if ((conflict->name_collision || conflict->directoryfile) && + (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && + (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { + if (side == conflict->ours) side_label = data->opts.our_label ? data->opts.our_label : "ours"; diff --git a/tests-clar/checkout/conflict.c b/tests-clar/checkout/conflict.c index 836495fef..da4a20fd8 100644 --- a/tests-clar/checkout/conflict.c +++ b/tests-clar/checkout/conflict.c @@ -53,6 +53,12 @@ struct checkout_index_entry { 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); @@ -91,12 +97,24 @@ static void create_index(struct checkout_index_entry *entries, size_t entries_le git_oid_fromstr(&entry.oid, entries[i].oid_str); entry.path = entries[i].path; - git_index_add(g_index, &entry); + 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[] = { @@ -148,6 +166,12 @@ static void ensure_workdir_mode(const char *path, int mode) #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 @@ -458,3 +482,355 @@ void test_checkout_conflict__mode_change(void) 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"); +} From fc36800ecd8f8903f4cf7e40a54966855fdd4b76 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 8 Aug 2013 13:16:13 -0500 Subject: [PATCH 05/11] Get rid of some quick hacks --- src/checkout_conflicts.c | 48 ++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index 45e43a324..54e040380 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -23,7 +23,8 @@ typedef struct { const git_index_entry *theirs; int name_collision:1, - directoryfile:1; + directoryfile:1, + one_to_two:1; } checkout_conflictdata; GIT_INLINE(int) checkout_idxentry_cmp( @@ -104,7 +105,6 @@ 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); } @@ -207,6 +207,8 @@ static int checkout_conflicts_load_byname_entry( 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(conflicts, name_entry->theirs)) == NULL || theirs->theirs == NULL) { giterr_set(GITERR_INDEX, @@ -267,6 +269,10 @@ static int checkout_conflicts_coalesce_renames( 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(conflicts, checkout_conflictdata_empty); @@ -275,29 +281,27 @@ 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); + const char *p, *c; - if (child_len == parent_len) { - *path_eq = (strcmp(parent, child) == 0); - *path_prefixed = 0; - return; - } - *path_eq = 0; + *path_prefixed = 0; - if (child_len < parent_len || - strncmp(parent, child, parent_len) != 0) - *path_prefixed = 0; - else - *path_prefixed = (child[parent_len] == '/'); + for (p = parent, c = child; *p && *c; p++, c++) { + if (*p != *c) + return; + } + + if (!*p) + *path_prefixed = (*c == '/'); + + if (!*p && !*c) + *path_eq = 1; } static int checkout_conflicts_mark_directoryfile( @@ -483,7 +487,6 @@ static int checkout_write_merge( /* 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) @@ -514,15 +517,6 @@ done: 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; @@ -569,7 +563,7 @@ int git_checkout__conflicts(checkout_data *data) /* Add/add conflicts and rename 1->2 conflicts, write the * ours/theirs sides (potentially name mangled). */ - else if (conflict_is_1_to_2(conflict)) + else if (conflict->one_to_two) error = checkout_write_entries(data, conflict); /* If all sides are links, write the ours side */ From e47f859db9a52f38d9ca94d725a178ac4ba93836 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 8 Aug 2013 16:46:49 -0500 Subject: [PATCH 06/11] Don't overwrite ~ files checking out conflicts If a D/F conflict or rename 2->1 conflict occurs, we write the file sides as filename~branchname. If a file with that name already exists in the working directory, write as filename~branchname_0 instead. (Incrementing 0 until a unique filename is found.) --- src/checkout_conflicts.c | 91 ++++++++++++++------- tests-clar/checkout/conflict.c | 140 +++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 30 deletions(-) diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index 54e040380..dcf27145d 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -328,7 +328,7 @@ static int checkout_conflicts_mark_directoryfile( if ((error = git_index_find(&j, data->index, path)) < 0) { if (error == GIT_ENOTFOUND) - giterr_set(GITERR_MERGE, + giterr_set(GITERR_INDEX, "Index inconsistency, could not find entry for expected conflict '%s'", path); goto done; @@ -336,7 +336,7 @@ static int checkout_conflicts_mark_directoryfile( for (; j < len; j++) { if ((entry = git_index_get_byindex(data->index, j)) == NULL) { - giterr_set(GITERR_MERGE, + giterr_set(GITERR_INDEX, "Index inconsistency, truncated index while loading expected conflict '%s'", path); error = -1; goto done; @@ -371,16 +371,33 @@ static int conflict_entry_name( return 0; } -static int conflict_path_suffixed( - git_buf *out, - const char *path, - const char *side_name) +static int checkout_path_suffixed(git_buf *path, const char *suffix) { - if (git_buf_puts(out, path) < 0 || - git_buf_putc(out, '~') < 0 || - git_buf_puts(out, side_name) < 0) + 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; } @@ -389,7 +406,7 @@ static int checkout_write_entry( checkout_conflictdata *conflict, const git_index_entry *side) { - const char *hint_path = NULL, *side_label; + const char *hint_path = NULL, *suffix; struct stat st; assert (side == conflict->ours || @@ -404,14 +421,13 @@ static int checkout_write_entry( (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { if (side == conflict->ours) - side_label = data->opts.our_label ? data->opts.our_label : + suffix = data->opts.our_label ? data->opts.our_label : "ours"; else if (side == conflict->theirs) - side_label = data->opts.their_label ? data->opts.their_label : + suffix = data->opts.their_label ? data->opts.their_label : "theirs"; - if (git_buf_putc(&data->path, '~') < 0 || - git_buf_puts(&data->path, side_label) < 0) + if (checkout_path_suffixed(&data->path, suffix) < 0) return -1; hint_path = side->path; @@ -433,6 +449,33 @@ static int checkout_write_entries( 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 i = 0, 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) @@ -444,7 +487,6 @@ static int checkout_write_merge( 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 && @@ -457,8 +499,8 @@ static int checkout_write_merge( 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"; + 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. @@ -485,19 +527,8 @@ static int checkout_write_merge( goto done; } - /* Rename 2->1 conflicts need the branch name appended */ - if (conflict->name_collision) { - 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) || + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 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) diff --git a/tests-clar/checkout/conflict.c b/tests-clar/checkout/conflict.c index da4a20fd8..8b6dd4cd8 100644 --- a/tests-clar/checkout/conflict.c +++ b/tests-clar/checkout/conflict.c @@ -834,3 +834,143 @@ void test_checkout_conflict__rename_keep_ours(void) 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); +} From 6f8cc7bb6a77e7b03751051be86a0c6b36bdd86f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 5 Aug 2013 20:06:09 -0500 Subject: [PATCH 07/11] Fix warning, fix memory leak --- src/checkout_conflicts.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index dcf27145d..eb1ac6e14 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -56,14 +56,16 @@ static int checkout_conflictdata_cmp(const void *a, const void *b) int checkout_conflictdata_empty(const git_vector *conflicts, size_t idx) { - const checkout_conflictdata *conflict; + checkout_conflictdata *conflict; if ((conflict = git_vector_get(conflicts, idx)) == NULL) return -1; - return (conflict->ancestor == NULL && - conflict->ours == NULL && - conflict->theirs == NULL); + if (conflict->ancestor || conflict->ours || conflict->theirs) + return 0; + + git__free(conflict); + return 1; } static int checkout_conflicts_load(checkout_data *data, git_vector *conflicts) @@ -409,8 +411,7 @@ static int checkout_write_entry( const char *hint_path = NULL, *suffix; struct stat st; - assert (side == conflict->ours || - side == conflict->theirs); + assert (side == conflict->ours || side == conflict->theirs); git_buf_truncate(&data->path, data->workdir_len); if (git_buf_puts(&data->path, side->path) < 0) @@ -423,7 +424,7 @@ static int checkout_write_entry( if (side == conflict->ours) suffix = data->opts.our_label ? data->opts.our_label : "ours"; - else if (side == conflict->theirs) + else suffix = data->opts.their_label ? data->opts.their_label : "theirs"; @@ -456,7 +457,7 @@ static int checkout_merge_path( git_merge_file_result *result) { const char *our_label_raw, *their_label_raw, *suffix; - int i = 0, error = 0; + int error = 0; if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0) return error; From cfae7f85fbbc5b0e023e94f78486bf72cc1436f5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 9 Aug 2013 20:23:36 -0500 Subject: [PATCH 08/11] Honor UPDATE_ONLY bit when checking out conflicts --- src/checkout.c | 4 +-- src/checkout.h | 4 +++ src/checkout_conflicts.c | 15 ++++++++-- tests-clar/checkout/conflict.c | 50 +++++++++++++++++++++++++++++++++- 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index fa0609fe3..7c90ef81d 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -851,7 +851,7 @@ static void report_progress( data->opts.progress_payload); } -static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) +int git_checkout__safe_for_update_only(const char *path, mode_t expected_mode) { struct stat st; @@ -921,7 +921,7 @@ static int checkout_blob( return -1; if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = checkout_safe_for_update_only( + int rval = git_checkout__safe_for_update_only( git_buf_cstr(&data->path), file->mode); if (rval <= 0) return rval; diff --git a/src/checkout.h b/src/checkout.h index 27e682fe1..9a8098998 100644 --- a/src/checkout.h +++ b/src/checkout.h @@ -40,6 +40,10 @@ extern int git_checkout_iterator( git_iterator *target, const git_checkout_opts *opts); +int git_checkout__safe_for_update_only( + const char *path, + mode_t expected_mode); + int git_checkout__write_content( checkout_data *data, const git_oid *oid, diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index eb1ac6e14..aa9064752 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -410,6 +410,7 @@ static int checkout_write_entry( { const char *hint_path = NULL, *suffix; struct stat st; + int error; assert (side == conflict->ours || side == conflict->theirs); @@ -434,6 +435,10 @@ static int checkout_write_entry( hint_path = side->path; } + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = git_checkout__safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) + return error; + return git_checkout__write_content(data, &side->oid, git_buf_cstr(&data->path), hint_path, side->mode, &st); } @@ -528,8 +533,14 @@ static int checkout_write_merge( goto done; } - if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0 || - (error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) + goto done; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = git_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) diff --git a/tests-clar/checkout/conflict.c b/tests-clar/checkout/conflict.c index 8b6dd4cd8..0b37ec8f5 100644 --- a/tests-clar/checkout/conflict.c +++ b/tests-clar/checkout/conflict.c @@ -82,7 +82,9 @@ static void create_index(struct checkout_index_entry *entries, size_t entries_le for (i = 0; i < entries_len; i++) { git_buf_joinpath(&path, TEST_REPO_PATH, entries[i].path); - p_unlink(git_buf_cstr(&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); } @@ -203,6 +205,7 @@ void test_checkout_conflict__ignored(void) 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)); @@ -974,3 +977,48 @@ void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) 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")); +} From 216f97e4f68c99c21d57a9196702df1f8f4366d4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 23 Sep 2013 09:47:47 -0400 Subject: [PATCH 09/11] Two-step conflict checkout (load / perform) Move conflict handling into two steps: load the conflicts and then apply the conflicts. This is more compatible with the existing checkout implementation and makes progress reporting more sane. --- src/checkout.c | 54 ++++++++------ src/checkout.h | 29 ++++++++ src/checkout_conflicts.c | 126 ++++++++++++++++++++------------- tests-clar/checkout/conflict.c | 100 ++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 73 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 7c90ef81d..323863ce2 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -29,18 +29,6 @@ /* See docs/checkout-internals.md for more information */ -enum { - CHECKOUT_ACTION__NONE = 0, - CHECKOUT_ACTION__REMOVE = 1, - CHECKOUT_ACTION__UPDATE_BLOB = 2, - CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, - CHECKOUT_ACTION__CONFLICT = 8, - CHECKOUT_ACTION__MAX = 8, - CHECKOUT_ACTION__DEFER_REMOVE = 16, - CHECKOUT_ACTION__REMOVE_AND_UPDATE = - (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), -}; - static int checkout_notify( checkout_data *data, git_checkout_notify_t why, @@ -641,6 +629,14 @@ static int checkout_get_actions( goto fail; } + + if ((error = git_checkout__get_conflicts(data, workdir, &pathspec)) < 0) + goto fail; + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts); + + /* HERE */ + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); @@ -841,7 +837,7 @@ static int checkout_submodule( return checkout_submodule_update_index(data, file); } -static void report_progress( +void git_checkout__report_progress( checkout_data *data, const char *path) { @@ -965,7 +961,7 @@ static int checkout_remove_the_old( return error; data->completed_steps++; - report_progress(data, delta->old_file.path); + git_checkout__report_progress(data, delta->old_file.path); if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && @@ -982,7 +978,7 @@ static int checkout_remove_the_old( return error; data->completed_steps++; - report_progress(data, str); + git_checkout__report_progress(data, str); if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && data->index != NULL) @@ -1041,7 +1037,7 @@ static int checkout_create_the_new( return error; data->completed_steps++; - report_progress(data, delta->new_file.path); + git_checkout__report_progress(data, delta->new_file.path); } } @@ -1077,7 +1073,7 @@ static int checkout_create_submodules( return error; data->completed_steps++; - report_progress(data, delta->new_file.path); + git_checkout__report_progress(data, delta->new_file.path); } } @@ -1102,6 +1098,9 @@ static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) static void checkout_data_clear(checkout_data *data) { + checkout_conflictdata *conflict; + size_t i; + if (data->opts_free_baseline) { git_tree_free(data->opts.baseline); data->opts.baseline = NULL; @@ -1110,6 +1109,11 @@ static void checkout_data_clear(checkout_data *data) git_vector_free(&data->removes); git_pool_clear(&data->pool); + git_vector_foreach(&data->conflicts, i, conflict) + git__free(conflict); + + git_vector_free(&data->conflicts); + git__free(data->pfx); data->pfx = NULL; @@ -1226,6 +1230,7 @@ static int checkout_data_init( } 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_buf_puts(&data->path, data->opts.target_directory)) < 0 || (error = git_path_to_dir(&data->path)) < 0) @@ -1296,16 +1301,18 @@ int git_checkout_iterator( goto cleanup; /* 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) goto cleanup; data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + 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 */ + git_checkout__report_progress(&data, NULL); /* establish 0 baseline */ /* To deal with some order dependencies, perform remaining checkout * in three passes: removes, then update blobs, then update submodules. @@ -1322,10 +1329,11 @@ int git_checkout_iterator( (error = checkout_create_submodules(actions, &data)) < 0) goto cleanup; - assert(data.completed_steps == data.total_steps); + if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && + (error = git_checkout__conflicts(&data)) < 0) + goto cleanup; - /* Write conflict data to disk */ - error = git_checkout__conflicts(&data); + assert(data.completed_steps == data.total_steps); cleanup: if (error == GIT_EUSER) diff --git a/src/checkout.h b/src/checkout.h index 9a8098998..d48e263e4 100644 --- a/src/checkout.h +++ b/src/checkout.h @@ -22,6 +22,7 @@ typedef struct { git_index *index; git_pool pool; git_vector removes; + git_vector conflicts; git_buf path; size_t workdir_len; unsigned int strategy; @@ -31,6 +32,29 @@ typedef struct { size_t completed_steps; } 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; + +enum { + CHECKOUT_ACTION__NONE = 0, + CHECKOUT_ACTION__REMOVE = 1, + CHECKOUT_ACTION__UPDATE_BLOB = 2, + CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, + CHECKOUT_ACTION__CONFLICT = 8, + CHECKOUT_ACTION__UPDATE_CONFLICT = 16, + CHECKOUT_ACTION__MAX = 16, + CHECKOUT_ACTION__DEFER_REMOVE = 32, + CHECKOUT_ACTION__REMOVE_AND_UPDATE = + (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), +}; + /** * Update the working directory to match the target iterator. The * expected baseline value can be passed in via the checkout options @@ -52,6 +76,11 @@ int git_checkout__write_content( unsigned int mode, struct stat *st); +void git_checkout__report_progress( + checkout_data *data, + const char *path); + +int git_checkout__get_conflicts(checkout_data *data, git_iterator *workdir, git_vector *pathspec); int git_checkout__conflicts(checkout_data *data); #endif diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index aa9064752..c39cb764e 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -11,22 +11,13 @@ #include "vector.h" #include "index.h" +#include "pathspec.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, - one_to_two:1; -} checkout_conflictdata; - GIT_INLINE(int) checkout_idxentry_cmp( const git_index_entry *a, const git_index_entry *b) @@ -68,7 +59,34 @@ int checkout_conflictdata_empty(const git_vector *conflicts, size_t idx) return 1; } -static int checkout_conflicts_load(checkout_data *data, git_vector *conflicts) +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; @@ -78,11 +96,12 @@ static int checkout_conflicts_load(checkout_data *data, git_vector *conflicts) if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0) goto done; - conflicts->_cmp = checkout_conflictdata_cmp; + data->conflicts._cmp = checkout_conflictdata_cmp; /* Collect the conflicts */ - while ((error = git_index_conflict_next( - &ancestor, &ours, &theirs, iterator)) == 0) { + 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); @@ -91,7 +110,7 @@ static int checkout_conflicts_load(checkout_data *data, git_vector *conflicts) conflict->ours = ours; conflict->theirs = theirs; - git_vector_insert(conflicts, conflict); + git_vector_insert(&data->conflicts, conflict); } if (error == GIT_ITEROVER) @@ -122,25 +141,25 @@ static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) } static checkout_conflictdata *checkout_conflicts_search_ancestor( - git_vector *conflicts, + checkout_data *data, const char *path) { size_t pos; - if (git_vector_bsearch2(&pos, conflicts, checkout_conflicts_cmp_ancestor, path) < 0) + if (git_vector_bsearch2(&pos, &data->conflicts, checkout_conflicts_cmp_ancestor, path) < 0) return NULL; - return git_vector_get(conflicts, pos); + return git_vector_get(&data->conflicts, pos); } static checkout_conflictdata *checkout_conflicts_search_branch( - git_vector *conflicts, + checkout_data *data, const char *path) { checkout_conflictdata *conflict; size_t i; - git_vector_foreach(conflicts, i, conflict) { + git_vector_foreach(&data->conflicts, i, conflict) { int cmp = -1; if (conflict->ancestor) @@ -162,7 +181,7 @@ static int checkout_conflicts_load_byname_entry( checkout_conflictdata **ancestor_out, checkout_conflictdata **ours_out, checkout_conflictdata **theirs_out, - git_vector *conflicts, + checkout_data *data, const git_index_name_entry *name_entry) { checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; @@ -184,7 +203,7 @@ static int checkout_conflicts_load_byname_entry( goto done; } - if ((ancestor = checkout_conflicts_search_ancestor(conflicts, + 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", @@ -196,7 +215,7 @@ static int checkout_conflicts_load_byname_entry( 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 || + 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", @@ -211,7 +230,7 @@ static int checkout_conflicts_load_byname_entry( theirs = ancestor; else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) theirs = ours; - else if ((theirs = checkout_conflicts_search_branch(conflicts, name_entry->theirs)) == NULL || + 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", @@ -230,8 +249,7 @@ done: } static int checkout_conflicts_coalesce_renames( - checkout_data *data, - git_vector *conflicts) + checkout_data *data) { const git_index_name_entry *name_entry; checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; @@ -239,15 +257,14 @@ static int checkout_conflicts_coalesce_renames( int error = 0; /* Juggle entries based on renames */ - for (i = 0, names = git_index_name_entrycount(data->index); - i < names; - i++) { - + 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, - conflicts, name_entry)) < 0) + data, name_entry)) < 0) goto done; if (our_conflict && our_conflict != ancestor_conflict) { @@ -277,7 +294,7 @@ static int checkout_conflicts_coalesce_renames( ancestor_conflict->one_to_two = 1; } - git_vector_remove_matching(conflicts, checkout_conflictdata_empty); + git_vector_remove_matching(&data->conflicts, checkout_conflictdata_empty); done: return error; @@ -307,8 +324,7 @@ GIT_INLINE(void) path_equal_or_prefixed( } static int checkout_conflicts_mark_directoryfile( - checkout_data *data, - git_vector *conflicts) + checkout_data *data) { checkout_conflictdata *conflict; const git_index_entry *entry; @@ -320,7 +336,7 @@ static int checkout_conflicts_mark_directoryfile( len = git_index_entrycount(data->index); /* Find d/f conflicts */ - git_vector_foreach(conflicts, i, conflict) { + git_vector_foreach(&data->conflicts, i, conflict) { if ((conflict->ours && conflict->theirs) || (!conflict->ours && !conflict->theirs)) continue; @@ -560,6 +576,22 @@ done: return error; } +int git_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; +} + int git_checkout__conflicts(checkout_data *data) { git_vector conflicts = GIT_VECTOR_INIT; @@ -567,15 +599,7 @@ int git_checkout__conflicts(checkout_data *data) 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) { + git_vector_foreach(&data->conflicts, i, conflict) { /* Both deleted: nothing to do */ if (conflict->ours == NULL && conflict->theirs == NULL) error = 0; @@ -621,13 +645,15 @@ int git_checkout__conflicts(checkout_data *data) else error = checkout_write_merge(data, conflict); + + if (error) + break; + + data->completed_steps++; + git_checkout__report_progress(data, + conflict->ours ? conflict->ours->path : + (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); } -done: - git_vector_foreach(&conflicts, i, conflict) - git__free(conflict); - - git_vector_free(&conflicts); - return error; } diff --git a/tests-clar/checkout/conflict.c b/tests-clar/checkout/conflict.c index 0b37ec8f5..d261f3860 100644 --- a/tests-clar/checkout/conflict.c +++ b/tests-clar/checkout/conflict.c @@ -1022,3 +1022,103 @@ void test_checkout_conflict__update_only(void) 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); +} From 7fa73de16378ecfa484747d1b61656282923a88a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 23 Sep 2013 17:52:40 -0400 Subject: [PATCH 10/11] Move functions in checkout_conflicts to checkout.c It seemed exceptionally silly to have a split there where no split needed to be. --- src/checkout.c | 704 ++++++++++++++++++++++++++++++++++++++- src/checkout.h | 62 ---- src/checkout_conflicts.c | 659 ------------------------------------ 3 files changed, 696 insertions(+), 729 deletions(-) delete mode 100644 src/checkout_conflicts.c diff --git a/src/checkout.c b/src/checkout.c index 323863ce2..c226f4342 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -26,9 +26,52 @@ #include "diff.h" #include "pathspec.h" #include "buf_text.h" +#include "merge_file.h" /* See docs/checkout-internals.md for more information */ +enum { + CHECKOUT_ACTION__NONE = 0, + CHECKOUT_ACTION__REMOVE = 1, + CHECKOUT_ACTION__UPDATE_BLOB = 2, + CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, + CHECKOUT_ACTION__CONFLICT = 8, + CHECKOUT_ACTION__UPDATE_CONFLICT = 16, + CHECKOUT_ACTION__MAX = 16, + CHECKOUT_ACTION__DEFER_REMOVE = 32, + CHECKOUT_ACTION__REMOVE_AND_UPDATE = + (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_vector conflicts; + 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; + +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( checkout_data *data, git_checkout_notify_t why, @@ -562,6 +605,383 @@ static int checkout_remaining_wd_items( 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; +} + +GIT_INLINE(void) path_equal_or_prefixed( + bool *path_eq, + bool *path_prefixed, + const char *parent, + const char *child) +{ + const char *p, *c; + + *path_eq = 0; + *path_prefixed = 0; + + for (p = parent, c = child; *p && *c; p++, c++) { + if (*p != *c) + return; + } + + if (!*p) + *path_prefixed = (*c == '/'); + + if (!*p && !*c) + *path_eq = 1; +} + +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; + bool eq, prefixed; + int 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; + } + + path_equal_or_prefixed(&eq, &prefixed, path, entry->path); + + if (eq) + continue; + + if (prefixed) + 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( uint32_t **actions_ptr, size_t **counts_ptr, @@ -630,7 +1050,7 @@ static int checkout_get_actions( } - if ((error = git_checkout__get_conflicts(data, workdir, &pathspec)) < 0) + if ((error = checkout_get_conflicts(data, workdir, &pathspec)) < 0) goto fail; counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts); @@ -837,7 +1257,7 @@ static int checkout_submodule( return checkout_submodule_update_index(data, file); } -void git_checkout__report_progress( +void report_progress( checkout_data *data, const char *path) { @@ -961,7 +1381,7 @@ static int checkout_remove_the_old( return error; data->completed_steps++; - git_checkout__report_progress(data, delta->old_file.path); + report_progress(data, delta->old_file.path); if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && @@ -978,7 +1398,7 @@ static int checkout_remove_the_old( return error; data->completed_steps++; - git_checkout__report_progress(data, str); + report_progress(data, str); if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && data->index != NULL) @@ -1037,7 +1457,7 @@ static int checkout_create_the_new( return error; data->completed_steps++; - git_checkout__report_progress(data, delta->new_file.path); + report_progress(data, delta->new_file.path); } } @@ -1073,7 +1493,7 @@ static int checkout_create_submodules( return error; data->completed_steps++; - git_checkout__report_progress(data, delta->new_file.path); + report_progress(data, delta->new_file.path); } } @@ -1096,6 +1516,274 @@ static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) 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 = git_checkout__safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) + return error; + + 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_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 = git_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) { checkout_conflictdata *conflict; @@ -1312,7 +2000,7 @@ int git_checkout_iterator( counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; - git_checkout__report_progress(&data, NULL); /* establish 0 baseline */ + report_progress(&data, NULL); /* establish 0 baseline */ /* To deal with some order dependencies, perform remaining checkout * in three passes: removes, then update blobs, then update submodules. @@ -1330,7 +2018,7 @@ int git_checkout_iterator( goto cleanup; if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && - (error = git_checkout__conflicts(&data)) < 0) + (error = checkout_create_conflicts(&data)) < 0) goto cleanup; assert(data.completed_steps == data.total_steps); diff --git a/src/checkout.h b/src/checkout.h index d48e263e4..6d7186860 100644 --- a/src/checkout.h +++ b/src/checkout.h @@ -9,52 +9,9 @@ #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_vector conflicts; - 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; - -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; - -enum { - CHECKOUT_ACTION__NONE = 0, - CHECKOUT_ACTION__REMOVE = 1, - CHECKOUT_ACTION__UPDATE_BLOB = 2, - CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, - CHECKOUT_ACTION__CONFLICT = 8, - CHECKOUT_ACTION__UPDATE_CONFLICT = 16, - CHECKOUT_ACTION__MAX = 16, - CHECKOUT_ACTION__DEFER_REMOVE = 32, - CHECKOUT_ACTION__REMOVE_AND_UPDATE = - (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), -}; - /** * Update the working directory to match the target iterator. The * expected baseline value can be passed in via the checkout options @@ -64,23 +21,4 @@ extern int git_checkout_iterator( git_iterator *target, const git_checkout_opts *opts); -int git_checkout__safe_for_update_only( - const char *path, - mode_t expected_mode); - -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); - -void git_checkout__report_progress( - checkout_data *data, - const char *path); - -int git_checkout__get_conflicts(checkout_data *data, git_iterator *workdir, git_vector *pathspec); -int git_checkout__conflicts(checkout_data *data); - #endif diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c deleted file mode 100644 index c39cb764e..000000000 --- a/src/checkout_conflicts.c +++ /dev/null @@ -1,659 +0,0 @@ -/* - * 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 - -#include "checkout.h" - -#include "vector.h" -#include "index.h" -#include "pathspec.h" -#include "merge_file.h" -#include "git2/repository.h" -#include "git2/types.h" -#include "git2/index.h" -#include "git2/sys/index.h" - -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; -} - -GIT_INLINE(void) path_equal_or_prefixed( - bool *path_eq, - bool *path_prefixed, - const char *parent, - const char *child) -{ - const char *p, *c; - - *path_eq = 0; - *path_prefixed = 0; - - for (p = parent, c = child; *p && *c; p++, c++) { - if (*p != *c) - return; - } - - if (!*p) - *path_prefixed = (*c == '/'); - - if (!*p && !*c) - *path_eq = 1; -} - -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; - bool eq, prefixed; - int 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; - } - - 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 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 = git_checkout__safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) - return error; - - 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_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 = git_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; -} - -int git_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; -} - -int git_checkout__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++; - git_checkout__report_progress(data, - conflict->ours ? conflict->ours->path : - (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); - } - - return error; -} From c929d6b727cb35a681152ae2c7d6dfbaf971f829 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 9 Oct 2013 18:26:42 -0400 Subject: [PATCH 11/11] Move path prefixed help to path.h --- src/checkout.c | 51 +++++++++++++------------------------------------- src/path.h | 28 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index c226f4342..85a817608 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -27,6 +27,7 @@ #include "pathspec.h" #include "buf_text.h" #include "merge_file.h" +#include "path.h" /* See docs/checkout-internals.md for more information */ @@ -887,29 +888,6 @@ done: return error; } -GIT_INLINE(void) path_equal_or_prefixed( - bool *path_eq, - bool *path_prefixed, - const char *parent, - const char *child) -{ - const char *p, *c; - - *path_eq = 0; - *path_prefixed = 0; - - for (p = parent, c = child; *p && *c; p++, c++) { - if (*p != *c) - return; - } - - if (!*p) - *path_prefixed = (*c == '/'); - - if (!*p && !*c) - *path_eq = 1; -} - static int checkout_conflicts_mark_directoryfile( checkout_data *data) { @@ -917,8 +895,7 @@ static int checkout_conflicts_mark_directoryfile( const git_index_entry *entry; size_t i, j, len; const char *path; - bool eq, prefixed; - int error = 0; + int prefixed, error = 0; len = git_index_entrycount(data->index); @@ -947,12 +924,12 @@ static int checkout_conflicts_mark_directoryfile( goto done; } - path_equal_or_prefixed(&eq, &prefixed, path, entry->path); + prefixed = git_path_equal_or_prefixed(path, entry->path); - if (eq) + if (prefixed == GIT_PATH_EQUAL) continue; - if (prefixed) + if (prefixed == GIT_PATH_PREFIX) conflict->directoryfile = 1; break; @@ -1055,8 +1032,6 @@ static int checkout_get_actions( counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts); - /* HERE */ - git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); @@ -1257,7 +1232,7 @@ static int checkout_submodule( return checkout_submodule_update_index(data, file); } -void report_progress( +static void report_progress( checkout_data *data, const char *path) { @@ -1267,7 +1242,7 @@ void report_progress( data->opts.progress_payload); } -int git_checkout__safe_for_update_only(const char *path, mode_t expected_mode) +static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) { struct stat st; @@ -1288,7 +1263,7 @@ int git_checkout__safe_for_update_only(const char *path, mode_t expected_mode) return 0; } -int git_checkout__write_content( +static int checkout_write_content( checkout_data *data, const git_oid *oid, const char *full_path, @@ -1337,13 +1312,13 @@ static int checkout_blob( return -1; if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = git_checkout__safe_for_update_only( + int rval = checkout_safe_for_update_only( git_buf_cstr(&data->path), file->mode); if (rval <= 0) return rval; } - error = git_checkout__write_content( + error = checkout_write_content( data, &file->oid, git_buf_cstr(&data->path), NULL, file->mode, &st); /* update the index unless prevented */ @@ -1593,10 +1568,10 @@ static int checkout_write_entry( } if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = git_checkout__safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) + (error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) return error; - return git_checkout__write_content(data, + return checkout_write_content(data, &side->oid, git_buf_cstr(&data->path), hint_path, side->mode, &st); } @@ -1694,7 +1669,7 @@ static int checkout_write_merge( goto done; if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = git_checkout__safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 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 || diff --git a/src/path.h b/src/path.h index eaf94d486..17f4f7726 100644 --- a/src/path.h +++ b/src/path.h @@ -358,6 +358,34 @@ extern int git_path_dirload_with_stat( const char *end_stat, 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 */ extern int git_path_set_error( int errno_value, const char *path, const char *action);