Skip to content

Commit

Permalink
making heads raw encode into opaque Data type
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelangel-dev committed Jul 12, 2024
1 parent a55177a commit 5fa3988
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 27 deletions.
25 changes: 22 additions & 3 deletions Sources/Automerge/ChangeHash.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AutomergeUniffi
import Foundation

/// An opaque hash that represents a change within an Automerge document.
public struct ChangeHash: Equatable, Hashable, CustomDebugStringConvertible, Sendable {
Expand All @@ -15,9 +16,27 @@ public extension Set<ChangeHash> {
/// Transforms each `ChangeHash` in the set into its byte array (`[UInt8]`). This raw byte representation
/// captures the state of the document at a specific point in its history, allowing for efficient storage
/// and retrieval of document states.
func raw() -> [[UInt8]] {
map(\.bytes).sorted { lhs, rhs in
lhs[0] > rhs[0]
func raw() -> Data {
let rawBytes = map(\.bytes).sorted { lhs, rhs in
lhs.hashValue > rhs.hashValue
}
return Data(rawBytes.joined())
}
}

public extension Data {

/// Returns the related set of changes of a state representation within an Automerge document.
func heads() -> Set<ChangeHash>? {
let rawBytes = Array(self)
guard rawBytes.count % 32 == 0 else { return nil }
let totalHashes = rawBytes.count / 32
let heads = (0..<totalHashes).map { index in
let lowerBound = index * 32
let upperBound = (index + 1) * 32
let bytes = rawBytes[lowerBound..<upperBound]
return ChangeHash(bytes: Array(bytes))
}
return Set(heads)
}
}
11 changes: 0 additions & 11 deletions Sources/Automerge/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1035,17 +1035,6 @@ public final class Document: @unchecked Sendable {
}
}

/// Returns the related set of changes of a state representation within an Automerge document.
public func heads(raw: [[UInt8]]) -> Set<ChangeHash>? {
var output: Set<ChangeHash> = []
for bytes in raw {
guard let changeHash = change(hash: ChangeHash(bytes: bytes))?.hash
else { return nil }
output.insert(changeHash)
}
return output
}

/// Generates patches between two points in the document history.
///
/// Use:
Expand Down
25 changes: 12 additions & 13 deletions Tests/AutomergeTests/TestChanges.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,28 +94,27 @@ class ChangeSetTests: XCTestCase {
try doc.merge(other: doc1)

let heads = doc.heads()
let restored = doc.heads(raw: heads.raw())
let restored = doc.heads().raw().heads()

XCTAssertEqual(heads, restored)
}

func testChangeHash_WhenRawIsManipulated_DocumentDoesNotAccept() throws {
func testChangeHash_SameHeads_ResultSameRawData() throws {
let doc = Document()
let textId = try! doc.putObject(obj: ObjId.ROOT, key: "text", ty: .Text)
let doc1 = doc.fork()
try doc.spliceText(obj: textId, start: 0, delete: 0, value: "Hello")
try doc1.spliceText(obj: textId, start: 0, delete: 0, value: " World!")
let doc2 = doc.fork()
let doc3 = doc.fork()
try doc.spliceText(obj: textId, start: 0, delete: 0, value: "[0]")
try doc1.spliceText(obj: textId, start: 0, delete: 0, value: "[1]")
try doc2.spliceText(obj: textId, start: 0, delete: 0, value: "[2]")
try doc3.spliceText(obj: textId, start: 0, delete: 0, value: "[3]")
try doc.merge(other: doc1)
try doc.merge(other: doc2)
try doc.merge(other: doc3)

let headsRaw = doc.heads().raw().map { raw in
var raw = raw
raw[0] = 0
raw[7] = 0
raw[14] = 0
return raw
}
let restored = doc.heads(raw: headsRaw)
let rawHashes = (0..<100).map { _ in doc.heads().raw().hashValue }

XCTAssertNil(restored)
XCTAssertEqual(Set(rawHashes).count, 1)
}
}

0 comments on commit 5fa3988

Please sign in to comment.