Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions Sources/ContainerBuild/BuildFSSync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ actor BuildFSSync: BuildPipelineHandler {
func read(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: BuildTransfer, _ buildID: String) async throws {
let offset: UInt64 = packet.offset() ?? 0
let size: Int = packet.len() ?? 0
var path: URL = URL(filePath: packet.source.cleanPathComponent)
var path: URL
if packet.source.hasPrefix("/") {
path = URL(fileURLWithPath: packet.source).standardizedFileURL
} else {
path =
contextDir
.appendingPathComponent(packet.source)
.standardizedFileURL
}
if !FileManager.default.fileExists(atPath: path.cleanPath) {
path = URL(filePath: self.contextDir.cleanPath)
path.append(components: packet.source.cleanPathComponent)
Expand All @@ -87,8 +95,15 @@ actor BuildFSSync: BuildPipelineHandler {
}

func info(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: BuildTransfer, _ buildID: String) async throws {
var path = self.contextDir
path.append(components: packet.source.cleanPathComponent)
let path: URL
if packet.source.hasPrefix("/") {
path = URL(fileURLWithPath: packet.source).standardizedFileURL
} else {
path =
contextDir
.appendingPathComponent(packet.source)
.standardizedFileURL
}
let transfer = try path.buildTransfer(id: packet.id, contextDir: self.contextDir, complete: true)
var response = ClientStream()
response.buildID = buildID
Expand Down Expand Up @@ -123,6 +138,9 @@ actor BuildFSSync: BuildPipelineHandler {

let followPathsWalked = try walk(root: self.contextDir, includePatterns: followPaths)
for url in followPathsWalked {
guard self.contextDir.absoluteURL.cleanPath != url.absoluteURL.cleanPath else {
continue
}
guard self.contextDir.parentOf(url) else {
continue
}
Expand Down
107 changes: 56 additions & 51 deletions Sources/ContainerBuild/URL+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,77 +14,82 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

//

import Foundation

extension URL {
func parentOf(_ url: URL) -> Bool {
// if self is a relative path
guard self.cleanPath.hasPrefix("/") else {
return true
}
let pathItems = self.standardizedFileURL.absoluteURL.pathComponents.map { $0.cleanPathComponent }
let urlItems = url.standardizedFileURL.absoluteURL.pathComponents.map { $0.cleanPathComponent }
extension String {
fileprivate var fs_cleaned: String {
var value = self

if pathItems.count > urlItems.count {
return false
}
for (index, pathItem) in pathItems.enumerated() {
if urlItems[index] != pathItem {
return false
}
if value.hasPrefix("file://") {
value.removeFirst("file://".count)
}
return true
}

func relativeChildPath(to context: URL) throws -> String {
if !context.parentOf(self.absoluteURL.standardizedFileURL) {
throw BuildFSSync.Error.pathIsNotChild(self.cleanPath, context.cleanPath)
if value.count > 1 && value.last == "/" {
value.removeLast()
}

let pathItems = context.standardizedFileURL.pathComponents.map { $0.cleanPathComponent }
let urlItems = self.standardizedFileURL.pathComponents.map { $0.cleanPathComponent }
return value.removingPercentEncoding ?? value
}

return String(urlItems.dropFirst(pathItems.count).joined(separator: "/").trimming { $0 == "/" })
fileprivate var fs_components: [String] {
var parts: [String] = []
for segment in self.split(separator: "/", omittingEmptySubsequences: true) {
switch segment {
case ".":
continue
case "..":
if !parts.isEmpty { parts.removeLast() }
default:
parts.append(String(segment))
}
}
return parts
}

fileprivate var fs_isAbsolute: Bool { first == "/" }
}

extension URL {
var cleanPath: String {
let pathStr = self.path(percentEncoded: false)
if let cleanPath = pathStr.removingPercentEncoding {
return cleanPath
}
return pathStr
self.path.fs_cleaned
}

func relativePathFrom(from base: URL) -> String {
let destComponents = self.standardizedFileURL.pathComponents.map { $0.cleanPathComponent }
let baseComponents = base.standardizedFileURL.pathComponents.map { $0.cleanPathComponent }
func parentOf(_ url: URL) -> Bool {
let parentPath = self.absoluteURL.cleanPath
let childPath = url.absoluteURL.cleanPath

// Find the last common path between the two
var lastCommon: Int = 0
while lastCommon < baseComponents.count && lastCommon < destComponents.count && baseComponents[lastCommon] == destComponents[lastCommon] {
lastCommon += 1
guard parentPath.fs_isAbsolute else {
return true
}

if lastCommon == 0 {
return self.path
}
let parentParts = parentPath.fs_components
let childParts = childPath.fs_components

var relPath: [String] = []
guard parentParts.count <= childParts.count else { return false }
return zip(parentParts, childParts).allSatisfy { $0 == $1 }
}

// Add "../" for each component that's a directory after the common prefix
for i in lastCommon..<baseComponents.count {
let sub = baseComponents[0...i]
let currentPath = URL(filePath: sub.joined(separator: "/"))
let resourceValues: URLResourceValues? = try? currentPath.resourceValues(forKeys: [.isDirectoryKey])
if case let isDirectory = resourceValues?.isDirectory, isDirectory == true {
relPath.append("..")
}
func relativeChildPath(to context: URL) throws -> String {
guard context.parentOf(self) else {
throw BuildFSSync.Error.pathIsNotChild(cleanPath, context.cleanPath)
}

relPath.append(contentsOf: destComponents[lastCommon...])
return relPath.joined(separator: "/")
let ctxParts = context.cleanPath.fs_components
let selfParts = cleanPath.fs_components

return selfParts.dropFirst(ctxParts.count).joined(separator: "/")
}

func relativePathFrom(from base: URL) -> String {
let destParts = cleanPath.fs_components
let baseParts = base.cleanPath.fs_components

let common = zip(destParts, baseParts).prefix { $0 == $1 }.count
guard common > 0 else { return cleanPath }

let ups = Array(repeating: "..", count: baseParts.count - common)
let remainder = destParts.dropFirst(common)
return (ups + remainder).joined(separator: "/")
}

func zeroCopyReader(
Expand Down
13 changes: 0 additions & 13 deletions Tests/ContainerBuildTests/BuilderExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

//

import Foundation
import Testing

Expand Down Expand Up @@ -185,17 +183,6 @@ import Testing
#expect(false == fileURL.parentOf(httpURL))
}

@Test func testParentOfRelativePaths() throws {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test wasn't covering a valid condition in our logic. So, it has been removed

let absoluteChildDir = baseTempURL.appendingPathComponent("someDir")
try createDirectory(at: absoluteChildDir)
let relativeSelfURL = URL(fileURLWithPath: "a/relative/path")
#expect(relativeSelfURL.parentOf(absoluteChildDir))
let potentiallyParentRelative = URL(fileURLWithPath: baseTempURL.lastPathComponent)
#expect(potentiallyParentRelative.parentOf(absoluteChildDir))
}

// MARK: - relativeChildPath Tests

@Test func testRelativeChildPathDirectChild() throws {
let parentDir = baseTempURL.appendingPathComponent("dir1")
let childFile = parentDir.appendingPathComponent("dir2").appendingPathComponent("file")
Expand Down