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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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[..<prefixIndex])
let suffix = String(hash[prefixIndex...])

// Ensure path separator is handled by caller or returned as standard relative path
let path = "\(prefix)/\(suffix)"

return (directory: prefix, filename: suffix, path: path)
}

/// Generates the relative path for a given hash.
/// - Parameter hash: The hex string representation of the hash.
/// - Returns: The relative path string (e.g., "ab/cdef...").
public static func path(for hash: String) -> String {
return layout(for: hash).path
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
}