From da922ef6bc5f09ddf3f5e50174d41b64fed4d5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 15:40:24 +0200 Subject: [PATCH 01/10] bindings: difference between document change-hash --- .../Automerge/Automerge.docc/Curation/Document.md | 2 ++ rust/src/automerge.udl | 2 ++ rust/src/doc.rs | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/Sources/Automerge/Automerge.docc/Curation/Document.md b/Sources/Automerge/Automerge.docc/Curation/Document.md index 7b41af2b..dc8cd17f 100644 --- a/Sources/Automerge/Automerge.docc/Curation/Document.md +++ b/Sources/Automerge/Automerge.docc/Curation/Document.md @@ -75,6 +75,8 @@ - ``heads()`` - ``getHistory()`` - ``change(hash:)`` +- ``difference(from:to:)`` +- ``difference(from:)`` ### Reading historical map values diff --git a/rust/src/automerge.udl b/rust/src/automerge.udl index 91cc92c9..faa9f98f 100644 --- a/rust/src/automerge.udl +++ b/rust/src/automerge.udl @@ -233,6 +233,8 @@ interface Doc { Change? change_by_hash(ChangeHash hash); + sequence difference(sequence before, sequence after); + void commit_with(string? msg, i64 time); sequence save(); diff --git a/rust/src/doc.rs b/rust/src/doc.rs index 90c363cd..e8dbd3e8 100644 --- a/rust/src/doc.rs +++ b/rust/src/doc.rs @@ -585,6 +585,20 @@ impl Doc { changes.into_iter().map(|h| h.hash().into()).collect() } + pub fn difference(&self, before: Vec, after: Vec) -> Vec { + let lhs = before + .into_iter() + .map(am::ChangeHash::from) + .collect::>(); + let rhs = after + .into_iter() + .map(am::ChangeHash::from) + .collect::>(); + 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 { let doc = self.0.write().unwrap(); doc.get_change_by_hash(&am::ChangeHash::from(hash)) From abd5c595489ae24ee2e1c986b7cc601027e66d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 16:32:00 +0200 Subject: [PATCH 02/10] api: patches between two set of change hash --- Sources/Automerge/Document.swift | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index ac2b7ff3..c1c639ee 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -946,6 +946,27 @@ 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. + /// - to: heads at ending point in the documents history. + /// - 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, to after: Set) -> [Patch] { + sync { + let patches = self.doc.wrapErrors { doc in + doc.difference(before: before.map(\.bytes), after: after.map(\.bytes)) + } + return patches.map { Patch($0) } + } + } + /// Get the path to an object within the document. /// /// - Parameter obj: The identifier of an array, dictionary or text object. From aa9f8588e4a37d01f2592dfec1c848573b4b20d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 16:35:00 +0200 Subject: [PATCH 03/10] api: patches from/to commits in the doc history --- Sources/Automerge/Document.swift | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index c1c639ee..090b6c23 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -967,6 +967,37 @@ public final class Document: @unchecked Sendable { } } + /// 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. + /// - Note: `from` do not have to be chronological. Document state can move backward. + /// - Returns: The difference needed to produce current document given an arbitrary + /// point in the history. + public func difference(from lhs: Set) -> [Patch] { + 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: `from` 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) -> [Patch] { + 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. From 0e5477bbfed1c4c52943a8f299f67cb74891cd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 16:35:44 +0200 Subject: [PATCH 04/10] uni tests: differences between points in the history --- Tests/AutomergeTests/TestChanges.swift | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Tests/AutomergeTests/TestChanges.swift b/Tests/AutomergeTests/TestChanges.swift index 177d890d..e0f55a5e 100644 --- a/Tests/AutomergeTests/TestChanges.swift +++ b/Tests/AutomergeTests/TestChanges.swift @@ -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) + } } From c9643a35da9c51499054324fecf11ed67859658e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 16:40:09 +0200 Subject: [PATCH 05/10] missing method into curation doc --- Sources/Automerge/Automerge.docc/Curation/Document.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Automerge/Automerge.docc/Curation/Document.md b/Sources/Automerge/Automerge.docc/Curation/Document.md index dc8cd17f..32b4933c 100644 --- a/Sources/Automerge/Automerge.docc/Curation/Document.md +++ b/Sources/Automerge/Automerge.docc/Curation/Document.md @@ -77,6 +77,7 @@ - ``change(hash:)`` - ``difference(from:to:)`` - ``difference(from:)`` +- ``difference(to:)`` ### Reading historical map values From 50dce34d3b300d522bccc68c95c0ee85d31cca19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 17:43:20 +0200 Subject: [PATCH 06/10] minor: typo in the difference binding api description --- Sources/Automerge/Document.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 090b6c23..2de45ce8 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -992,7 +992,7 @@ public final class Document: @unchecked Sendable { /// ``` /// - Parameters: /// - to: heads at ending point in the documents history. - /// - Note: `from` do not have to be chronological. Document state can move backward. + /// - 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) -> [Patch] { difference(from: heads(), to: rhs) From 57653374142cea6ba99584426d61f3e641cbe040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20D=C3=ADaz?= Date: Thu, 6 Jun 2024 23:01:11 +0200 Subject: [PATCH 07/10] Update Sources/Automerge/Document.swift Co-authored-by: Joseph Heck --- Sources/Automerge/Document.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 2de45ce8..6bc415bc 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -975,7 +975,7 @@ public final class Document: @unchecked Sendable { /// doc.difference(from: doc.heads()) /// ``` /// - Parameters: - /// - from: heads at beginning point in the documents history. + /// - since: The set of heads at the point in the documents history to compare to. /// - Note: `from` do not have to be chronological. Document state can move backward. /// - Returns: The difference needed to produce current document given an arbitrary /// point in the history. From 0d8de936103b4c58c9b476f2bfe196c99df85e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20D=C3=ADaz?= Date: Thu, 6 Jun 2024 23:01:16 +0200 Subject: [PATCH 08/10] Update Sources/Automerge/Document.swift Co-authored-by: Joseph Heck --- Sources/Automerge/Document.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 6bc415bc..9888e5ab 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -954,7 +954,7 @@ public final class Document: @unchecked Sendable { /// doc.difference(to: doc.heads()) /// ``` /// - Parameters: - /// - from: heads at beginning point in the documents history. + /// - from: The set of heads at beginning point in the documents history. /// - to: heads at ending point in the documents history. /// - 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. From cc75d8b2c4ff4fa8f2e776bc3040bc93680f1d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20D=C3=ADaz?= Date: Thu, 6 Jun 2024 23:01:30 +0200 Subject: [PATCH 09/10] Update Sources/Automerge/Document.swift Co-authored-by: Joseph Heck --- Sources/Automerge/Document.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 9888e5ab..3c185728 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -955,7 +955,7 @@ public final class Document: @unchecked Sendable { /// ``` /// - Parameters: /// - from: The set of heads at beginning point in the documents history. - /// - to: heads at ending point in the documents history. + /// - to: The set of heads at ending point in the documents history. /// - 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, to after: Set) -> [Patch] { From 0f3aa34ecde55271434a8896b9ec532ab69ceb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20A=CC=81ngel?= Date: Thu, 6 Jun 2024 23:11:10 +0200 Subject: [PATCH 10/10] comments in PR #183 --- .../Automerge.docc/Curation/Document.md | 2 +- Sources/Automerge/Document.swift | 24 ++++++++++++------- Tests/AutomergeTests/TestChanges.swift | 13 +++++----- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Sources/Automerge/Automerge.docc/Curation/Document.md b/Sources/Automerge/Automerge.docc/Curation/Document.md index 32b4933c..6bb6923b 100644 --- a/Sources/Automerge/Automerge.docc/Curation/Document.md +++ b/Sources/Automerge/Automerge.docc/Curation/Document.md @@ -76,7 +76,7 @@ - ``getHistory()`` - ``change(hash:)`` - ``difference(from:to:)`` -- ``difference(from:)`` +- ``difference(since:)`` - ``difference(to:)`` ### Reading historical map values diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 3c185728..2c196fe3 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -951,8 +951,15 @@ public final class Document: @unchecked Sendable { /// Use: /// ``` /// let doc = Document() - /// doc.difference(to: doc.heads()) + /// 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") + /// let after = doc.heads() + /// + /// let patches = doc.difference(from: before, to: after) /// ``` + /// /// - Parameters: /// - from: The set of heads at beginning point in the documents history. /// - to: The set of heads at ending point in the documents history. @@ -967,19 +974,19 @@ public final class Document: @unchecked Sendable { } } - /// Generates patches **from** a given point in the document history. + /// Generates patches **since** a given point in the document history. /// /// Use: /// ``` /// let doc = Document() - /// doc.difference(from: doc.heads()) + /// doc.difference(since: doc.heads()) /// ``` + /// /// - Parameters: /// - since: The set of heads at the point in the documents history to compare to. - /// - Note: `from` do not have to be chronological. Document state can move backward. /// - Returns: The difference needed to produce current document given an arbitrary /// point in the history. - public func difference(from lhs: Set) -> [Patch] { + public func difference(since lhs: Set) -> [Patch] { difference(from: lhs, to: heads()) } @@ -990,10 +997,11 @@ public final class Document: @unchecked Sendable { /// 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. + /// - to: The set of heads at ending point in the documents history. + /// - Returns: The difference needed to move current document to a previous point + /// in the history. public func difference(to rhs: Set) -> [Patch] { difference(from: heads(), to: rhs) } diff --git a/Tests/AutomergeTests/TestChanges.swift b/Tests/AutomergeTests/TestChanges.swift index e0f55a5e..c5acccea 100644 --- a/Tests/AutomergeTests/TestChanges.swift +++ b/Tests/AutomergeTests/TestChanges.swift @@ -34,10 +34,9 @@ class ChangeSetTests: XCTestCase { 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 ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ") @@ -47,15 +46,15 @@ class ChangeSetTests: XCTestCase { XCTAssertEqual(patches.first?.action, .DeleteSeq(DeleteSeq(obj: textId, index: 5, length: length))) } - func testDifferenceFromPreviousCommit() throws { + func testDifferenceSincePreviousCommit() 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) + let patches = doc.difference(since: before) XCTAssertEqual(patches.count, 1) XCTAssertEqual(patches.first?.action, .SpliceText(obj: textId, index: 5, value: " World ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", marks: [:])) } @@ -73,7 +72,7 @@ class ChangeSetTests: XCTestCase { XCTAssertEqual(patches.first?.action, .SpliceText(obj: textId, index: 0, value: "Hello World ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", marks: [:])) } - func testDifferenceProperty_DifferenceBetweenCommitAndCurrent_DifferenceFromCommit_ResultsEquals() throws { + func testDifferenceProperty_DifferenceBetweenCommitAndCurrent_DifferenceSinceCommit_ResultsEquals() throws { let doc = Document() let textId = try! doc.putObject(obj: ObjId.ROOT, key: "text", ty: .Text) let before = doc.heads() @@ -81,7 +80,7 @@ class ChangeSetTests: XCTestCase { 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) + let patches2 = doc.difference(since: before) XCTAssertEqual(patches1.count, 1) XCTAssertEqual(patches1, patches2) }