@@ -371,12 +371,19 @@ static int match_pathspec_item(const struct index_state *istate,
371371 !ps_strncmp (item , match , name , namelen ))
372372 return MATCHED_RECURSIVELY_LEADING_PATHSPEC ;
373373
374- /* name" doesn't match up to the first wild character */
374+ /* name doesn't match up to the first wild character */
375375 if (item -> nowildcard_len < item -> len &&
376376 ps_strncmp (item , match , name ,
377377 item -> nowildcard_len - prefix ))
378378 return 0 ;
379379
380+ /*
381+ * name has no wildcard, and it didn't match as a leading
382+ * pathspec so return.
383+ */
384+ if (item -> nowildcard_len == item -> len )
385+ return 0 ;
386+
380387 /*
381388 * Here is where we would perform a wildmatch to check if
382389 * "name" can be matched as a directory (or a prefix) against
@@ -1652,6 +1659,8 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
16521659 const char * dirname , int len , int baselen , int exclude ,
16531660 const struct pathspec * pathspec )
16541661{
1662+ int nested_repo = 0 ;
1663+
16551664 /* The "len-1" is to strip the final '/' */
16561665 switch (directory_exists_in_index (istate , dirname , len - 1 )) {
16571666 case index_directory :
@@ -1661,15 +1670,16 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
16611670 return path_none ;
16621671
16631672 case index_nonexistent :
1664- if (dir -> flags & DIR_SKIP_NESTED_GIT ) {
1665- int nested_repo ;
1673+ if (( dir -> flags & DIR_SKIP_NESTED_GIT ) ||
1674+ !( dir -> flags & DIR_NO_GITLINKS )) {
16661675 struct strbuf sb = STRBUF_INIT ;
16671676 strbuf_addstr (& sb , dirname );
16681677 nested_repo = is_nonbare_repository_dir (& sb );
16691678 strbuf_release (& sb );
1670- if (nested_repo )
1671- return path_none ;
16721679 }
1680+ if (nested_repo )
1681+ return ((dir -> flags & DIR_SKIP_NESTED_GIT ) ? path_none :
1682+ (exclude ? path_excluded : path_untracked ));
16731683
16741684 if (dir -> flags & DIR_SHOW_OTHER_DIRECTORIES )
16751685 break ;
@@ -1697,13 +1707,6 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
16971707
16981708 return path_none ;
16991709 }
1700- if (!(dir -> flags & DIR_NO_GITLINKS )) {
1701- struct strbuf sb = STRBUF_INIT ;
1702- strbuf_addstr (& sb , dirname );
1703- if (is_nonbare_repository_dir (& sb ))
1704- return exclude ? path_excluded : path_untracked ;
1705- strbuf_release (& sb );
1706- }
17071710 return path_recurse ;
17081711 }
17091712
@@ -2123,6 +2126,40 @@ static void close_cached_dir(struct cached_dir *cdir)
21232126 }
21242127}
21252128
2129+ static void add_path_to_appropriate_result_list (struct dir_struct * dir ,
2130+ struct untracked_cache_dir * untracked ,
2131+ struct cached_dir * cdir ,
2132+ struct index_state * istate ,
2133+ struct strbuf * path ,
2134+ int baselen ,
2135+ const struct pathspec * pathspec ,
2136+ enum path_treatment state )
2137+ {
2138+ /* add the path to the appropriate result list */
2139+ switch (state ) {
2140+ case path_excluded :
2141+ if (dir -> flags & DIR_SHOW_IGNORED )
2142+ dir_add_name (dir , istate , path -> buf , path -> len );
2143+ else if ((dir -> flags & DIR_SHOW_IGNORED_TOO ) ||
2144+ ((dir -> flags & DIR_COLLECT_IGNORED ) &&
2145+ exclude_matches_pathspec (path -> buf , path -> len ,
2146+ pathspec )))
2147+ dir_add_ignored (dir , istate , path -> buf , path -> len );
2148+ break ;
2149+
2150+ case path_untracked :
2151+ if (dir -> flags & DIR_SHOW_IGNORED )
2152+ break ;
2153+ dir_add_name (dir , istate , path -> buf , path -> len );
2154+ if (cdir -> fdir )
2155+ add_untracked (untracked , path -> buf + baselen );
2156+ break ;
2157+
2158+ default :
2159+ break ;
2160+ }
2161+ }
2162+
21262163/*
21272164 * Read a directory tree. We currently ignore anything but
21282165 * directories, regular files and symlinks. That's because git
@@ -2147,6 +2184,15 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
21472184 struct untracked_cache_dir * untracked , int check_only ,
21482185 int stop_at_first_file , const struct pathspec * pathspec )
21492186{
2187+ /*
2188+ * WARNING WARNING WARNING:
2189+ *
2190+ * Any updates to the traversal logic here may need corresponding
2191+ * updates in treat_leading_path(). See the commit message for the
2192+ * commit adding this warning as well as the commit preceding it
2193+ * for details.
2194+ */
2195+
21502196 struct cached_dir cdir ;
21512197 enum path_treatment state , subdir_state , dir_state = path_none ;
21522198 struct strbuf path = STRBUF_INIT ;
@@ -2226,29 +2272,9 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
22262272 continue ;
22272273 }
22282274
2229- /* add the path to the appropriate result list */
2230- switch (state ) {
2231- case path_excluded :
2232- if (dir -> flags & DIR_SHOW_IGNORED )
2233- dir_add_name (dir , istate , path .buf , path .len );
2234- else if ((dir -> flags & DIR_SHOW_IGNORED_TOO ) ||
2235- ((dir -> flags & DIR_COLLECT_IGNORED ) &&
2236- exclude_matches_pathspec (path .buf , path .len ,
2237- pathspec )))
2238- dir_add_ignored (dir , istate , path .buf , path .len );
2239- break ;
2240-
2241- case path_untracked :
2242- if (dir -> flags & DIR_SHOW_IGNORED )
2243- break ;
2244- dir_add_name (dir , istate , path .buf , path .len );
2245- if (cdir .fdir )
2246- add_untracked (untracked , path .buf + baselen );
2247- break ;
2248-
2249- default :
2250- break ;
2251- }
2275+ add_path_to_appropriate_result_list (dir , untracked , & cdir ,
2276+ istate , & path , baselen ,
2277+ pathspec , state );
22522278 }
22532279 close_cached_dir (& cdir );
22542280 out :
@@ -2278,41 +2304,104 @@ static int treat_leading_path(struct dir_struct *dir,
22782304 const char * path , int len ,
22792305 const struct pathspec * pathspec )
22802306{
2307+ /*
2308+ * WARNING WARNING WARNING:
2309+ *
2310+ * Any updates to the traversal logic here may need corresponding
2311+ * updates in treat_leading_path(). See the commit message for the
2312+ * commit adding this warning as well as the commit preceding it
2313+ * for details.
2314+ */
2315+
22812316 struct strbuf sb = STRBUF_INIT ;
2282- int baselen , rc = 0 ;
2317+ int prevlen , baselen ;
22832318 const char * cp ;
2284- int old_flags = dir -> flags ;
2319+ struct cached_dir cdir ;
2320+ struct dirent * de ;
2321+ enum path_treatment state = path_none ;
2322+
2323+ /*
2324+ * For each directory component of path, we are going to check whether
2325+ * that path is relevant given the pathspec. For example, if path is
2326+ * foo/bar/baz/
2327+ * then we will ask treat_path() whether we should go into foo, then
2328+ * whether we should go into bar, then whether baz is relevant.
2329+ * Checking each is important because e.g. if path is
2330+ * .git/info/
2331+ * then we need to check .git to know we shouldn't traverse it.
2332+ * If the return from treat_path() is:
2333+ * * path_none, for any path, we return false.
2334+ * * path_recurse, for all path components, we return true
2335+ * * <anything else> for some intermediate component, we make sure
2336+ * to add that path to the relevant list but return false
2337+ * signifying that we shouldn't recurse into it.
2338+ */
22852339
22862340 while (len && path [len - 1 ] == '/' )
22872341 len -- ;
22882342 if (!len )
22892343 return 1 ;
2344+
2345+ /*
2346+ * We need a manufactured dirent with sufficient space to store a
2347+ * leading directory component of path in its d_name. Here, we
2348+ * assume that the dirent's d_name is either declared as
2349+ * char d_name[BIG_ENOUGH]
2350+ * or that it is declared at the end of the struct as
2351+ * char d_name[]
2352+ * For either case, padding with len+1 bytes at the end will ensure
2353+ * sufficient storage space.
2354+ */
2355+ de = xcalloc (1 , st_add3 (sizeof (struct dirent ), len , 1 ));
2356+ memset (& cdir , 0 , sizeof (cdir ));
2357+ cdir .de = de ;
2358+ #if defined(DT_UNKNOWN ) && !defined(NO_D_TYPE_IN_DIRENT )
2359+ de -> d_type = DT_DIR ;
2360+ #endif
22902361 baselen = 0 ;
2291- dir -> flags &= ~ DIR_SHOW_OTHER_DIRECTORIES ;
2362+ prevlen = 0 ;
22922363 while (1 ) {
2293- cp = path + baselen + !!baselen ;
2364+ prevlen = baselen + !!baselen ;
2365+ cp = path + prevlen ;
22942366 cp = memchr (cp , '/' , path + len - cp );
22952367 if (!cp )
22962368 baselen = len ;
22972369 else
22982370 baselen = cp - path ;
2299- strbuf_setlen (& sb , 0 );
2371+ strbuf_reset (& sb );
23002372 strbuf_add (& sb , path , baselen );
23012373 if (!is_directory (sb .buf ))
23022374 break ;
2303- if (simplify_away (sb .buf , sb .len , pathspec ))
2304- break ;
2305- if (treat_one_path (dir , NULL , istate , & sb , baselen , pathspec ,
2306- DT_DIR , NULL ) == path_none )
2375+ strbuf_reset (& sb );
2376+ strbuf_add (& sb , path , prevlen );
2377+ memcpy (de -> d_name , path + prevlen , baselen - prevlen );
2378+ de -> d_name [baselen - prevlen ] = '\0' ;
2379+ state = treat_path (dir , NULL , & cdir , istate , & sb , prevlen ,
2380+ pathspec );
2381+ if (state == path_untracked &&
2382+ get_dtype (cdir .de , istate , sb .buf , sb .len ) == DT_DIR &&
2383+ (dir -> flags & DIR_SHOW_IGNORED_TOO ||
2384+ do_match_pathspec (istate , pathspec , sb .buf , sb .len ,
2385+ baselen , NULL , DO_MATCH_LEADING_PATHSPEC ) == MATCHED_RECURSIVELY_LEADING_PATHSPEC )) {
2386+ add_path_to_appropriate_result_list (dir , NULL , & cdir ,
2387+ istate ,
2388+ & sb , baselen ,
2389+ pathspec , state );
2390+ state = path_recurse ;
2391+ }
2392+
2393+ if (state != path_recurse )
23072394 break ; /* do not recurse into it */
2308- if (len <= baselen ) {
2309- rc = 1 ;
2395+ if (len <= baselen )
23102396 break ; /* finished checking */
2311- }
23122397 }
2398+ add_path_to_appropriate_result_list (dir , NULL , & cdir , istate ,
2399+ & sb , baselen , pathspec ,
2400+ state );
2401+
2402+ free (de );
23132403 strbuf_release (& sb );
2314- dir -> flags = old_flags ;
2315- return rc ;
2404+ return state == path_recurse ;
23162405}
23172406
23182407static const char * get_ident_string (void )
0 commit comments