-
Notifications
You must be signed in to change notification settings - Fork 8
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
Replant the tree! 🌲 #23
Conversation
src/lib.rs
Outdated
@@ -12,6 +12,8 @@ | |||
// See the License for the specific language governing permissions and | |||
// limitations under the License. | |||
|
|||
extern crate smallbitvec; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need extern crate
in rust 2018.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't enable Rust 2018 yet because it prints a ton of variable does not need to be mutable
warnings for the nodes!
macro:
warning: variable does not need to be mutable
--> src/tests.rs:60:13
|
60 | let mut item = Item::new(Guid::from($guid), Kind::$kind);
| ----^^^^
| |
| help: remove this `mut`
...
95 | let remote_tree = nodes!({
| _______________________-
96 | | ("unfiled_____", Folder[needs_merge = true], {
97 | | ("folderBBBBBB", Folder[needs_merge = true], {
98 | | ("bookmarkDDDD", Bookmark[needs_merge = true]),
... |
108 | | })
109 | | }).into_tree().unwrap();
| |______- in this macro invocation
...When item
definitely needs to be mutable, because we modify its fields!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some cases it doesn't need to be mutable: e.g. when [ $( $name:ident = $value:expr ),* ]
matches 0 tokens (also when there's 0 children for the macro's other case).
I suspect avoiding the muts in those case will either be difficult and or verbose. Since it's just for tests I'd say add #[allow(unused_mut)]
to both muts in the macro and enjoy the 2018 edition
Codecov Report
@@ Coverage Diff @@
## master #23 +/- ##
==========================================
- Coverage 92.46% 90.97% -1.49%
==========================================
Files 7 7
Lines 3144 2638 -506
==========================================
- Hits 2907 2400 -507
- Misses 237 238 +1
Continue to review full report at Codecov.
|
Yikes, that diff isn't very helpful. The interesting parts are |
...And here's how it works on Desktop. |
4b64d1c
to
0445bd6
Compare
src/tests.rs
Outdated
.children(&parent_guid) | ||
.item(&parent_guid), | ||
node.item.into())?; | ||
match b.item(node.item) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: map + or_else is maybe nicer? #negligble
match b.item(node.item) { | |
b.item(node.item) | |
.map(|_| ()) | |
.or_else(|err| match err.kind() { | |
ErrorKind::DuplicateItem(_) => Ok(()), | |
_ => Err(err), | |
})?; |
src/tests.rs
Outdated
.and_then(|b| b.parent_for(&"folderBBBBBB".into()).by_children(&"folderAAAAAA".into())) | ||
.expect("Should insert B"); | ||
|
||
match b.into_tree() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: unwrap_err
/expect_err
's nice for these (not 100% equivalent, printing the Kind vs the wrapping err):
match b.into_tree() { | |
match b.into_tree().expect_err("Should not build tree with cycles").kind() { | |
ErrorKind::Cycle(guid) => assert_eq!(guid, &Guid::from("folderAAAAAA")), | |
err @ _ => assert!(false, "Wrong error kind for cycle: {:?}", err), | |
} |
src/lib.rs
Outdated
@@ -12,6 +12,8 @@ | |||
// See the License for the specific language governing permissions and | |||
// limitations under the License. | |||
|
|||
extern crate smallbitvec; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some cases it doesn't need to be mutable: e.g. when [ $( $name:ident = $value:expr ),* ]
matches 0 tokens (also when there's 0 children for the macro's other case).
I suspect avoiding the muts in those case will either be difficult and or verbose. Since it's just for tests I'd say add #[allow(unused_mut)]
to both muts in the macro and enjoy the 2018 edition
src/tree.rs
Outdated
/// # Resolving divergences | ||
/// | ||
/// Walking the tree, using `Tree::node_for_guid`, `Node::parent`, and | ||
/// `Node::children`, resolves divergences using these rules: | ||
/// Building a tree using `TreeBuilder::into_tree` resolves divergences using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Building a tree using `TreeBuilder::into_tree` resolves divergences using | |
/// Building a tree using `Builder::into_tree` resolves divergences using |
0445bd6
to
36bd737
Compare
36bd737
to
27ecc8d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great
This started out as a fix for corruption corner cases, grew into simplifying how we build the remote tree, and turned into a rewrite (again >.>). Instead of managing two different sets of structure at insert and merge time, we now store items and parent-child relationships in a tree builder, then build a consistent tree that flags divergent items. The old tree was complicated because it had to maintain a valid structure before and after every insert, but with references to potentially unknown parents and children. Since we couldn't insert an item without knowing its parent, we had to pick a canonical structure (parents by `children`), insert children in level order, and note divergent structure (by `parentid`) separately. This meant Desktop's `Store::fetch_remote_tree()` had to left-join `mirror.items` to `mirror.structure`, store the items in a pseudo-tree, then recursively walk children to inflate the complete tree. This was complicated enough with a valid tree, let alone orphans and multiple parents. With the new approach, we build the tree in three steps: 1. First, we add all items, without structure. 2. Next, we set parent-child relationships. Parents by `children` require the parent to exist, but not the child; this handles the case where a folder mentions nonexistent or deleted GUIDs in its `children`. Parents by `parentid` require the item to exist, but not its parent; this handles orphans that reference missing or non-folder parents. 3. Finally, once we've added all entries to the tree, we have a complete view of the structure, so we can resolve missing, multiple, and conflicting parents. For cases where we know the structure is valid and in level order, like Desktop's `Store::fetch_local_tree()`, we handle steps 1 and 2 at the same time: `builder.item(item)?.by_structure(&parent_guid)?`. For the remote tree, we insert all items and their `parentid`s first (`builder.item(item)?.by_parent_guid(&parent_guid)?`, where `parent_guid` might not be in the tree), then add structure from children later: `builder.parent_for(&child_guid)?.by_children(&parent_guid)?`, where `child_guid` might not be in the tree. Closes #22.
27ecc8d
to
7f5f931
Compare
This started out as a fix for corruption corner cases, grew into
simplifying how we build the remote tree, and turned into a rewrite
(again >.>). Instead of managing two different sets of structure at
insert and merge time, we now store items and parent-child
relationships in a tree builder, then build a consistent tree that
flags divergent items.
The old tree was complicated because it had to maintain a valid
structure before and after every insert, but with references to
potentially unknown parents and children. Since we couldn't insert
an item without knowing its parent, we had to pick a canonical
structure (parents by
children
), insert children in levelorder, and note divergent structure (by
parentid
) separately.This meant Desktop's
Store::fetch_remote_tree()
had to left-joinmirror.items
tomirror.structure
, store the items in apseudo-tree, then recursively walk children to inflate the complete
tree. This was complicated enough with a valid tree, let alone
orphans and multiple parents.
With the new approach, we build the tree in three steps:
children
require the parent to exist, but not the child; this handles the
case where a folder mentions nonexistent or deleted GUIDs in
its
children
. Parents byparentid
require the item to exist,but not its parent; this handles orphans that reference missing
or non-folder parents.
complete view of the structure, so we can resolve missing, multiple,
and conflicting parents.
For cases where we know the structure is valid and in level order,
like Desktop's
Store::fetch_local_tree()
, we handle steps 1 and 2at the same time:
builder.item(item)?.by_structure(&parent_guid)?
.For the remote tree, we insert all items and their
parentid
sfirst (
builder.item(item)?.by_parent_guid(&parent_guid)?
, whereparent_guid
might not be in the tree), then add structure fromchildren later:
builder.parent_for(&child_guid)?.by_children(&parent_guid)?
,where
child_guid
might not be in the tree.Closes #22.