Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exposing Automerge::Diff through to Swift #183

Merged
merged 11 commits into from
Jun 7, 2024
3 changes: 3 additions & 0 deletions Sources/Automerge/Automerge.docc/Curation/Document.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
- ``heads()``
- ``getHistory()``
- ``change(hash:)``
- ``difference(from:to:)``
- ``difference(from:)``
heckj marked this conversation as resolved.
Show resolved Hide resolved
- ``difference(to:)``
miguelangel-dev marked this conversation as resolved.
Show resolved Hide resolved

### Reading historical map values

Expand Down
52 changes: 52 additions & 0 deletions Sources/Automerge/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,58 @@ public final class Document: @unchecked Sendable {
}
}

/// Generates patches between two points in the document history.
///
/// Use:
/// ```
/// let doc = Document()
/// doc.difference(to: doc.heads())
/// ```
/// - Parameters:
/// - from: heads at beginning point in the documents history.
miguelangel-dev marked this conversation as resolved.
Show resolved Hide resolved
/// - to: heads at ending point in the documents history.
miguelangel-dev marked this conversation as resolved.
Show resolved Hide resolved
/// - Note: `from` and `to` do not have to be chronological. Document state can move backward.
/// - Returns: The difference needed to produce a document at `to` when it is set at `from` in history.
public func difference(from before: Set<ChangeHash>, to after: Set<ChangeHash>) -> [Patch] {
sync {
let patches = self.doc.wrapErrors { doc in
doc.difference(before: before.map(\.bytes), after: after.map(\.bytes))
}
return patches.map { Patch($0) }
}
}

/// Generates patches **from** a given point in the document history.
///
/// Use:
/// ```
/// let doc = Document()
/// doc.difference(from: doc.heads())
/// ```
/// - Parameters:
/// - from: heads at beginning point in the documents history.
miguelangel-dev marked this conversation as resolved.
Show resolved Hide resolved
/// - Note: `from` do not have to be chronological. Document state can move backward.
heckj marked this conversation as resolved.
Show resolved Hide resolved
/// - Returns: The difference needed to produce current document given an arbitrary
/// point in the history.
public func difference(from lhs: Set<ChangeHash>) -> [Patch] {
heckj marked this conversation as resolved.
Show resolved Hide resolved
difference(from: lhs, to: heads())
}

/// Generates patches **to** a given point in the document history.
///
/// Use:
/// ```
/// let doc = Document()
/// doc.difference(to: doc.heads())
/// ```
/// - Parameters:
/// - to: heads at ending point in the documents history.
/// - Note: `to` do not have to be chronological. Document state can move backward.
/// - Returns: The difference needed to produce a document at `to` in the history.
public func difference(to rhs: Set<ChangeHash>) -> [Patch] {
miguelangel-dev marked this conversation as resolved.
Show resolved Hide resolved
difference(from: heads(), to: rhs)
}

/// Get the path to an object within the document.
///
/// - Parameter obj: The identifier of an array, dictionary or text object.
Expand Down
54 changes: 54 additions & 0 deletions Tests/AutomergeTests/TestChanges.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,58 @@ class ChangeSetTests: XCTestCase {
XCTAssertEqual(bytes, data)
// print(data.hexEncodedString())
}

func testDifferenceToPreviousCommit() throws {
let doc = Document()
doc.difference(to: doc.heads())
let textId = try! doc.putObject(obj: ObjId.ROOT, key: "text", ty: .Text)
try doc.spliceText(obj: textId, start: 0, delete: 0, value: "Hello")

let before = doc.heads()
try doc.spliceText(obj: textId, start: 5, delete: 0, value: " World 👨‍👩‍👧‍👦")

let patches = doc.difference(to: before)
let length = UInt64(" World 👨‍👩‍👧‍👦".unicodeScalars.count)
XCTAssertEqual(patches.count, 1)
XCTAssertEqual(patches.first?.action, .DeleteSeq(DeleteSeq(obj: textId, index: 5, length: length)))
}

func testDifferenceFromPreviousCommit() throws {
let doc = Document()
let textId = try! doc.putObject(obj: ObjId.ROOT, key: "text", ty: .Text)
try doc.spliceText(obj: textId, start: 0, delete: 0, value: "Hello")

let before = doc.heads()
try doc.spliceText(obj: textId, start: 5, delete: 0, value: " World 👨‍👩‍👧‍👦")

let patches = doc.difference(from: before)
XCTAssertEqual(patches.count, 1)
XCTAssertEqual(patches.first?.action, .SpliceText(obj: textId, index: 5, value: " World 👨‍👩‍👧‍👦", marks: [:]))
}

func testDifferenceBetweenTwoCommitsInHistory() throws {
let doc = Document()
let textId = try! doc.putObject(obj: ObjId.ROOT, key: "text", ty: .Text)
let before = doc.heads()
try doc.spliceText(obj: textId, start: 0, delete: 0, value: "Hello")
try doc.spliceText(obj: textId, start: 5, delete: 0, value: " World 👨‍👩‍👧‍👦")
let after = doc.heads()

let patches = doc.difference(from: before, to: after)
XCTAssertEqual(patches.count, 1)
XCTAssertEqual(patches.first?.action, .SpliceText(obj: textId, index: 0, value: "Hello World 👨‍👩‍👧‍👦", marks: [:]))
}

func testDifferenceProperty_DifferenceBetweenCommitAndCurrent_DifferenceFromCommit_ResultsEquals() throws {
let doc = Document()
let textId = try! doc.putObject(obj: ObjId.ROOT, key: "text", ty: .Text)
let before = doc.heads()
try doc.spliceText(obj: textId, start: 0, delete: 0, value: "Hello")
try doc.spliceText(obj: textId, start: 5, delete: 0, value: " World 👨‍👩‍👧‍👦")

let patches1 = doc.difference(from: before, to: doc.heads())
let patches2 = doc.difference(from: before)
XCTAssertEqual(patches1.count, 1)
XCTAssertEqual(patches1, patches2)
}
}
2 changes: 2 additions & 0 deletions rust/src/automerge.udl
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ interface Doc {

Change? change_by_hash(ChangeHash hash);

sequence<Patch> difference(sequence<ChangeHash> before, sequence<ChangeHash> after);

void commit_with(string? msg, i64 time);

sequence<u8> save();
Expand Down
14 changes: 14 additions & 0 deletions rust/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,20 @@ impl Doc {
changes.into_iter().map(|h| h.hash().into()).collect()
}

pub fn difference(&self, before: Vec<ChangeHash>, after: Vec<ChangeHash>) -> Vec<Patch> {
let lhs = before
.into_iter()
.map(am::ChangeHash::from)
.collect::<Vec<_>>();
let rhs = after
.into_iter()
.map(am::ChangeHash::from)
.collect::<Vec<_>>();
let mut doc = self.0.write().unwrap();
let patches = doc.diff(&lhs, &rhs);
patches.into_iter().map(Patch::from).collect()
}

pub fn change_by_hash(&self, hash: ChangeHash) -> Option<Change> {
let doc = self.0.write().unwrap();
doc.get_change_by_hash(&am::ChangeHash::from(hash))
Expand Down
Loading