From f754182c476b5a95357c7d69835c5eeaddfb6f42 Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Fri, 19 Dec 2025 23:31:53 -0600 Subject: [PATCH] feat: Add GitObjectLayout strategy - Implements GitObjectLayout for generating git-style directory paths from hashes - Adds unit tests for layout generation - Fixes redundant public modifier warning in HashAlgorithmProtocol --- .../Hashing/HashAlgorithmProtocol.swift | 2 +- .../Algorithms/Storage/GitObjectLayout.swift | 50 +++++++++++++++++++ .../Algorithms/GitObjectLayoutTests.swift | 45 +++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 Sources/DesignAlgorithmsKit/Algorithms/Storage/GitObjectLayout.swift create mode 100644 Tests/DesignAlgorithmsKitTests/Algorithms/GitObjectLayoutTests.swift diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift index e91846e..6b90b63 100644 --- a/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift +++ b/Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift @@ -33,7 +33,7 @@ public extension HashAlgorithmProtocol { /// - Returns: Hash value as Data, or empty Data if UTF-8 conversion fails /// - Note: UTF-8 conversion failure returns empty Data, which will hash to a valid hash value. /// This path is testable by creating strings that fail UTF-8 conversion (rare but possible). - public static func hash(string: String) -> Data { + static func hash(string: String) -> Data { guard let data = string.data(using: .utf8) else { // UTF-8 conversion failed - return hash of empty data // This is a valid fallback that ensures we always return a hash diff --git a/Sources/DesignAlgorithmsKit/Algorithms/Storage/GitObjectLayout.swift b/Sources/DesignAlgorithmsKit/Algorithms/Storage/GitObjectLayout.swift new file mode 100644 index 0000000..9f1f6e5 --- /dev/null +++ b/Sources/DesignAlgorithmsKit/Algorithms/Storage/GitObjectLayout.swift @@ -0,0 +1,50 @@ +// +// GitObjectLayout.swift +// DesignAlgorithmsKit +// +// Created for DesignAlgorithmsKit +// + +import Foundation + +/// Git-Style Directory Layout Strategy +/// +/// Implements the directory layout strategy used by Git for loose objects, +/// where a content hash is split into a directory (prefix) and filename (remainder). +/// +/// Example: +/// Hash: "a1b2c3d4..." +/// Directory: "a1" +/// Filename: "b2c3d4..." +/// Path: "a1/b2c3d4..." +public struct GitObjectLayout { + + /// The length of the prefix used for the directory name + public static let prefixLength = 2 + + /// Generates the layout components for a given hash. + /// - Parameter hash: The hex string representation of the hash. + /// - Returns: A tuple containing the directory, filename, and combined relative path. + /// If the hash is too short (<= prefixLength), returns empty directory and the hash as filename. + public static func layout(for hash: String) -> (directory: String, filename: String, path: String) { + guard hash.count > Self.prefixLength else { + return (directory: "", filename: hash, path: hash) + } + + let prefixIndex = hash.index(hash.startIndex, offsetBy: Self.prefixLength) + let prefix = String(hash[.. String { + return layout(for: hash).path + } +} diff --git a/Tests/DesignAlgorithmsKitTests/Algorithms/GitObjectLayoutTests.swift b/Tests/DesignAlgorithmsKitTests/Algorithms/GitObjectLayoutTests.swift new file mode 100644 index 0000000..45499f7 --- /dev/null +++ b/Tests/DesignAlgorithmsKitTests/Algorithms/GitObjectLayoutTests.swift @@ -0,0 +1,45 @@ +import XCTest +@testable import DesignAlgorithmsKit + +final class GitObjectLayoutTests: XCTestCase { + + func testLayoutGeneration() { + let hash = "a1b2c3d4e5f6" + let layout = GitObjectLayout.layout(for: hash) + + XCTAssertEqual(layout.directory, "a1") + XCTAssertEqual(layout.filename, "b2c3d4e5f6") + XCTAssertEqual(layout.path, "a1/b2c3d4e5f6") + } + + func testShortHash() { + let hash = "ab" + let layout = GitObjectLayout.layout(for: hash) + + // Expect behavior for short hashes (implementation specific: no split) + XCTAssertEqual(layout.path, "ab") + XCTAssertEqual(layout.directory, "") + XCTAssertEqual(layout.filename, "ab") + + let hash2 = "a" + let layout2 = GitObjectLayout.layout(for: hash2) + XCTAssertEqual(layout2.path, "a") + XCTAssertEqual(layout2.directory, "") + XCTAssertEqual(layout2.filename, "a") + } + + func testPathHelper() { + let hash = "1234567890" + XCTAssertEqual(GitObjectLayout.path(for: hash), "12/34567890") + } + + func testStandardGitHash() { + // Example SHA-1 hash + let hash = "5e80dc522e0327ba4944d180bbf261904e545805" + let layout = GitObjectLayout.layout(for: hash) + + XCTAssertEqual(layout.directory, "5e") + XCTAssertEqual(layout.filename, "80dc522e0327ba4944d180bbf261904e545805") + XCTAssertEqual(layout.path, "5e/80dc522e0327ba4944d180bbf261904e545805") + } +}