Commit 4b136a94 by Russell Belfer

Fix crash in new status and add recurse option

This fixes the bug that @nulltoken found (thank you!) where
if there were untracked directories alphabetically after the
last tracked item, the diff implementation would deref a NULL
pointer.

The fix involved the code which decides if it is necessary
to recurse into a directory in the working dir, so it was
easy to add a new option `GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS`
to control if the contents of untracked directories should be
included in status.
parent 98c4613e
......@@ -40,7 +40,8 @@ enum {
GIT_DIFF_PATIENCE = (1 << 6),
GIT_DIFF_INCLUDE_IGNORED = (1 << 7),
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9)
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
};
/**
......
......@@ -87,19 +87,27 @@ typedef enum {
* should be made even on unmodified files.
* - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories
* which appear to be submodules should just be skipped over.
* - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the
* contents of untracked directories should be included in the
* status. Normally if an entire directory is new, then just
* the top-level directory will be included (with a trailing
* slash on the entry name). Given this flag, the directory
* itself will not be included, but all the files in it will.
*/
#define GIT_STATUS_OPT_INCLUDE_UNTRACKED (1 << 0)
#define GIT_STATUS_OPT_INCLUDE_IGNORED (1 << 1)
#define GIT_STATUS_OPT_INCLUDE_UNMODIFIED (1 << 2)
#define GIT_STATUS_OPT_EXCLUDE_SUBMODULES (1 << 3)
#define GIT_STATUS_OPT_INCLUDE_UNTRACKED (1 << 0)
#define GIT_STATUS_OPT_INCLUDE_IGNORED (1 << 1)
#define GIT_STATUS_OPT_INCLUDE_UNMODIFIED (1 << 2)
#define GIT_STATUS_OPT_EXCLUDE_SUBMODULES (1 << 3)
#define GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS (1 << 4)
/**
* Options to control which callbacks will be made by
* `git_status_foreach_ext()`
* Options to control how callbacks will be made by
* `git_status_foreach_ext()`.
*/
typedef struct {
git_status_show_t show;
unsigned int flags;
git_strarray pathspec;
} git_status_options;
/**
......
......@@ -439,7 +439,12 @@ static int diff_from_iterators(
is_ignored = git_iterator_current_is_ignored(new);
if (S_ISDIR(nitem->mode)) {
if (git__prefixcmp(oitem->path, nitem->path) == 0) {
/* recurse into directory if explicitly requested or
* if there are tracked items inside the directory
*/
if ((diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) ||
(oitem && git__prefixcmp(oitem->path, nitem->path) == 0))
{
if (is_ignored)
ignore_prefix = nitem->path;
if (git_iterator_advance_into_directory(new, &nitem) < 0)
......
......@@ -137,12 +137,16 @@ int git_status_foreach_ext(
}
memset(&diffopt, 0, sizeof(diffopt));
memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
/* TODO: support EXCLUDE_SUBMODULES flag */
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
......
......@@ -143,3 +143,60 @@ static const unsigned int entry_statuses3[] = {
};
static const size_t entry_count3 = 21;
/* entries for a copy of tests/resources/status with some mods
* and different options to the status call
*/
static const char *entry_paths4[] = {
".new_file",
"current_file",
"current_file/current_file",
"current_file/modified_file",
"current_file/new_file",
"file_deleted",
"modified_file",
"new_file",
"staged_changes",
"staged_changes_file_deleted",
"staged_changes_modified_file",
"staged_delete_file_deleted",
"staged_delete_modified_file",
"staged_new_file",
"staged_new_file_deleted_file",
"staged_new_file_modified_file",
"subdir",
"subdir/current_file",
"subdir/deleted_file",
"subdir/modified_file",
"zzz_new_dir/new_file",
"zzz_new_file"
};
static const unsigned int entry_statuses4[] = {
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
};
static const size_t entry_count4 = 22;
......@@ -174,6 +174,41 @@ void test_status_worktree__swap_subdir_and_file(void)
}
void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
{
struct status_entry_counts counts;
git_repository *repo = cl_git_sandbox_init("status");
git_status_options opts;
/* first alter the contents of the worktree */
cl_git_pass(p_rename("status/current_file", "status/swap"));
cl_git_pass(p_rename("status/subdir", "status/current_file"));
cl_git_pass(p_rename("status/swap", "status/subdir"));
cl_git_mkfile("status/.new_file", "dummy");
cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777));
cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
cl_git_mkfile("status/zzz_new_file", "dummy");
/* now get status */
memset(&counts, 0x0, sizeof(struct status_entry_counts));
counts.expected_entry_count = entry_count4;
counts.expected_paths = entry_paths4;
counts.expected_statuses = entry_statuses4;
memset(&opts, 0, sizeof(opts));
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
/* TODO: set pathspec to "current_file" eventually */
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
);
cl_assert(counts.entry_count == counts.expected_entry_count);
cl_assert(counts.wrong_status_flags_count == 0);
cl_assert(counts.wrong_sorted_path == 0);
}
/* this test is equivalent to t18-status.c:singlestatus0 */
void test_status_worktree__single_file(void)
{
......
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