Hash :
8b6e2895
Author :
Date :
2018-09-21T15:18:03
index: fix adding index entries with conflicting files When adding an index entry "a/b/c" while an index entry "a/b" already exists, git will happily remove "a/b/c" and only add the new index entry: $ git init test Initialized empty Git repository in /tmp/test.repo/test/.git/ $ touch x $ git add x $ rm x $ mkdir x $ touch x/y $ git add x/y $ git status A x/y The other way round, adding an index entry "a/b" with an entry "a/b/c" already existing is equivalent, where git will remove "a/b/c" and add "a/b". In contrast, libgit2 will currently fail to add these properly and instead complain about the entry appearing as both a file and a directory. This is a programming error, though: our current code already tries to detect and, in the case of `git_index_add`, to automatically replace such index entries. Funnily enough, we already remove the conflicting index entries, but instead of adding the new entry we then bail out afterwards. This leaves callers with the worst of both worlds: we both remove the old entry but fail to add the new one. The root cause is weird semantics of the `has_file_name` and `has_dir_name` functions. While these functions only sound like they are responsible for detecting such conflicts, they will also already remove them in case where its `ok_to_replace` parameter is set. But even if we tell it to replace such entries, it will return an error code. Fix the error by returning success in case where the entries have been replaced. Fix an already existing test which tested for wrong behaviour. Note that the test didn't notice that the resulting tree had no entries. Thus it is fine to change existing behaviour here, as the previous result could've let to silently loosing data. Also add a new test that verifies behaviour in the reverse conflicting case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
#include "clar_libgit2.h"
#include "git2/repository.h"
#include "git2/index.h"
static git_repository *g_repo;
static git_odb *g_odb;
static git_index *g_index;
static git_oid g_empty_id;
void test_index_collision__initialize(void)
{
g_repo = cl_git_sandbox_init("empty_standard_repo");
cl_git_pass(git_repository_odb(&g_odb, g_repo));
cl_git_pass(git_repository_index(&g_index, g_repo));
cl_git_pass(git_odb_write(&g_empty_id, g_odb, "", 0, GIT_OBJ_BLOB));
}
void test_index_collision__cleanup(void)
{
git_index_free(g_index);
git_odb_free(g_odb);
cl_git_sandbox_cleanup();
}
void test_index_collision__add_blob_with_conflicting_file(void)
{
git_index_entry entry;
git_tree_entry *tentry;
git_oid tree_id;
git_tree *tree;
memset(&entry, 0, sizeof(entry));
entry.ctime.seconds = 12346789;
entry.mtime.seconds = 12346789;
entry.mode = 0100644;
entry.file_size = 0;
git_oid_cpy(&entry.id, &g_empty_id);
entry.path = "a/b";
cl_git_pass(git_index_add(g_index, &entry));
/* Check a/b exists here */
cl_git_pass(git_index_write_tree(&tree_id, g_index));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b"));
git_tree_entry_free(tentry);
git_tree_free(tree);
/* create a tree/blob collision */
entry.path = "a/b/c";
cl_git_pass(git_index_add(g_index, &entry));
/* a/b should now be a tree and a/b/c a blob */
cl_git_pass(git_index_write_tree(&tree_id, g_index));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c"));
git_tree_entry_free(tentry);
git_tree_free(tree);
}
void test_index_collision__add_blob_with_conflicting_dir(void)
{
git_index_entry entry;
git_tree_entry *tentry;
git_oid tree_id;
git_tree *tree;
memset(&entry, 0, sizeof(entry));
entry.ctime.seconds = 12346789;
entry.mtime.seconds = 12346789;
entry.mode = 0100644;
entry.file_size = 0;
git_oid_cpy(&entry.id, &g_empty_id);
entry.path = "a/b/c";
cl_git_pass(git_index_add(g_index, &entry));
/* Check a/b/c exists here */
cl_git_pass(git_index_write_tree(&tree_id, g_index));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c"));
git_tree_entry_free(tentry);
git_tree_free(tree);
/* create a blob/tree collision */
entry.path = "a/b";
cl_git_pass(git_index_add(g_index, &entry));
/* a/b should now be a tree and a/b/c a blob */
cl_git_pass(git_index_write_tree(&tree_id, g_index));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b"));
cl_git_fail(git_tree_entry_bypath(&tentry, tree, "a/b/c"));
git_tree_entry_free(tentry);
git_tree_free(tree);
}
void test_index_collision__add_with_highstage_1(void)
{
git_index_entry entry;
memset(&entry, 0, sizeof(entry));
entry.ctime.seconds = 12346789;
entry.mtime.seconds = 12346789;
entry.mode = 0100644;
entry.file_size = 0;
git_oid_cpy(&entry.id, &g_empty_id);
entry.path = "a/b";
GIT_IDXENTRY_STAGE_SET(&entry, 2);
cl_git_pass(git_index_add(g_index, &entry));
/* create a blob beneath the previous tree entry */
entry.path = "a/b/c";
entry.flags = 0;
cl_git_pass(git_index_add(g_index, &entry));
/* create another tree entry above the blob */
entry.path = "a/b";
GIT_IDXENTRY_STAGE_SET(&entry, 1);
cl_git_pass(git_index_add(g_index, &entry));
}
void test_index_collision__add_with_highstage_2(void)
{
git_index_entry entry;
memset(&entry, 0, sizeof(entry));
entry.ctime.seconds = 12346789;
entry.mtime.seconds = 12346789;
entry.mode = 0100644;
entry.file_size = 0;
git_oid_cpy(&entry.id, &g_empty_id);
entry.path = "a/b/c";
GIT_IDXENTRY_STAGE_SET(&entry, 1);
cl_git_pass(git_index_add(g_index, &entry));
/* create a blob beneath the previous tree entry */
entry.path = "a/b/c";
GIT_IDXENTRY_STAGE_SET(&entry, 2);
cl_git_pass(git_index_add(g_index, &entry));
/* create another tree entry above the blob */
entry.path = "a/b";
GIT_IDXENTRY_STAGE_SET(&entry, 3);
cl_git_pass(git_index_add(g_index, &entry));
}