diff options
-rw-r--r-- | include/git2/diff.h | 7 | ||||
-rw-r--r-- | include/git2/status.h | 31 | ||||
-rw-r--r-- | include/git2/types.h | 12 | ||||
-rw-r--r-- | src/checkout.c | 2 | ||||
-rw-r--r-- | src/diff.c | 21 | ||||
-rw-r--r-- | src/diff_file.c | 1 | ||||
-rw-r--r-- | src/diff_print.c | 18 | ||||
-rw-r--r-- | src/diff_tform.c | 4 | ||||
-rw-r--r-- | src/path.c | 10 | ||||
-rw-r--r-- | src/status.c | 13 | ||||
-rw-r--r-- | tests/status/status_helpers.c | 3 | ||||
-rw-r--r-- | tests/status/worktree.c | 99 |
12 files changed, 189 insertions, 32 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h index 675c209be..8147fd31c 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -152,6 +152,12 @@ typedef enum { */ GIT_DIFF_UPDATE_INDEX = (1u << 15), + /** Include unreadable files in the diff */ + GIT_DIFF_INCLUDE_UNREADABLE = (1u << 16), + + /** Include unreadable files in the diff */ + GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 17), + /* * Options controlling how output will be generated */ @@ -237,6 +243,7 @@ typedef enum { GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */ GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */ GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */ + GIT_DELTA_UNREADABLE = 9, /**< entry is unreadable */ } git_delta_t; /** diff --git a/include/git2/status.h b/include/git2/status.h index effe5e1ea..3c86e5d7b 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -43,6 +43,7 @@ typedef enum { GIT_STATUS_WT_DELETED = (1u << 9), GIT_STATUS_WT_TYPECHANGE = (1u << 10), GIT_STATUS_WT_RENAMED = (1u << 11), + GIT_STATUS_WT_UNREADABLE = (1u << 12), GIT_STATUS_IGNORED = (1u << 14), } git_status_t; @@ -133,20 +134,22 @@ typedef enum { * together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline. */ typedef enum { - GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0), - GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1), - GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2), - GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3), - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), - GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11), - GIT_STATUS_OPT_NO_REFRESH = (1u << 12), - GIT_STATUS_OPT_UPDATE_INDEX = (1u << 13), + GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0), + GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1), + GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2), + GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3), + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), + GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11), + GIT_STATUS_OPT_NO_REFRESH = (1u << 12), + GIT_STATUS_OPT_UPDATE_INDEX = (1u << 13), + GIT_STATUS_OPT_INCLUDE_UNREADABLE = (1u << 14), + GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 15), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/include/git2/types.h b/include/git2/types.h index 6295ebbfa..76175b6bd 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -198,12 +198,12 @@ typedef enum { /** Valid modes for index and tree entries. */ typedef enum { - GIT_FILEMODE_NEW = 0000000, - GIT_FILEMODE_TREE = 0040000, - GIT_FILEMODE_BLOB = 0100644, - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755, - GIT_FILEMODE_LINK = 0120000, - GIT_FILEMODE_COMMIT = 0160000, + GIT_FILEMODE_UNREADABLE = 0000000, + GIT_FILEMODE_TREE = 0040000, + GIT_FILEMODE_BLOB = 0100644, + GIT_FILEMODE_BLOB_EXECUTABLE = 0100755, + GIT_FILEMODE_LINK = 0120000, + GIT_FILEMODE_COMMIT = 0160000, } git_filemode_t; typedef struct git_refspec git_refspec; diff --git a/src/checkout.c b/src/checkout.c index adb3c81e0..f25a6eff0 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -119,6 +119,7 @@ static int checkout_notify( case GIT_DELTA_ADDED: case GIT_DELTA_IGNORED: case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: target = &delta->new_file; break; case GIT_DELTA_DELETED: @@ -2143,6 +2144,7 @@ int git_checkout_iterator( diff_opts.flags = GIT_DIFF_INCLUDE_UNMODIFIED | + GIT_DIFF_INCLUDE_UNREADABLE | GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ GIT_DIFF_INCLUDE_IGNORED | diff --git a/src/diff.c b/src/diff.c index 325c599e0..2691d7ca0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -92,6 +92,10 @@ static int diff_delta__from_one( if (status == GIT_DELTA_UNTRACKED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) return 0; + + if (status == GIT_DELTA_UNREADABLE && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) + return 0; if (!git_pathspec__match( &diff->pathspec, entry->path, @@ -196,6 +200,7 @@ static git_diff_delta *diff_delta__last_for_item( if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) return delta; break; + case GIT_DELTA_UNREADABLE: case GIT_DELTA_UNTRACKED: if (diff->strcomp(delta->new_file.path, item->path) == 0 && git_oid__cmp(&delta->new_file.id, &item->id) == 0) @@ -293,6 +298,10 @@ bool git_diff_delta__should_skip( (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) return true; + if (delta->status == GIT_DELTA_UNREADABLE && + (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) + return true; + return false; } @@ -734,6 +743,11 @@ static int maybe_modified( else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) status = GIT_DELTA_TYPECHANGE; + else if (nmode == GIT_FILEMODE_UNREADABLE) { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) + error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, nitem); + return error; + } else { if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); @@ -954,6 +968,13 @@ static int handle_unmatched_new_item( } } + else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) + delta_type = GIT_DELTA_UNTRACKED; + else + delta_type = GIT_DELTA_UNREADABLE; + } + /* Actually create the record for this item if necessary */ if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0) return error; diff --git a/src/diff_file.c b/src/diff_file.c index f2a1d5099..96be0942b 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -112,6 +112,7 @@ int git_diff_file_content__init_from_diff( has_data = !use_old && (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; break; + case GIT_DELTA_UNREADABLE: case GIT_DELTA_MODIFIED: case GIT_DELTA_COPIED: case GIT_DELTA_RENAMED: diff --git a/src/diff_print.c b/src/diff_print.c index bb925ef98..fb62a5fc1 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -82,14 +82,15 @@ char git_diff_status_char(git_delta_t status) char code; switch (status) { - case GIT_DELTA_ADDED: code = 'A'; break; - case GIT_DELTA_DELETED: code = 'D'; break; - case GIT_DELTA_MODIFIED: code = 'M'; break; - case GIT_DELTA_RENAMED: code = 'R'; break; - case GIT_DELTA_COPIED: code = 'C'; break; - case GIT_DELTA_IGNORED: code = 'I'; break; - case GIT_DELTA_UNTRACKED: code = '?'; break; - default: code = ' '; break; + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + case GIT_DELTA_UNREADABLE: code = 'X'; break; + default: code = ' '; break; } return code; @@ -441,6 +442,7 @@ static int diff_print_patch_file( if (S_ISDIR(delta->new_file.mode) || delta->status == GIT_DELTA_UNMODIFIED || delta->status == GIT_DELTA_IGNORED || + delta->status == GIT_DELTA_UNREADABLE || (delta->status == GIT_DELTA_UNTRACKED && (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) return 0; diff --git a/src/diff_tform.c b/src/diff_tform.c index a2dab0ae2..423a0ca33 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -114,7 +114,7 @@ static git_diff_delta *diff_delta__merge_like_cgit_reversed( if ((dup = diff_delta__dup(a, pool)) == NULL) return NULL; - if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED) + if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED || b->status == GIT_DELTA_UNREADABLE) return dup; if (dup->status == GIT_DELTA_DELETED) { @@ -732,6 +732,7 @@ static bool is_rename_source( switch (delta->status) { case GIT_DELTA_ADDED: case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: case GIT_DELTA_IGNORED: return false; @@ -786,6 +787,7 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) { return (delta->status == GIT_DELTA_ADDED || delta->status == GIT_DELTA_UNTRACKED || + delta->status == GIT_DELTA_UNREADABLE || delta->status == GIT_DELTA_IGNORED); } diff --git a/src/path.c b/src/path.c index 6d0b3749b..4837b01f9 100644 --- a/src/path.c +++ b/src/path.c @@ -1116,7 +1116,15 @@ int git_path_dirload_with_stat( git_vector_remove(contents, i--); continue; } - + /* Treat the file as unreadable if we get any other error */ + if (error != 0) { + giterr_clear(); + error = 0; + memset(&ps->st, 0, sizeof(ps->st)); + ps->st.st_mode = GIT_FILEMODE_UNREADABLE; + continue; + } + break; } diff --git a/src/status.c b/src/status.c index 8d7612f72..cb2490042 100644 --- a/src/status.c +++ b/src/status.c @@ -62,6 +62,9 @@ static unsigned int workdir_delta2status( case GIT_DELTA_UNTRACKED: st = GIT_STATUS_WT_NEW; break; + case GIT_DELTA_UNREADABLE: + st = GIT_STATUS_WT_UNREADABLE; + break; case GIT_DELTA_DELETED: st = GIT_STATUS_WT_DELETED; break; @@ -310,6 +313,10 @@ int git_status_list_new( diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED; if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) findopt.flags = findopt.flags | @@ -329,8 +336,9 @@ int git_status_list_new( if (show != GIT_STATUS_SHOW_INDEX_ONLY) { if ((error = git_diff_index_to_workdir( - &status->idx2wd, repo, index, &diffopt)) < 0) + &status->idx2wd, repo, index, &diffopt)) < 0) { goto done; + } if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) @@ -407,8 +415,9 @@ int git_status_foreach_ext( size_t i; int error = 0; - if ((error = git_status_list_new(&status, repo, opts)) < 0) + if ((error = git_status_list_new(&status, repo, opts)) < 0) { return error; + } git_vector_foreach(&status->paired, i, status_entry) { const char *path = status_entry->head_to_index ? diff --git a/tests/status/status_helpers.c b/tests/status/status_helpers.c index 088279252..5d13caa9a 100644 --- a/tests/status/status_helpers.c +++ b/tests/status/status_helpers.c @@ -82,6 +82,9 @@ int cb_status__print( if (status_flags & GIT_STATUS_IGNORED) { wstatus = 'I'; wcount++; } + if (status_flags & GIT_STATUS_WT_UNREADABLE) { + wstatus = 'X'; wcount++; + } fprintf(stderr, "%c%c %s (%d/%d%s)\n", istatus, wstatus, path, icount, wcount, diff --git a/tests/status/worktree.c b/tests/status/worktree.c index ca9068aba..2e86c03b0 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -935,3 +935,102 @@ void test_status_worktree__update_stat_cache_0(void) git_status_list_free(status); } + +void test_status_worktree__unreadable(void) +{ + const char *expected_paths[] = { "no_permission/foo" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + /* Create directory with no read permission */ + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); + p_chmod("empty_standard_repo/no_permission", 0644); + + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + + /* Restore permissions so we can cleanup :) */ + p_chmod("empty_standard_repo/no_permission", 0777); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__unreadable_not_included(void) +{ + const char *expected_paths[] = { "no_permission/" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + /* Create directory with no read permission */ + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); + p_chmod("empty_standard_repo/no_permission", 0644); + + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = (GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED); + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + + /* Restore permissions so we can cleanup :) */ + p_chmod("empty_standard_repo/no_permission", 0777); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__unreadable_as_untracked(void) +{ + const char *expected_paths[] = { "no_permission/foo" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + /* Create directory with no read permission */ + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); + p_chmod("empty_standard_repo/no_permission", 0644); + + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_UNREADABLE | + GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + + /* Restore permissions so we can cleanup :) */ + p_chmod("empty_standard_repo/no_permission", 0777); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + |