fix merge bug

This commit is contained in:
Orion Henry 2021-11-16 18:09:49 -05:00
parent 8a980d5340
commit 8f3ac5a626
4 changed files with 31 additions and 19 deletions
automerge-js
automerge/src

View file

@ -100,7 +100,7 @@ function save(doc) {
}
function merge(local, remote) {
const localState = local[STATE]
const localState = local[STATE].clone()
const remoteState = remote[STATE]
const changes = localState.getChangesAdded(remoteState)
localState.applyChanges(changes)
@ -187,7 +187,7 @@ function applyChanges(doc, changes) {
if (doc[READ_ONLY] === false) {
throw new RangeError("Calls to Automerge.change cannot be nested")
}
const state = doc[STATE]
const state = doc[STATE].clone()
state.applyChanges(changes)
return [rootProxy(state, true)];
}

View file

@ -167,14 +167,16 @@ const MapHandler = {
},
set (target, key, val) {
//console.log("MAP.SET", key, val)
let { context, objectId, path, readonly, frozen, conflicts } = target
let [ value, datatype] = import_value(val)
if (val && val[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object')
}
if (key === FROZEN) {
target.frozen = val
return
}
conflicts = conflicts || local_conflicts(context, objectId, key)
let [ value, datatype ] = import_value(val)
if (map_get(target,key) === val && !conflicts) {
return
}
@ -290,7 +292,11 @@ const MapHandler = {
function splice(target, index, del, vals) {
const [context, objectId, path, readonly, frozen, conflicts] = target
index = parseListIndex(index)
const values = vals.map((val) => import_value(val))
for (let val of vals) {
if (val && val[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object')
}
}
if (frozen) {
throw new RangeError("Attempting to use an outdated Automerge document")
}
@ -303,6 +309,7 @@ function splice(target, index, del, vals) {
result.push(value)
context.del(objectId, index)
}
const values = vals.map((val) => import_value(val))
for (let [value,datatype] of values) {
switch (datatype) {
case "list":
@ -341,12 +348,15 @@ const ListHandler = {
//console.log("SET", index, val, objectId)
//console.log("len", context.length(objectId))
index = parseListIndex(index)
const [ value, datatype] = import_value(val)
if (val && val[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object')
}
if (index === FROZEN) {
target.frozen = val
return
}
conflicts = conflicts || local_conflicts(context, objectId, index)
const [ value, datatype] = import_value(val)
if (list_get(target,index) === val && !conflicts) {
return
}

View file

@ -416,7 +416,7 @@ describe('Automerge', () => {
Automerge.change(s1, doc => {
assert.throws(() => { doc.foo = undefined }, /Unsupported type of value: undefined/)
assert.throws(() => { doc.foo = {prop: undefined} }, /Unsupported type of value: undefined/)
assert.throws(() => { doc.foo = () => {} }, /Unsupported type of value: function/)
assert.throws(() => { doc.foo = () => {} }, /Unsupported type of value: function/)
assert.throws(() => { doc.foo = Symbol('foo') }, /Unsupported type of value: symbol/)
})
})
@ -750,7 +750,7 @@ describe('Automerge', () => {
assert.deepStrictEqual(s1.maze[0][0][0][0][0][0][0][1][1], 'here')
})
it.skip('should not allow several references to the same list object', () => {
it('should not allow several references to the same list object', () => {
s1 = Automerge.change(s1, doc => doc.list = [])
assert.throws(() => {
Automerge.change(s1, doc => { doc.x = doc.list })
@ -830,7 +830,7 @@ describe('Automerge', () => {
})
})
it.skip('should detect concurrent updates of the same field', () => {
it('should detect concurrent updates of the same field', () => {
s1 = Automerge.change(s1, doc => doc.field = 'one')
s2 = Automerge.change(s2, doc => doc.field = 'two')
s3 = Automerge.merge(s1, s2)
@ -845,7 +845,7 @@ describe('Automerge', () => {
})
})
it.skip('should detect concurrent updates of the same list element', () => {
it('should detect concurrent updates of the same list element', () => {
s1 = Automerge.change(s1, doc => doc.birds = ['finch'])
s2 = Automerge.merge(s2, s1)
s1 = Automerge.change(s1, doc => doc.birds[0] = 'greenfinch')
@ -887,7 +887,7 @@ describe('Automerge', () => {
})
})
it.skip('should handle changes within a conflicting list element', () => {
it('should handle changes within a conflicting list element', () => {
s1 = Automerge.change(s1, doc => doc.list = ['hello'])
s2 = Automerge.merge(s2, s1)
s1 = Automerge.change(s1, doc => doc.list[0] = {map1: true})
@ -917,7 +917,7 @@ describe('Automerge', () => {
})
})
it.skip('should clear conflicts after assigning a new value', () => {
it('should clear conflicts after assigning a new value', () => {
s1 = Automerge.change(s1, doc => doc.field = 'one')
s2 = Automerge.change(s2, doc => doc.field = 'two')
s3 = Automerge.merge(s1, s2)

View file

@ -172,7 +172,7 @@ impl Automerge {
(OpId(0, _), OpId(0, _)) => Ordering::Equal,
(OpId(0, _), OpId(_, _)) => Ordering::Less,
(OpId(_, _), OpId(0, _)) => Ordering::Greater,
(OpId(a, x), OpId(b, y)) if a == b => self.actors[x].cmp(&self.actors[y]),
(OpId(a, x), OpId(b, y)) if a == b => self.actors[y].cmp(&self.actors[x]),
(OpId(a, _), OpId(b, _)) => a.cmp(&b),
}
}
@ -228,7 +228,7 @@ impl Automerge {
pred: vec![],
insert,
};
self.insert_op(op.clone(), true);
let op = self.insert_op(op, true);
tx.operations.push(op);
self.transaction = Some(tx);
Ok(id)
@ -326,7 +326,7 @@ impl Automerge {
self.ops[*pos].succ.push(op.id);
op.pred.push(self.ops[*pos].id);
}
} else if op.pred.iter().any(|i| i == &op.id) {
} else if op.pred.iter().any(|i| i == &self.ops[*pos].id) {
self.ops[*pos].succ.push(op.id);
}
*pos += 1
@ -409,7 +409,7 @@ impl Automerge {
}
} else if self.ops[*pos].visible()
&& self.ops[*pos].elemid() == op.elemid()
&& op.pred.iter().any(|i| i == &op.id)
&& op.pred.iter().any(|i| i == &self.ops[*pos].id)
{
self.ops[*pos].succ.push(op.id);
}
@ -470,11 +470,12 @@ impl Automerge {
}
}
fn insert_op(&mut self, mut op: Op, local: bool) {
fn insert_op(&mut self, mut op: Op, local: bool) -> Op {
let cursor = self.seek_to_op(&mut op, local); //mut to collect pred
if !op.is_del() {
self.ops.insert(cursor.pos, op);
self.ops.insert(cursor.pos, op.clone());
}
op
}
pub fn keys(&self, obj: &ObjId) -> Vec<Key> {
@ -646,7 +647,7 @@ impl Automerge {
let ops = self.import_ops(&change, self.history.len());
self.update_history(change);
for op in ops {
self.insert_op(op, false)
self.insert_op(op, false);
}
}
@ -898,6 +899,7 @@ impl Automerge {
}
// Return those changes in the reverse of the order in which the depth-first search
// found them. This is not necessarily a topological sort, but should usually be close.
added_change_hashes.reverse();
added_change_hashes
.into_iter()
.filter_map(|h| other.get_change_by_hash(&h))