Merge pull request #3429 from ethomson/checkout_chmod Checkout: handle mode changes
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
diff --git a/src/checkout.c b/src/checkout.c
index de48c9e..8c06b33 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -243,6 +243,12 @@ static int checkout_action_common(
if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL)
*action |= CHECKOUT_ACTION__REMOVE;
+ /* if the file is on disk and doesn't match our mode, force update */
+ if (wd &&
+ GIT_PERMS_IS_EXEC(wd->mode) !=
+ GIT_PERMS_IS_EXEC(delta->new_file.mode))
+ *action |= CHECKOUT_ACTION__REMOVE;
+
notify = GIT_CHECKOUT_NOTIFY_UPDATED;
}
@@ -1500,15 +1506,6 @@ static int blob_content_to_file(
if (error < 0)
return error;
- if (GIT_PERMS_IS_EXEC(mode)) {
- data->perfdata.chmod_calls++;
-
- if ((error = p_chmod(path, mode)) < 0) {
- giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
- return error;
- }
- }
-
if (st) {
data->perfdata.stat_calls++;
diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c
index be40198..5680b86 100644
--- a/tests/checkout/tree.c
+++ b/tests/checkout/tree.c
@@ -946,7 +946,7 @@ void test_checkout_tree__filemode_preserved_in_index(void)
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);
+ cl_assert(GIT_PERMS_IS_EXEC(entry->mode));
git_commit_free(commit);
@@ -957,7 +957,7 @@ void test_checkout_tree__filemode_preserved_in_index(void)
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);
+ cl_assert(!GIT_PERMS_IS_EXEC(entry->mode));
git_commit_free(commit);
@@ -968,7 +968,18 @@ void test_checkout_tree__filemode_preserved_in_index(void)
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);
+ cl_assert(GIT_PERMS_IS_EXEC(entry->mode));
+
+ git_commit_free(commit);
+
+
+ /* Finally, check out the text file again and check that the exec bit is cleared */
+ 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(!GIT_PERMS_IS_EXEC(entry->mode));
git_commit_free(commit);
@@ -976,6 +987,73 @@ void test_checkout_tree__filemode_preserved_in_index(void)
git_index_free(index);
}
+mode_t read_filemode(const char *path)
+{
+ git_buf fullpath = GIT_BUF_INIT;
+ struct stat st;
+ mode_t result;
+
+ git_buf_joinpath(&fullpath, "testrepo", path);
+ cl_must_pass(p_stat(fullpath.ptr, &st));
+
+ result = GIT_PERMS_IS_EXEC(st.st_mode) ?
+ GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB;
+
+ git_buf_free(&fullpath);
+
+ return result;
+}
+
+void test_checkout_tree__filemode_preserved_in_workdir(void)
+{
+#ifndef GIT_WIN32
+ git_oid executable_oid;
+ git_commit *commit;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ /* 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));
+
+ cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+ cl_assert(GIT_PERMS_IS_EXEC(read_filemode("executable.txt")));
+
+ 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(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt")));
+
+ 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(GIT_PERMS_IS_EXEC(read_filemode("a/b.txt")));
+
+ git_commit_free(commit);
+
+
+ /* Finally, check out the text file again and check that the exec bit is cleared */
+ 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(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt")));
+
+ git_commit_free(commit);
+#endif
+}
+
void test_checkout_tree__removes_conflicts(void)
{
git_oid commit_id;