Merge pull request #3032 from jfultz/index-file-modes Fix git_checkout_tree() to do index filemodes correctly on Windows.
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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
diff --git a/src/index.c b/src/index.c
index dbcc37a..0d2a03e 100644
--- a/src/index.c
+++ b/src/index.c
@@ -987,9 +987,10 @@ 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_mode is whether we trust the mode in entry_ptr.
*/
static int index_insert(
- git_index *index, git_index_entry **entry_ptr, int replace)
+ git_index *index, git_index_entry **entry_ptr, int replace, bool trust_mode)
{
int error = 0;
size_t path_length, position;
@@ -1021,7 +1022,10 @@ static int index_insert(
&position, index, entry->path, 0, GIT_IDXENTRY_STAGE(entry), false)) {
existing = index->entries.contents[position];
/* update filemode to existing values if stat is not trusted */
- entry->mode = index_merge_mode(index, existing, entry->mode);
+ if (trust_mode)
+ entry->mode = git_index__create_mode(entry->mode);
+ else
+ entry->mode = index_merge_mode(index, existing, entry->mode);
}
/* look for tree / blob name collisions, removing conflicts if requested */
@@ -1122,7 +1126,7 @@ int git_index_add_frombuffer(
git_oid_cpy(&entry->id, &id);
entry->file_size = len;
- if ((error = index_insert(index, &entry, 1)) < 0)
+ if ((error = index_insert(index, &entry, 1, true)) < 0)
return error;
/* Adding implies conflict was resolved, move conflict entries to REUC */
@@ -1142,7 +1146,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)) < 0)
+ (ret = index_insert(index, &entry, 1, false)) < 0)
return ret;
/* Adding implies conflict was resolved, move conflict entries to REUC */
@@ -1182,7 +1186,7 @@ int git_index_add(git_index *index, const git_index_entry *source_entry)
}
if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 ||
- (ret = index_insert(index, &entry, 1)) < 0)
+ (ret = index_insert(index, &entry, 1, true)) < 0)
return ret;
git_tree_cache_invalidate_path(index->tree, entry->path);
@@ -1313,7 +1317,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], 1)) < 0)
+ if ((ret = index_insert(index, &entries[i], 1, true)) < 0)
goto on_error;
entries[i] = NULL; /* don't free if later entry fails */
@@ -2537,7 +2541,7 @@ int git_index_add_all(
entry->id = blobid;
/* add working directory item to index */
- if ((error = index_insert(index, &entry, 1)) < 0)
+ if ((error = index_insert(index, &entry, 1, false)) < 0)
break;
git_tree_cache_invalidate_path(index->tree, wd->path);
diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c
index 7d4c784..3973d93 100644
--- a/tests/checkout/tree.c
+++ b/tests/checkout/tree.c
@@ -925,18 +925,43 @@ void test_checkout_tree__filemode_preserved_in_index(void)
git_index *index;
const git_index_entry *entry;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
cl_git_pass(git_repository_index(&index, g_repo));
+ /* test a freshly added executable */
cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
- opts.checkout_strategy = GIT_CHECKOUT_FORCE;
-
cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0));
cl_assert_equal_i(0100755, entry->mode);
git_commit_free(commit);
+
+
+ /* Now start with a commit which has a text file */
+ cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9"));
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+ cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+ cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
+ cl_assert_equal_i(0100644, entry->mode);
+
+ git_commit_free(commit);
+
+
+ /* And then check out to a commit which converts the text file to an executable */
+ cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e"));
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+ cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+ cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
+ cl_assert_equal_i(0100755, entry->mode);
+
+ git_commit_free(commit);
+
+
git_index_free(index);
}
diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c
index 58d7935..b390799 100644
--- a/tests/index/filemodes.c
+++ b/tests/index/filemodes.c
@@ -153,6 +153,85 @@ void test_index_filemodes__trusted(void)
git_index_free(index);
}
+#define add_entry_and_check_mode(I,FF,X) add_entry_and_check_mode_(I,FF,X,__FILE__,__LINE__)
+
+static void add_entry_and_check_mode_(
+ git_index *index, bool from_file, git_filemode_t mode,
+ const char *file, int line)
+{
+ size_t pos;
+ const git_index_entry* entry;
+ git_index_entry new_entry;
+
+ /* If old_filename exists, we copy that to the new file, and test
+ * git_index_add(), otherwise create a new entry testing git_index_add_frombuffer
+ */
+ if (from_file)
+ {
+ clar__assert(!git_index_find(&pos, index, "exec_off"),
+ file, line, "Cannot find original index entry", NULL, 1);
+
+ entry = git_index_get_byindex(index, pos);
+
+ memcpy(&new_entry, entry, sizeof(new_entry));
+ }
+ else
+ memset(&new_entry, 0x0, sizeof(git_index_entry));
+
+ new_entry.path = "filemodes/explicit_test";
+ new_entry.mode = mode;
+
+ if (from_file)
+ {
+ clar__assert(!git_index_add(index, &new_entry),
+ file, line, "Cannot add index entry", NULL, 1);
+ }
+ else
+ {
+ const char *content = "hey there\n";
+ clar__assert(!git_index_add_frombuffer(index, &new_entry, content, strlen(content)),
+ file, line, "Cannot add index entry from buffer", NULL, 1);
+ }
+
+ clar__assert(!git_index_find(&pos, index, "filemodes/explicit_test"),
+ file, line, "Cannot find new index entry", NULL, 1);
+
+ entry = git_index_get_byindex(index, pos);
+
+ clar__assert_equal(file, line, "Expected mode does not match index",
+ 1, "%07o", (unsigned int)entry->mode, (unsigned int)mode);
+}
+
+void test_index_filemodes__explicit(void)
+{
+ git_index *index;
+
+ /* These tests should run and work everywhere, as the filemode is
+ * given explicitly to git_index_add or git_index_add_frombuffer
+ */
+ cl_repo_set_bool(g_repo, "core.filemode", false);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ /* Each of these tests keeps overwriting the same file in the index. */
+ /* 1 - add new 0644 entry */
+ add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB);
+
+ /* 2 - add 0755 entry over existing 0644 */
+ add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ /* 3 - add 0644 entry over existing 0755 */
+ add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB);
+
+ /* 4 - add 0755 buffer entry over existing 0644 */
+ add_entry_and_check_mode(index, false, GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ /* 5 - add 0644 buffer entry over existing 0755 */
+ add_entry_and_check_mode(index, false, GIT_FILEMODE_BLOB);
+
+ git_index_free(index);
+}
+
void test_index_filemodes__invalid(void)
{
git_index *index;