@@ -139,6 +139,55 @@ struct ArchiverTests {
139139 #expect( try String ( contentsOf: extractedLinkURL, encoding: . utf8) == " hello " )
140140 }
141141
142+ @Test
143+ func testCompressAndUncompressRewritesArchivedAbsoluteSymbolicLinkTargetThroughSymlinkedAncestor( ) throws {
144+ let fileManager = FileManager . default
145+ let tempURL = try fileManager. url (
146+ for: . itemReplacementDirectory,
147+ in: . userDomainMask,
148+ appropriateFor: . temporaryDirectory,
149+ create: true
150+ )
151+ defer { try ? fileManager. removeItem ( at: tempURL) }
152+
153+ let sourceURL = tempURL. appendingPathComponent ( " source " )
154+ let archiveURL = tempURL. appendingPathComponent ( " archive.tar.gz " )
155+ let destinationURL = tempURL. appendingPathComponent ( " destination " )
156+ try fileManager. createDirectory ( at: sourceURL, withIntermediateDirectories: true )
157+
158+ let realDirectoryURL = sourceURL. appendingPathComponent ( " real " )
159+ try fileManager. createDirectory ( at: realDirectoryURL, withIntermediateDirectories: true )
160+ let targetURL = realDirectoryURL. appendingPathComponent ( " target.txt " )
161+ try #require( " hello " . data ( using: . utf8) ) . write ( to: targetURL)
162+
163+ let aliasURL = sourceURL. appendingPathComponent ( " alias " )
164+ try fileManager. createSymbolicLink ( atPath: aliasURL. path, withDestinationPath: " real " )
165+
166+ let linkURL = sourceURL. appendingPathComponent ( " link.txt " )
167+ try fileManager. createSymbolicLink (
168+ atPath: linkURL. path,
169+ withDestinationPath: sourceURL. appendingPathComponent ( " alias/target.txt " ) . path
170+ )
171+
172+ _ = try Archiver . compress ( source: sourceURL, destination: archiveURL) { url in
173+ let sourcePath = sourceURL. standardizedFileURL. path
174+ let path = url. standardizedFileURL. path
175+ let relativePath = String ( path. dropFirst ( sourcePath. count + 1 ) )
176+ return Archiver . ArchiveEntryInfo (
177+ pathOnHost: url,
178+ pathInArchive: URL ( fileURLWithPath: relativePath)
179+ )
180+ }
181+
182+ try Archiver . uncompress ( source: archiveURL, destination: destinationURL)
183+
184+ let extractedLinkURL = destinationURL. appendingPathComponent ( " link.txt " )
185+ let values = try extractedLinkURL. resourceValues ( forKeys: [ . isSymbolicLinkKey] )
186+ #expect( values. isSymbolicLink == true )
187+ #expect( try fileManager. destinationOfSymbolicLink ( atPath: extractedLinkURL. path) == " real/target.txt " )
188+ #expect( try String ( contentsOf: extractedLinkURL, encoding: . utf8) == " hello " )
189+ }
190+
142191 @Test
143192 func testCompressDigestChangesWhenSymlinkTargetChanges( ) throws {
144193 let fileManager = FileManager . default
0 commit comments