@@ -460,6 +460,8 @@ struct phantom_symlink_info {
460460struct phantom_symlink_list {
461461 struct cb_node node ;
462462 struct phantom_symlink_info * list ;
463+ struct strbuf directory ;
464+ uint64_t directory_generation ;
463465 char key [FLEX_ARRAY ];
464466};
465467
@@ -468,9 +470,86 @@ static struct cb_tree phantom_symlinks = {
468470 .key_offset = offsetof(struct phantom_symlink_list , key )
469471};
470472static CRITICAL_SECTION phantom_symlinks_cs ;
473+ static uint64_t phantom_directory_generation ;
474+ static volatile LONG phantom_directories_invalid ;
471475
472476static wchar_t * normalize_ntpath (wchar_t * wbuf );
473477
478+ static void invalidate_phantom_directories (void )
479+ {
480+ InterlockedExchange (& phantom_directories_invalid , 1 );
481+ }
482+
483+ static struct phantom_symlink_list * phantom_symlink_list (const struct strbuf * key )
484+ {
485+ struct phantom_symlink_list * list ;
486+ struct cb_node * existing ;
487+
488+ FLEX_ALLOC_MEM (list , key , key -> buf , key -> len );
489+ strbuf_init (& list -> directory , 0 );
490+ existing = cb_insert (& phantom_symlinks , & list -> node , key -> len + 1 );
491+ if (existing ) {
492+ free (list );
493+ list = container_of (existing ,
494+ struct phantom_symlink_list , node );
495+ }
496+ return list ;
497+ }
498+
499+ static int full_path (struct strbuf * path , const wchar_t * name )
500+ {
501+ wchar_t full [MAX_LONG_PATH ];
502+ int len = GetFullPathNameW (name , ARRAY_SIZE (full ), full , NULL );
503+
504+ if (!len || len >= ARRAY_SIZE (full )) {
505+ errno = len ? ENAMETOOLONG : err_win_to_posix (GetLastError ());
506+ return -1 ;
507+ }
508+ strbuf_reset (path );
509+ strbuf_grow (path , 3 * len );
510+ len = xwcstoutf (path -> buf , normalize_ntpath (full ), path -> alloc );
511+ if (len < 0 )
512+ die_errno ("could not encode path '%ls'" , name );
513+ strbuf_setlen (path , len );
514+ return 0 ;
515+ }
516+
517+ static int append_cached_directory (struct cb_node * node , void * data )
518+ {
519+ struct phantom_symlink_list * list =
520+ container_of (node , struct phantom_symlink_list , node );
521+
522+ if (list -> directory_generation != phantom_directory_generation )
523+ return 0 ;
524+ strbuf_addbuf (data , & list -> directory );
525+ return 1 ;
526+ }
527+
528+ /* Cache keys preserve spelling; they cannot collide with absolute paths. */
529+ static int append_from_cached_parent (struct strbuf * resolved ,
530+ const struct strbuf * path )
531+ {
532+ struct strbuf key = STRBUF_INIT ;
533+ size_t parent_len ;
534+ int found ;
535+
536+ /* Advance generations here, not during an in-flight cache fill. */
537+ if (InterlockedExchange (& phantom_directories_invalid , 0 ))
538+ phantom_directory_generation ++ ;
539+
540+ strbuf_addbuf (& key , path );
541+ strbuf_strip_file_from_path (& key );
542+ parent_len = key .len ;
543+ strbuf_insertstr (& key , 0 , "dir:" );
544+ found = cb_each (& phantom_symlinks , (const uint8_t * )key .buf ,
545+ key .len + 1 , append_cached_directory , resolved );
546+ if (found )
547+ strbuf_add (resolved , path -> buf + parent_len ,
548+ path -> len - parent_len );
549+ strbuf_release (& key );
550+ return found ;
551+ }
552+
474553static void path_to_key (struct strbuf * path , int resolve )
475554{
476555 struct strbuf resolved = STRBUF_INIT ;
@@ -488,38 +567,64 @@ static void path_to_key(struct strbuf *path, int resolve)
488567 strbuf_release (& resolved );
489568}
490569
570+ static void cache_directory (const struct strbuf * name ,
571+ const struct strbuf * resolved )
572+ {
573+ struct strbuf key = STRBUF_INIT ;
574+ struct phantom_symlink_list * list ;
575+
576+ strbuf_addstr (& key , "dir:" );
577+ strbuf_addbuf (& key , name );
578+ strbuf_complete (& key , '/' );
579+ list = phantom_symlink_list (& key );
580+ strbuf_reset (& list -> directory );
581+ strbuf_addbuf (& list -> directory , resolved );
582+ strbuf_complete (& list -> directory , '/' );
583+ list -> directory_generation = phantom_directory_generation ;
584+ strbuf_release (& key );
585+ }
586+
491587static int remember_phantom_symlink (struct phantom_symlink_info * psi ,
492588 const wchar_t * wlink )
493589{
494- struct strbuf path = STRBUF_INIT ;
590+ struct strbuf path = STRBUF_INIT , full = STRBUF_INIT ;
495591 struct phantom_symlink_list * list ;
496- struct cb_node * existing ;
592+ struct stat st ;
497593 wchar_t relative [MAX_LONG_PATH ];
498594 char target [MAX_LONG_PATH * 3 ];
499595 const wchar_t * wtarget = make_relative_to (psi -> wtarget , wlink ,
500596 relative , ARRAY_SIZE (relative ));
597+ int ret = -1 ;
501598
502599 if (!wtarget )
503600 return -1 ;
504- if (wtarget != relative )
505- wcscpy (relative , wtarget );
506- if (xwcstoutf (target , normalize_ntpath (relative ), sizeof (target )) < 0 )
507- return error_errno ("could not encode symlink target '%ls'" ,
508- wtarget );
509- strbuf_addstr (& path , target );
510- path_to_key (& path , 1 );
511-
512- FLEX_ALLOC_MEM (list , key , path .buf , path .len );
513- existing = cb_insert (& phantom_symlinks , & list -> node , path .len + 1 );
514- if (existing ) {
515- free (list );
516- list = container_of (existing ,
517- struct phantom_symlink_list , node );
601+ if (!full_path (& full , wtarget ) && full .len &&
602+ !is_dir_sep (full .buf [full .len - 1 ]) &&
603+ append_from_cached_parent (& path , & full ) &&
604+ lstat (full .buf , & st ) < 0 && errno == ENOENT ) {
605+ path_to_key (& path , 0 );
606+ } else {
607+ if (wtarget != relative )
608+ wcscpy (relative , wtarget );
609+ if (xwcstoutf (target , normalize_ntpath (relative ),
610+ sizeof (target )) < 0 ) {
611+ error_errno ("could not encode symlink target '%ls'" ,
612+ wtarget );
613+ goto out ;
614+ }
615+ strbuf_reset (& path );
616+ strbuf_addstr (& path , target );
617+ path_to_key (& path , 1 );
518618 }
619+
620+ list = phantom_symlink_list (& path );
519621 psi -> next = list -> list ;
520622 list -> list = psi ;
623+ ret = 0 ;
624+ out :
625+ strbuf_release (& full );
521626 strbuf_release (& path );
522- return 0 ;
627+ return ret ;
523628}
524629
525630static int collect_phantom_symlinks (struct cb_node * node , void * data )
@@ -543,25 +648,31 @@ static void process_phantom_symlinks(const wchar_t *name, int recursive)
543648{
544649 struct strbuf path = STRBUF_INIT , resolved = STRBUF_INIT ;
545650 struct phantom_symlink_info * pending = NULL ;
546- wchar_t wpath [MAX_LONG_PATH ];
547- int len ;
548651
549652 EnterCriticalSection (& phantom_symlinks_cs );
550653 if (!phantom_symlinks .root )
551654 goto out ;
552- len = GetFullPathNameW (name , ARRAY_SIZE (wpath ), wpath , NULL );
553- if (!len || len >= ARRAY_SIZE (wpath )) {
554- errno = len ? ENAMETOOLONG : err_win_to_posix (GetLastError ());
655+ if (full_path (& path , name )) {
555656 error_errno ("could not get full path of '%ls'" , name );
556657 goto out ;
557658 }
558- strbuf_grow (& path , 3 * len );
559- len = xwcstoutf (path .buf , normalize_ntpath (wpath ), path .alloc );
560- if (len < 0 )
561- die_errno ("could not encode path '%ls'" , wpath );
562- strbuf_setlen (& path , len );
563- if (!recursive && mingw_strbuf_realpath (& resolved , path .buf ))
564- path_to_key (& resolved , 0 );
659+ if (!recursive ) {
660+ strbuf_strip_suffix (& path , "/" );
661+ if (!append_from_cached_parent (& resolved , & path ) &&
662+ mingw_strbuf_realpath (& resolved , path .buf ) &&
663+ !strbuf_cmp (& path , & resolved )) {
664+ struct strbuf parent = STRBUF_INIT ;
665+
666+ strbuf_addbuf (& parent , & path );
667+ strbuf_strip_file_from_path (& parent );
668+ cache_directory (& parent , & parent );
669+ strbuf_release (& parent );
670+ }
671+ if (resolved .len ) {
672+ cache_directory (& path , & resolved );
673+ path_to_key (& resolved , 0 );
674+ }
675+ }
565676 path_to_key (& path , 0 );
566677 cb_each (& phantom_symlinks , (const uint8_t * )path .buf ,
567678 path .len + !recursive , collect_phantom_symlinks , & pending );
@@ -704,6 +815,7 @@ int mingw_unlink(const char *pathname, int handle_in_use_error)
704815 if (xutftowcs_long_path (wpathname , pathname ) < 0 )
705816 return -1 ;
706817
818+ invalidate_phantom_directories ();
707819 if (use_legacy_delete < 0 )
708820 use_legacy_delete = git_env_bool ("GIT_TEST_LEGACY_DELETE" , 0 );
709821
@@ -747,6 +859,7 @@ int mingw_unlink(const char *pathname, int handle_in_use_error)
747859
748860success :
749861 ret = 0 ;
862+ invalidate_phantom_directories ();
750863 if (target .len && xutftowcs_long_path (wpathname , target .buf ) >= 0 )
751864 process_phantom_symlinks (wpathname , 1 );
752865out :
@@ -804,9 +917,11 @@ int mingw_rmdir(const char *pathname)
804917 if (xutftowcs_long_path (wpathname , pathname ) < 0 )
805918 return -1 ;
806919
920+ invalidate_phantom_directories ();
807921 do {
808922 if (!_wrmdir (wpathname )) {
809923 invalidate_lstat_cache ();
924+ invalidate_phantom_directories ();
810925 return 0 ;
811926 }
812927 if (!is_file_in_use_error (GetLastError ()))
@@ -3139,6 +3254,7 @@ int mingw_rename(const char *pold, const char *pnew)
31393254 if (wpnew_len < 0 )
31403255 return -1 ;
31413256
3257+ invalidate_phantom_directories ();
31423258repeat :
31433259 if (supports_file_rename_info_ex ) {
31443260 /*
@@ -3191,8 +3307,10 @@ int mingw_rename(const char *pold, const char *pnew)
31913307 & rename_info , sizeof (rename_info ));
31923308 gle = GetLastError ();
31933309 CloseHandle (old_handle );
3194- if (success )
3310+ if (success ) {
3311+ invalidate_phantom_directories ();
31953312 return 0 ;
3313+ }
31963314
31973315 /*
31983316 * When we see ERROR_INVALID_PARAMETER we can assume that the
@@ -3215,8 +3333,10 @@ int mingw_rename(const char *pold, const char *pnew)
32153333 */
32163334 } else {
32173335 if (MoveFileExW (wpold , wpnew ,
3218- MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED ))
3336+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED )) {
3337+ invalidate_phantom_directories ();
32193338 return 0 ;
3339+ }
32203340 gle = GetLastError ();
32213341 }
32223342
@@ -3232,6 +3352,7 @@ int mingw_rename(const char *pold, const char *pnew)
32323352 if (MoveFileExW (wpold , wpnew ,
32333353 MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED )) {
32343354 SetFileAttributesW (wpnew , attrsold );
3355+ invalidate_phantom_directories ();
32353356 return 0 ;
32363357 }
32373358 gle = GetLastError ();
@@ -3256,8 +3377,10 @@ int mingw_rename(const char *pold, const char *pnew)
32563377 if (attrsold == INVALID_FILE_ATTRIBUTES ||
32573378 !(attrsold & FILE_ATTRIBUTE_DIRECTORY ))
32583379 errno = EISDIR ;
3259- else if (!_wrmdir (wpnew ))
3380+ else if (!_wrmdir (wpnew )) {
3381+ invalidate_phantom_directories ();
32603382 goto repeat ;
3383+ }
32613384 return -1 ;
32623385 }
32633386 if ((attrs & FILE_ATTRIBUTE_READONLY ) &&
0 commit comments