@@ -901,30 +901,68 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file)
901901static int packed_remove_loose (refdb_fs_backend * backend )
902902{
903903 size_t i ;
904- git_buf full_path = GIT_BUF_INIT ;
905- int failed = 0 ;
904+ git_buf ref_content = GIT_BUF_INIT ;
905+ int failed = 0 , error = 0 ;
906906
907907 /* backend->refcache is already locked when this is called */
908908
909909 for (i = 0 ; i < git_sortedcache_entrycount (backend -> refcache ); ++ i ) {
910910 struct packref * ref = git_sortedcache_entry (backend -> refcache , i );
911+ git_filebuf lock = GIT_FILEBUF_INIT ;
912+ git_oid current_id ;
911913
912914 if (!ref || !(ref -> flags & PACKREF_WAS_LOOSE ))
913915 continue ;
914916
915- if (git_buf_joinpath (& full_path , backend -> path , ref -> name ) < 0 )
916- return -1 ; /* critical; do not try to recover on oom */
917+ /* We need to stop anybody from updating the ref while we try to do a safe delete */
918+ error = loose_lock (& lock , backend , ref -> name );
919+ /* If someone else is updating it, let them do it */
920+ if (error == GIT_EEXISTS )
921+ continue ;
922+
923+ if (error < 0 ) {
924+ failed = 1 ;
925+ continue ;
926+ }
927+
928+ error = git_futils_readbuffer (& ref_content , lock .path_original );
929+ /* Someone else beat us to cleaning up the ref, let's simply continue */
930+ if (error == GIT_ENOTFOUND ) {
931+ git_filebuf_cleanup (& lock );
932+ continue ;
933+ }
934+
935+ /* This became a symref between us packing and trying to delete it, so ignore it */
936+ if (!git__prefixcmp (ref_content .ptr , GIT_SYMREF )) {
937+ git_filebuf_cleanup (& lock );
938+ continue ;
939+ }
940+
941+ /* Figure out the current id; if we fail record it but don't fail the whole operation */
942+ if ((error = loose_parse_oid (& current_id , lock .path_original , & ref_content )) < 0 ) {
943+ failed = 1 ;
944+ git_filebuf_cleanup (& lock );
945+ continue ;
946+ }
917947
918- if (git_path_exists (full_path .ptr ) && p_unlink (full_path .ptr ) < 0 ) {
948+ /* If the ref moved since we packed it, we must not delete it */
949+ if (!git_oid_equal (& current_id , & ref -> oid )) {
950+ git_filebuf_cleanup (& lock );
951+ continue ;
952+ }
953+
954+ if (p_unlink (lock .path_original ) < 0 ) {
919955 if (failed )
920956 continue ;
921957
922958 giterr_set (GITERR_REFERENCE ,
923959 "Failed to remove loose reference '%s' after packing: %s" ,
924- full_path . ptr , strerror (errno ));
960+ lock . path_original , strerror (errno ));
925961 failed = 1 ;
926962 }
927963
964+ git_filebuf_cleanup (& lock );
965+
928966 /*
929967 * if we fail to remove a single file, this is *not* good,
930968 * but we should keep going and remove as many as possible.
@@ -933,7 +971,6 @@ static int packed_remove_loose(refdb_fs_backend *backend)
933971 */
934972 }
935973
936- git_buf_free (& full_path );
937974 return failed ? -1 : 0 ;
938975}
939976
0 commit comments