Commit 6d6020de by Carlos Martín Nieto

Merge pull request #3353 from ethomson/wrongcase_add

index: canonicalize directory case when adding
parents 2964cbea a32bc85e
......@@ -31,6 +31,11 @@ v0.23 + 1
with which to implement the transactional/atomic semantics for the
configuration backend.
* `git_index_add` will now use the case as provided by the caller on
case insensitive systems. Previous versions would keep the case as
it existed in the index. This does not affect the higher-level
`git_index_add_bypath` or `git_index_add_frombuffer` functions.
v0.23
------
......
......@@ -977,16 +977,27 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
return 0;
}
static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src)
static void index_entry_cpy(
git_index_entry *tgt,
git_index *index,
const git_index_entry *src,
bool update_path)
{
const char *tgt_path = tgt->path;
memcpy(tgt, src, sizeof(*tgt));
tgt->path = tgt_path; /* reset to existing path data */
/* keep the existing path buffer, but update the path to the one
* given by the caller, if we trust it.
*/
tgt->path = tgt_path;
if (index->ignore_case && update_path)
memcpy((char *)tgt->path, src->path, strlen(tgt->path));
}
static int index_entry_dup(
git_index_entry **out,
git_repository *repo,
git_index *index,
const git_index_entry *src)
{
git_index_entry *entry;
......@@ -996,10 +1007,10 @@ static int index_entry_dup(
return 0;
}
if (index_entry_create(&entry, repo, src->path) < 0)
if (index_entry_create(&entry, INDEX_OWNER(index), src->path) < 0)
return -1;
index_entry_cpy(entry, src);
index_entry_cpy(entry, index, src, false);
*out = entry;
return 0;
}
......@@ -1102,6 +1113,74 @@ static int check_file_directory_collision(git_index *index,
return 0;
}
static int canonicalize_directory_path(
git_index *index, git_index_entry *entry)
{
const git_index_entry *match, *best = NULL;
char *search, *sep;
size_t pos, search_len, best_len;
if (!index->ignore_case)
return 0;
/* item already exists in the index, simply re-use the existing case */
if ((match = git_index_get_bypath(index, entry->path, 0)) != NULL) {
memcpy((char *)entry->path, match->path, strlen(entry->path));
return 0;
}
/* nothing to do */
if (strchr(entry->path, '/') == NULL)
return 0;
if ((search = git__strdup(entry->path)) == NULL)
return -1;
/* starting at the parent directory and descending to the root, find the
* common parent directory.
*/
while (!best && (sep = strrchr(search, '/'))) {
sep[1] = '\0';
search_len = strlen(search);
git_vector_bsearch2(
&pos, &index->entries, index->entries_search_path, search);
while ((match = git_vector_get(&index->entries, pos))) {
if (GIT_IDXENTRY_STAGE(match) != 0) {
/* conflicts do not contribute to canonical paths */
} else if (memcmp(search, match->path, search_len) == 0) {
/* prefer an exact match to the input filename */
best = match;
best_len = search_len;
break;
} else if (strncasecmp(search, match->path, search_len) == 0) {
/* continue walking, there may be a path with an exact
* (case sensitive) match later in the index, but use this
* as the best match until that happens.
*/
if (!best) {
best = match;
best_len = search_len;
}
} else {
break;
}
pos++;
}
sep[0] = '\0';
}
if (best)
memcpy((char *)entry->path, best->path, best_len);
git__free(search);
return 0;
}
static int index_no_dups(void **old, void *new)
{
const git_index_entry *entry = new;
......@@ -1115,10 +1194,17 @@ static int index_no_dups(void **old, void *new)
* it, then it will return an error **and also free the entry**. When
* it replaces an existing entry, it will update the entry_ptr with the
* actual entry in the index (and free the passed in one).
* trust_path is whether we use the given path, or whether (on case
* insensitive systems only) we try to canonicalize the given path to
* be within an existing directory.
* trust_mode is whether we trust the mode in entry_ptr.
*/
static int index_insert(
git_index *index, git_index_entry **entry_ptr, int replace, bool trust_mode)
git_index *index,
git_index_entry **entry_ptr,
int replace,
bool trust_path,
bool trust_mode)
{
int error = 0;
size_t path_length, position;
......@@ -1156,8 +1242,14 @@ static int index_insert(
entry->mode = index_merge_mode(index, existing, entry->mode);
}
/* canonicalize the directory name */
if (!trust_path)
error = canonicalize_directory_path(index, entry);
/* look for tree / blob name collisions, removing conflicts if requested */
error = check_file_directory_collision(index, entry, position, replace);
if (!error)
error = check_file_directory_collision(index, entry, position, replace);
if (error < 0)
/* skip changes */;
......@@ -1166,7 +1258,7 @@ static int index_insert(
*/
else if (existing) {
if (replace)
index_entry_cpy(existing, entry);
index_entry_cpy(existing, index, entry, trust_path);
index_entry_free(entry);
*entry_ptr = entry = existing;
}
......@@ -1246,7 +1338,7 @@ int git_index_add_frombuffer(
return -1;
}
if (index_entry_dup(&entry, INDEX_OWNER(index), source_entry) < 0)
if (index_entry_dup(&entry, index, source_entry) < 0)
return -1;
error = git_blob_create_frombuffer(&id, INDEX_OWNER(index), buffer, len);
......@@ -1258,7 +1350,7 @@ int git_index_add_frombuffer(
git_oid_cpy(&entry->id, &id);
entry->file_size = len;
if ((error = index_insert(index, &entry, 1, true)) < 0)
if ((error = index_insert(index, &entry, 1, true, true)) < 0)
return error;
/* Adding implies conflict was resolved, move conflict entries to REUC */
......@@ -1317,7 +1409,7 @@ int git_index_add_bypath(git_index *index, const char *path)
assert(index && path);
if ((ret = index_entry_init(&entry, index, path)) == 0)
ret = index_insert(index, &entry, 1, false);
ret = index_insert(index, &entry, 1, false, false);
/* If we were given a directory, let's see if it's a submodule */
if (ret < 0 && ret != GIT_EDIRECTORY)
......@@ -1343,7 +1435,7 @@ int git_index_add_bypath(git_index *index, const char *path)
if ((ret = add_repo_as_submodule(&entry, index, path)) < 0)
return ret;
if ((ret = index_insert(index, &entry, 1, false)) < 0)
if ((ret = index_insert(index, &entry, 1, false, false)) < 0)
return ret;
} else if (ret < 0) {
return ret;
......@@ -1393,8 +1485,8 @@ int git_index_add(git_index *index, const git_index_entry *source_entry)
return -1;
}
if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 ||
(ret = index_insert(index, &entry, 1, true)) < 0)
if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 ||
(ret = index_insert(index, &entry, 1, true, true)) < 0)
return ret;
git_tree_cache_invalidate_path(index->tree, entry->path);
......@@ -1543,9 +1635,9 @@ int git_index_conflict_add(git_index *index,
assert (index);
if ((ret = index_entry_dup(&entries[0], INDEX_OWNER(index), ancestor_entry)) < 0 ||
(ret = index_entry_dup(&entries[1], INDEX_OWNER(index), our_entry)) < 0 ||
(ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0)
if ((ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0 ||
(ret = index_entry_dup(&entries[1], index, our_entry)) < 0 ||
(ret = index_entry_dup(&entries[2], index, their_entry)) < 0)
goto on_error;
/* Validate entries */
......@@ -1579,7 +1671,7 @@ int git_index_conflict_add(git_index *index,
/* Make sure stage is correct */
GIT_IDXENTRY_STAGE_SET(entries[i], i + 1);
if ((ret = index_insert(index, &entries[i], 0, true)) < 0)
if ((ret = index_insert(index, &entries[i], 0, true, true)) < 0)
goto on_error;
entries[i] = NULL; /* don't free if later entry fails */
......@@ -2153,7 +2245,7 @@ static size_t read_entry(
entry.path = (char *)path_ptr;
if (index_entry_dup(out, INDEX_OWNER(index), &entry) < 0)
if (index_entry_dup(out, index, &entry) < 0)
return 0;
return entry_size;
......@@ -2670,7 +2762,7 @@ static int read_tree_cb(
entry->mode == old_entry->mode &&
git_oid_equal(&entry->id, &old_entry->id))
{
index_entry_cpy(entry, old_entry);
index_entry_cpy(entry, data->index, old_entry, false);
entry->flags_extended = 0;
}
......@@ -2802,7 +2894,7 @@ int git_index_read_index(
if (diff < 0) {
git_vector_insert(&remove_entries, (git_index_entry *)old_entry);
} else if (diff > 0) {
if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0)
if ((error = index_entry_dup(&entry, index, new_entry)) < 0)
goto done;
git_vector_insert(&new_entries, entry);
......@@ -2813,7 +2905,7 @@ int git_index_read_index(
if (git_oid_equal(&old_entry->id, &new_entry->id)) {
git_vector_insert(&new_entries, (git_index_entry *)old_entry);
} else {
if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0)
if ((error = index_entry_dup(&entry, index, new_entry)) < 0)
goto done;
git_vector_insert(&new_entries, entry);
......
......@@ -72,3 +72,171 @@ void test_index_bypath__add_hidden(void)
cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode);
#endif
}
void test_index_bypath__add_keeps_existing_case(void)
{
const git_index_entry *entry;
if (!cl_repo_get_bool(g_repo, "core.ignorecase"))
clar__skip();
cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file");
cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/file1.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/file1.txt", 0));
cl_assert_equal_s("just_a_dir/file1.txt", entry->path);
cl_git_rewritefile("submod2/just_a_dir/file1.txt", "Updated!");
cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/FILE1.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/FILE1.txt", 0));
cl_assert_equal_s("just_a_dir/file1.txt", entry->path);
}
void test_index_bypath__add_honors_existing_case(void)
{
const git_index_entry *entry;
if (!cl_repo_get_bool(g_repo, "core.ignorecase"))
clar__skip();
cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file");
cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file");
cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file");
cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file");
cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt"));
cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt"));
cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0));
cl_assert_equal_s("just_a_dir/File1.txt", entry->path);
cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0));
cl_assert_equal_s("just_a_dir/file2.txt", entry->path);
cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0));
cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path);
cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten");
cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0));
cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path);
}
void test_index_bypath__add_honors_existing_case_2(void)
{
git_index_entry dummy = { { 0 } };
const git_index_entry *entry;
if (!cl_repo_get_bool(g_repo, "core.ignorecase"))
clar__skip();
dummy.mode = GIT_FILEMODE_BLOB;
/* note that `git_index_add` does no checking to canonical directories */
dummy.path = "Just_a_dir/file0.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "just_a_dir/fileA.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "Just_A_Dir/fileB.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "JUST_A_DIR/fileC.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "just_A_dir/fileD.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "JUST_a_DIR/fileE.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file");
cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file");
cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file");
cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file");
cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt"));
cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt"));
cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt"));
cl_git_pass(git_index_add_bypath(g_idx, "JusT_A_DIR/FILE4.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0));
cl_assert_equal_s("just_a_dir/File1.txt", entry->path);
cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0));
cl_assert_equal_s("JUST_A_DIR/file2.txt", entry->path);
cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0));
cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path);
cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten");
cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0));
cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path);
}
void test_index_bypath__add_honors_existing_case_3(void)
{
git_index_entry dummy = { { 0 } };
const git_index_entry *entry;
if (!cl_repo_get_bool(g_repo, "core.ignorecase"))
clar__skip();
dummy.mode = GIT_FILEMODE_BLOB;
dummy.path = "just_a_dir/filea.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "Just_A_Dir/fileB.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "just_A_DIR/FILEC.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "Just_a_DIR/FileD.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
cl_git_mkfile("submod2/JuSt_A_DiR/fILEE.txt", "This is a file");
cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/fILEE.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/fILEE.txt", 0));
cl_assert_equal_s("just_a_dir/fILEE.txt", entry->path);
}
void test_index_bypath__add_honors_existing_case_4(void)
{
git_index_entry dummy = { { 0 } };
const git_index_entry *entry;
if (!cl_repo_get_bool(g_repo, "core.ignorecase"))
clar__skip();
dummy.mode = GIT_FILEMODE_BLOB;
dummy.path = "just_a_dir/a/b/c/d/e/file1.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
dummy.path = "just_a_dir/a/B/C/D/E/file2.txt";
cl_git_pass(git_index_add(g_idx, &dummy));
cl_must_pass(p_mkdir("submod2/just_a_dir/a", 0777));
cl_must_pass(p_mkdir("submod2/just_a_dir/a/b", 0777));
cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z", 0777));
cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y", 0777));
cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y/x", 0777));
cl_git_mkfile("submod2/just_a_dir/a/b/z/y/x/FOO.txt", "This is a file");
cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt"));
cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt", 0));
cl_assert_equal_s("just_a_dir/a/b/Z/y/X/foo.txt", entry->path);
}
......@@ -48,3 +48,34 @@ void test_index_rename__single_file(void)
cl_fixture_cleanup("rename");
}
void test_index_rename__casechanging(void)
{
git_repository *repo;
git_index *index;
const git_index_entry *entry;
git_index_entry new = {{0}};
p_mkdir("rename", 0700);
cl_git_pass(git_repository_init(&repo, "./rename", 0));
cl_git_pass(git_repository_index(&index, repo));
cl_git_mkfile("./rename/lame.name.txt", "new_file\n");
cl_git_pass(git_index_add_bypath(index, "lame.name.txt"));
cl_assert_equal_i(1, git_index_entrycount(index));
cl_assert((entry = git_index_get_bypath(index, "lame.name.txt", 0)));
memcpy(&new, entry, sizeof(git_index_entry));
new.path = "LAME.name.TXT";
cl_git_pass(git_index_add(index, &new));
cl_assert((entry = git_index_get_bypath(index, "LAME.name.TXT", 0)));
if (cl_repo_get_bool(repo, "core.ignorecase"))
cl_assert_equal_i(1, git_index_entrycount(index));
else
cl_assert_equal_i(2, git_index_entrycount(index));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment