mirror of
https://github.com/apple/container.git
synced 2026-07-15 14:07:04 +00:00
0885cdd6a9
This PR introduces the `Differ` with methods: ```swift // Differ protocol func diff(base: Snapshot?, target: Snapshot) async throws -> Descriptor func apply(descriptor: Descriptor, to base: Snapshot?) async throws -> Snapshot ``` It also introduces `DiffKey`, which is a MerkeTree based key for fast diff computations between two dirs
199 lines
6.8 KiB
Swift
199 lines
6.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerBuildIR
|
|
import ContainerizationOCI
|
|
import Foundation
|
|
|
|
/// A protocol for computing and storing filesystem diffs
|
|
///
|
|
/// The Differ is responsible for:
|
|
/// 1. Computing the delta between two filesystem states
|
|
/// 2. Serializing that delta into an OCI-compliant layer format
|
|
/// 3. Storing the layer to a content store
|
|
/// 4. Returning a descriptor that can be used in OCI manifests
|
|
public protocol Differ: Sendable {
|
|
/// The content store where diffs will be stored
|
|
var contentStore: any ContentStore { get }
|
|
|
|
/// Compute the difference between two snapshots and store it.
|
|
///
|
|
/// This method performs the complete diff workflow:
|
|
/// 1. Computes filesystem changes between base and target
|
|
/// 2. Creates a tar archive of those changes
|
|
/// 3. Applies the specified compression format
|
|
/// 4. Stores the result to the content store
|
|
/// 5. Returns a descriptor suitable for OCI manifests
|
|
///
|
|
/// - Parameters:
|
|
/// - base: The base snapshot (nil for initial/scratch layers)
|
|
/// - target: The target snapshot to diff against base
|
|
/// - format: The compression format to use for the layer
|
|
/// - Returns: A Descriptor containing the descriptor and statistics
|
|
/// - Throws: If diff computation or storage fails
|
|
func diff(
|
|
base: Snapshot?,
|
|
target: Snapshot
|
|
) async throws -> Descriptor
|
|
|
|
/// Apply a stored diff to a base snapshot to produce a target.
|
|
///
|
|
/// This is the inverse operation of computeAndStore, used when:
|
|
/// - Materializing snapshots from cached layers
|
|
/// - Applying patches during incremental builds
|
|
/// - Validating diff correctness
|
|
///
|
|
/// - Parameters:
|
|
/// - descriptor: The descriptor of the stored diff
|
|
/// - base: The base snapshot to apply the diff to (nil for scratch)
|
|
/// - Returns: The resulting snapshot after applying the diff
|
|
/// - Throws: If the diff cannot be applied
|
|
func apply(
|
|
descriptor: Descriptor,
|
|
to base: Snapshot?
|
|
) async throws -> Snapshot
|
|
}
|
|
|
|
/// Diff represents a filesystem diff entry.
|
|
///
|
|
/// - Additions and deletions only need the path previously, but we now surface
|
|
/// normalized attributes for both additions and modifications to avoid re-reading
|
|
/// from the OS during archive creation.
|
|
public enum Diff: Sendable, Equatable {
|
|
/// Details for an addition entry with surfaced attributes.
|
|
public struct Added: Sendable, Equatable {
|
|
public let path: BinaryPath
|
|
public let node: Modified.Node
|
|
public let permissions: FilePermissions?
|
|
public let size: Int64?
|
|
public let modificationTime: Date?
|
|
public let linkTarget: BinaryPath?
|
|
public let uid: UInt32?
|
|
public let gid: UInt32?
|
|
public let xattrs: [String: Data]?
|
|
public let devMajor: UInt32?
|
|
public let devMinor: UInt32?
|
|
public let nlink: UInt64?
|
|
|
|
public init(
|
|
path: BinaryPath,
|
|
node: Modified.Node,
|
|
permissions: FilePermissions?,
|
|
size: Int64?,
|
|
modificationTime: Date?,
|
|
linkTarget: BinaryPath?,
|
|
uid: UInt32?,
|
|
gid: UInt32?,
|
|
xattrs: [String: Data]?,
|
|
devMajor: UInt32?,
|
|
devMinor: UInt32?,
|
|
nlink: UInt64?
|
|
) {
|
|
self.path = path
|
|
self.node = node
|
|
self.permissions = permissions
|
|
self.size = size
|
|
self.modificationTime = modificationTime
|
|
self.linkTarget = linkTarget
|
|
self.uid = uid
|
|
self.gid = gid
|
|
self.xattrs = xattrs
|
|
self.devMajor = devMajor
|
|
self.devMinor = devMinor
|
|
self.nlink = nlink
|
|
}
|
|
}
|
|
|
|
/// Details for a modification entry.
|
|
public struct Modified: Sendable, Equatable {
|
|
/// The kind of modification detected. Derived from FileDiffResult.
|
|
public enum Kind: Sendable, Equatable {
|
|
case metadataOnly
|
|
case contentChanged
|
|
case typeChanged
|
|
case symlinkTargetChanged
|
|
}
|
|
|
|
/// Kind of filesystem node (target state).
|
|
public enum Node: Sendable, Equatable {
|
|
case regular
|
|
case directory
|
|
case symlink
|
|
case device
|
|
case fifo
|
|
case socket
|
|
}
|
|
|
|
public let path: BinaryPath
|
|
public let kind: Kind
|
|
public let node: Node
|
|
public let permissions: FilePermissions?
|
|
public let size: Int64?
|
|
public let modificationTime: Date?
|
|
public let linkTarget: BinaryPath?
|
|
public let uid: UInt32?
|
|
public let gid: UInt32?
|
|
public let xattrs: [String: Data]?
|
|
public let devMajor: UInt32?
|
|
public let devMinor: UInt32?
|
|
public let nlink: UInt64?
|
|
|
|
public init(
|
|
path: BinaryPath,
|
|
kind: Kind,
|
|
node: Node,
|
|
permissions: FilePermissions?,
|
|
size: Int64?,
|
|
modificationTime: Date?,
|
|
linkTarget: BinaryPath?,
|
|
uid: UInt32?,
|
|
gid: UInt32?,
|
|
xattrs: [String: Data]?,
|
|
devMajor: UInt32?,
|
|
devMinor: UInt32?,
|
|
nlink: UInt64?
|
|
) {
|
|
self.path = path
|
|
self.kind = kind
|
|
self.node = node
|
|
self.permissions = permissions
|
|
self.size = size
|
|
self.modificationTime = modificationTime
|
|
self.linkTarget = linkTarget
|
|
self.uid = uid
|
|
self.gid = gid
|
|
self.xattrs = xattrs
|
|
self.devMajor = devMajor
|
|
self.devMinor = devMinor
|
|
self.nlink = nlink
|
|
}
|
|
}
|
|
|
|
case added(Added)
|
|
case modified(Modified)
|
|
case deleted(path: BinaryPath)
|
|
}
|
|
|
|
/// POSIX file permission bits
|
|
public struct FilePermissions: Equatable, Sendable {
|
|
public let rawValue: UInt16
|
|
public init(rawValue: UInt16) { self.rawValue = rawValue }
|
|
}
|
|
|
|
public protocol ContentHasher: Sendable {
|
|
func hash(fileURL: URL) throws -> Data
|
|
}
|