]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'gg/worktree-from-the-above' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Jul 2022 20:00:29 +0000 (13:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Jul 2022 20:00:29 +0000 (13:00 -0700)
In a non-bare repository, the behavior of Git when the
core.worktree configuration variable points at a directory that has
a repository as its subdirectory, regressed in Git 2.27 days.
source: <20220616234433.225-1-gg.oss@outlook.com>
source: <20220616231956.154-1-gg.oss@outlook.com>

* gg/worktree-from-the-above:
  dir: minor refactoring / clean-up
  dir: traverse into repository

1  2 
dir.c

diff --combined dir.c
index 6ca2ef5f04e1f551842a9bad06dd771e56b3ebd3,4166d1b16a90e23017820c8caac0bbf2a24b0245..d7cfb08e441825f421f91a982063e00be4168c71
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -1861,7 -1861,7 +1861,7 @@@ static enum path_treatment treat_direct
         */
        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)) {
@@@ -2747,33 -2768,13 +2768,33 @@@ static void set_untracked_ident(struct 
        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);
                }
        }
  }
@@@ -2834,9 -2835,17 +2855,9 @@@ static struct untracked_cache_dir *vali
        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, "");
@@@ -3110,7 -3075,7 +3131,7 @@@ char *git_url_basename(const char *repo
         * 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
@@@ -3955,32 -3911,3 +3976,32 @@@ void relocate_gitdir(const char *path, 
  
        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");
 +}