@@ -130,6 +130,29 @@ impl TagStorage for FsRepository {
130130 }
131131}
132132
133+ impl FsRepository {
134+ /// Forcefully remove any lock file for the identified tag.
135+ ///
136+ /// # Safety
137+ /// This function is unsafe because it removes the lock file without
138+ /// ensuring that the tag file is not being written to, which can cause
139+ /// corruption in the tag file.
140+ pub async unsafe fn unlock_tag_in_namespace (
141+ & self ,
142+ namespace : Option < & TagNamespace > ,
143+ tag : tracking:: TagSpec ,
144+ ) -> Result < ( ) > {
145+ // Safety: we do not ensure the tag file is not being written to
146+ // but pass the responsibility to the caller.
147+ unsafe {
148+ self . opened ( )
149+ . await ?
150+ . unlock_tag_in_namespace ( namespace, tag)
151+ . await
152+ }
153+ }
154+ }
155+
133156impl OpenFsRepository {
134157 fn tags_root_in_namespace ( & self , namespace : Option < & TagNamespace > ) -> PathBuf {
135158 let mut tags_root = self . root ( ) . join ( "tags" ) ;
@@ -148,6 +171,23 @@ impl OpenFsRepository {
148171 }
149172 tags_root
150173 }
174+
175+ /// Forcefully remove any lock file for the identified tag.
176+ ///
177+ /// # Safety
178+ /// This function is unsafe because it removes the lock file without
179+ /// ensuring that the tag file is not being written to, which can cause
180+ /// corruption in the tag file.
181+ pub async unsafe fn unlock_tag_in_namespace (
182+ & self ,
183+ namespace : Option < & TagNamespace > ,
184+ tag : tracking:: TagSpec ,
185+ ) -> Result < ( ) > {
186+ let path = tag. to_path ( self . tags_root_in_namespace ( namespace) ) ;
187+ // Safety: we do not ensure the tag file is not being written to
188+ // but pass the responsibility to the caller.
189+ unsafe { TagLock :: remove ( path) }
190+ }
151191}
152192
153193#[ async_trait:: async_trait]
@@ -849,9 +889,11 @@ impl TagExt for tracking::TagSpec {
849889struct TagLock ( PathBuf ) ;
850890
851891impl TagLock {
892+ const LOCK_EXT : & ' static str = "tag.lock" ;
893+
852894 pub async fn new < P : AsRef < Path > > ( tag_file : P ) -> Result < TagLock > {
853895 let mut lock_file = tag_file. as_ref ( ) . to_path_buf ( ) ;
854- lock_file. set_extension ( "tag.lock" ) ;
896+ lock_file. set_extension ( Self :: LOCK_EXT ) ;
855897
856898 let timeout = std:: time:: Instant :: now ( ) + std:: time:: Duration :: from_secs ( 5 ) ;
857899 loop {
@@ -883,6 +925,23 @@ impl TagLock {
883925 }
884926 }
885927 }
928+
929+ /// Remove the lock file for a tag
930+ ///
931+ /// # Safety:
932+ /// Tag locks are used to ensure that only one process is writing to a tag file at a time.
933+ /// Removing the lock file without ensuring that the tag file is not being written to may
934+ /// cause the data within the file to become corrupt.
935+ pub unsafe fn remove < P : AsRef < Path > > ( tag_file : P ) -> Result < ( ) > {
936+ let mut lock_file = tag_file. as_ref ( ) . to_path_buf ( ) ;
937+ lock_file. set_extension ( Self :: LOCK_EXT ) ;
938+ if let Err ( err) = std:: fs:: remove_file ( & lock_file) {
939+ if err. kind ( ) != std:: io:: ErrorKind :: NotFound {
940+ return Err ( Error :: StorageWriteError ( "unlock tag" , lock_file, err) ) ;
941+ }
942+ }
943+ Ok ( ( ) )
944+ }
886945}
887946
888947impl Drop for TagLock {
0 commit comments