*/
enum path_treatment state;
int matches_how = 0;
- int nested_repo = 0, check_only, stop_early;
+ int check_only, stop_early;
int old_ignored_nr, old_untracked_nr;
/* The "len-1" is to strip the final '/' */
enum exist_status status = directory_exists_in_index(istate, dirname, len-1);
if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
!(dir->flags & DIR_NO_GITLINKS)) {
+ /*
+ * Determine if `dirname` is a nested repo by confirming that:
+ * 1) we are in a nonbare repository, and
+ * 2) `dirname` is not an immediate parent of `the_repository->gitdir`,
+ * which could occur if the git_dir or worktree location was
+ * manually configured by the user; see t2205 testcases 1-3 for
+ * examples where this matters
+ */
+ int nested_repo;
struct strbuf sb = STRBUF_INIT;
strbuf_addstr(&sb, dirname);
nested_repo = is_nonbare_repository_dir(&sb);
+
+ if (nested_repo) {
+ char *real_dirname, *real_gitdir;
+ strbuf_addstr(&sb, ".git");
+ real_dirname = real_pathdup(sb.buf, 1);
+ real_gitdir = real_pathdup(the_repository->gitdir, 1);
+
+ nested_repo = !!strcmp(real_dirname, real_gitdir);
+ free(real_gitdir);
+ free(real_dirname);
+ }
strbuf_release(&sb);
- }
- if (nested_repo) {
- if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
- (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC))
- return path_none;
- return excluded ? path_excluded : path_untracked;
+
+ if (nested_repo) {
+ if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
+ (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC))
+ return path_none;
+ return excluded ? path_excluded : path_untracked;
+ }
}
if (!(dir->flags & DIR_SHOW_OTHER_DIRECTORIES)) {
strbuf_addch(&uc->ident, 0);
}
-static void new_untracked_cache(struct index_state *istate)
+static unsigned new_untracked_cache_flags(struct index_state *istate)
+{
+ struct repository *repo = istate->repo;
+ char *val;
+
+ /*
+ * This logic is coordinated with the setting of these flags in
+ * wt-status.c#wt_status_collect_untracked(), and the evaluation
+ * of the config setting in commit.c#git_status_config()
+ */
+ if (!repo_config_get_string(repo, "status.showuntrackedfiles", &val) &&
+ !strcmp(val, "all"))
+ return 0;
+
+ /*
+ * The default, if "all" is not set, is "normal" - leading us here.
+ * If the value is "none" then it really doesn't matter.
+ */
+ return DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+}
+
+static void new_untracked_cache(struct index_state *istate, int flags)
{
struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
strbuf_init(&uc->ident, 100);
uc->exclude_per_dir = ".gitignore";
- /* should be the same flags used by git-status */
- uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+ uc->dir_flags = flags >= 0 ? flags : new_untracked_cache_flags(istate);
set_untracked_ident(uc);
istate->untracked = uc;
istate->cache_changed |= UNTRACKED_CHANGED;
void add_untracked_cache(struct index_state *istate)
{
if (!istate->untracked) {
- new_untracked_cache(istate);
+ new_untracked_cache(istate, -1);
} else {
if (!ident_in_untracked(istate->untracked)) {
free_untracked_cache(istate->untracked);
- new_untracked_cache(istate);
+ new_untracked_cache(istate, -1);
}
}
}
if (base_len || (pathspec && pathspec->nr))
return NULL;
- /* Different set of flags may produce different results */
- if (dir->flags != dir->untracked->dir_flags ||
- /*
- * See treat_directory(), case index_nonexistent. Without
- * this flag, we may need to also cache .git file content
- * for the resolve_gitlink_ref() call, which we don't.
- */
- !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
- /* We don't support collecting ignore files */
- (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
- DIR_COLLECT_IGNORED)))
+ /* We don't support collecting ignore files */
+ if (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
+ DIR_COLLECT_IGNORED))
return NULL;
/*
return NULL;
}
+ /*
+ * If the untracked structure we received does not have the same flags
+ * as requested in this run, we're going to need to either discard the
+ * existing structure (and potentially later recreate), or bypass the
+ * untracked cache mechanism for this run.
+ */
+ if (dir->flags != dir->untracked->dir_flags) {
+ /*
+ * If the untracked structure we received does not have the same flags
+ * as configured, then we need to reset / create a new "untracked"
+ * structure to match the new config.
+ *
+ * Keeping the saved and used untracked cache consistent with the
+ * configuration provides an opportunity for frequent users of
+ * "git status -uall" to leverage the untracked cache by aligning their
+ * configuration - setting "status.showuntrackedfiles" to "all" or
+ * "normal" as appropriate.
+ *
+ * Previously using -uall (or setting "status.showuntrackedfiles" to
+ * "all") was incompatible with untracked cache and *consistently*
+ * caused surprisingly bad performance (with fscache and fsmonitor
+ * enabled) on Windows.
+ *
+ * IMPROVEMENT OPPORTUNITY: If we reworked the untracked cache storage
+ * to not be as bound up with the desired output in a given run,
+ * and instead iterated through and stored enough information to
+ * correctly serve both "modes", then users could get peak performance
+ * with or without '-uall' regardless of their
+ * "status.showuntrackedfiles" config.
+ */
+ if (dir->untracked->dir_flags != new_untracked_cache_flags(istate)) {
+ free_untracked_cache(istate->untracked);
+ new_untracked_cache(istate, dir->flags);
+ dir->untracked = istate->untracked;
+ }
+ else {
+ /*
+ * Current untracked cache data is consistent with config, but not
+ * usable in this request/run; just bypass untracked cache.
+ */
+ return NULL;
+ }
+ }
+
if (!dir->untracked->root) {
/* Untracked cache existed but is not initialized; fix that */
FLEX_ALLOC_STR(dir->untracked->root, name, "");
* Skip scheme.
*/
start = strstr(repo, "://");
- if (start == NULL)
+ if (!start)
start = repo;
else
start += 3;
end--;
}
+ /*
+ * It should not be possible to overflow `ptrdiff_t` by passing in an
+ * insanely long URL, but GCC does not know that and will complain
+ * without this check.
+ */
+ if (end - start < 0)
+ die(_("No directory name could be guessed.\n"
+ "Please specify a directory on the command line"));
+
/*
* Strip trailing port number if we've got only a
* hostname (that is, there is no dir separator but a
connect_work_tree_and_git_dir(path, new_git_dir, 0);
}
+
+int path_match_flags(const char *const str, const enum path_match_flags flags)
+{
+ const char *p = str;
+
+ if (flags & PATH_MATCH_NATIVE &&
+ flags & PATH_MATCH_XPLATFORM)
+ BUG("path_match_flags() must get one match kind, not multiple!");
+ else if (!(flags & PATH_MATCH_KINDS_MASK))
+ BUG("path_match_flags() must get at least one match kind!");
+
+ if (flags & PATH_MATCH_STARTS_WITH_DOT_SLASH &&
+ flags & PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH)
+ BUG("path_match_flags() must get one platform kind, not multiple!");
+ else if (!(flags & PATH_MATCH_PLATFORM_MASK))
+ BUG("path_match_flags() must get at least one platform kind!");
+
+ if (*p++ != '.')
+ return 0;
+ if (flags & PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH &&
+ *p++ != '.')
+ return 0;
+
+ if (flags & PATH_MATCH_NATIVE)
+ return is_dir_sep(*p);
+ else if (flags & PATH_MATCH_XPLATFORM)
+ return is_xplatform_dir_sep(*p);
+ BUG("unreachable");
+}