diff --git a/src/checkout.c b/src/checkout.c index fd2b19a95..2893c63de 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -211,7 +211,7 @@ static bool checkout_is_workdir_modified( if (baseitem->size && wditem->file_size != baseitem->size) return true; - if (git_diff__oid_for_entry(&oid, data->diff, wditem, NULL) < 0) + if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) return false; /* Allow the checkout if the workdir is not modified *or* if the checkout diff --git a/src/diff.c b/src/diff.c index c9d1d8730..d7365ef77 100644 --- a/src/diff.c +++ b/src/diff.c @@ -570,7 +570,7 @@ int git_diff__oid_for_file( git_oid *out, git_diff *diff, const char *path, - uint16_t mode, + uint16_t mode, git_off_t size) { git_index_entry entry; @@ -580,13 +580,14 @@ int git_diff__oid_for_file( entry.file_size = size; entry.path = (char *)path; - return git_diff__oid_for_entry(out, diff, &entry, NULL); + return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); } int git_diff__oid_for_entry( git_oid *out, git_diff *diff, const git_index_entry *src, + uint16_t mode, const git_oid *update_match) { int error = 0; @@ -600,7 +601,7 @@ int git_diff__oid_for_entry( &full_path, git_repository_workdir(diff->repo), entry.path) < 0) return -1; - if (!entry.mode) { + if (!mode) { struct stat st; diff->perf.stat_calls++; @@ -616,7 +617,7 @@ int git_diff__oid_for_entry( } /* calculate OID for file if possible */ - if (S_ISGITLINK(entry.mode)) { + if (S_ISGITLINK(mode)) { git_submodule *sm; if (!git_submodule_lookup(&sm, diff->repo, entry.path)) { @@ -630,7 +631,7 @@ int git_diff__oid_for_entry( */ giterr_clear(); } - } else if (S_ISLNK(entry.mode)) { + } else if (S_ISLNK(mode)) { error = git_odb__hashlink(out, full_path.ptr); diff->perf.oid_calculations++; } else if (!git__is_sizet(entry.file_size)) { @@ -657,11 +658,14 @@ int git_diff__oid_for_entry( /* update index for entry if requested */ if (!error && update_match && git_oid_equal(out, update_match)) { git_index *idx; + git_index_entry updated_entry; - if (!(error = git_repository_index__weakptr(&idx, diff->repo))) { - git_oid_cpy(&entry.id, out); - error = git_index_add(idx, &entry); - } + memcpy(&updated_entry, &entry, sizeof(git_index_entry)); + updated_entry.mode = mode; + git_oid_cpy(&updated_entry.id, out); + + if (!(error = git_repository_index__weakptr(&idx, diff->repo))) + error = git_index_add(idx, &updated_entry); } git_buf_free(&full_path); @@ -856,7 +860,7 @@ static int maybe_modified( &oitem->id : NULL; if ((error = git_diff__oid_for_entry( - &noid, diff, nitem, update_check)) < 0) + &noid, diff, nitem, nmode, update_check)) < 0) return error; /* if oid matches, then mark unmodified (except submodules, where diff --git a/src/diff.h b/src/diff.h index 3305238d0..2a35fd9ac 100644 --- a/src/diff.h +++ b/src/diff.h @@ -94,7 +94,7 @@ extern int git_diff_delta__format_file_header( extern int git_diff__oid_for_file( git_oid *out, git_diff *, const char *, uint16_t, git_off_t); extern int git_diff__oid_for_entry( - git_oid *out, git_diff *, const git_index_entry *, const git_oid *update); + git_oid *out, git_diff *, const git_index_entry *, uint16_t, const git_oid *update); extern int git_diff__from_iterators( git_diff **diff_ptr, diff --git a/tests/status/worktree.c b/tests/status/worktree.c index f8d1f7f54..56f98a882 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -1096,3 +1096,51 @@ void test_status_worktree__unreadable_as_untracked(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } +void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo"); + git_reference *head; + git_object *head_object; + git_index *index; + const git_index_entry *idx_entry; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + const char *expected_paths[] = { "README" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_UPDATE_INDEX; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); + + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + + cl_git_rewritefile("testrepo/README", "This was rewritten."); + + /* this status rewrites the index because we have changed the + * contents of a tracked file + */ + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(1, counts.entry_count); + + /* now ensure that the status's rewrite of the index did not screw + * up the mode of the symlink `link_to_new.txt`, particularly + * on platforms that don't support symlinks + */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read(index, true)); + + cl_assert(idx_entry = git_index_get_bypath(index, "link_to_new.txt", 0)); + cl_assert(S_ISLNK(idx_entry->mode)); + + git_index_free(index); + git_object_free(head_object); + git_reference_free(head); +} +