in backend/new.js [1868:1920]
getChanges(haveDeps) {
if (!this.haveHashGraph) this.computeHashGraph()
// If the other replica has nothing, return all changes in history order
if (haveDeps.length === 0) {
return this.changes.slice()
}
// Fast path for the common case where all new changes depend only on haveDeps
let stack = [], seenHashes = {}, toReturn = []
for (let hash of haveDeps) {
seenHashes[hash] = true
const successors = this.dependentsByHash[hash]
if (!successors) throw new RangeError(`hash not found: ${hash}`)
stack.push(...successors)
}
// Depth-first traversal of the hash graph to find all changes that depend on `haveDeps`
while (stack.length > 0) {
const hash = stack.pop()
seenHashes[hash] = true
toReturn.push(hash)
if (!this.dependenciesByHash[hash].every(dep => seenHashes[dep])) {
// If a change depends on a hash we have not seen, abort the traversal and fall back to the
// slower algorithm. This will sometimes abort even if all new changes depend on `haveDeps`,
// because our depth-first traversal is not necessarily a topological sort of the graph.
break
}
stack.push(...this.dependentsByHash[hash])
}
// If the traversal above has encountered all the heads, and was not aborted early due to
// a missing dependency, then the set of changes it has found is complete, so we can return it
if (stack.length === 0 && this.heads.every(head => seenHashes[head])) {
return toReturn.map(hash => this.changes[this.changeIndexByHash[hash]])
}
// If we haven't encountered all of the heads, we have to search harder. This will happen if
// changes were added that are concurrent to `haveDeps`
stack = haveDeps.slice()
seenHashes = {}
while (stack.length > 0) {
const hash = stack.pop()
if (!seenHashes[hash]) {
const deps = this.dependenciesByHash[hash]
if (!deps) throw new RangeError(`hash not found: ${hash}`)
stack.push(...deps)
seenHashes[hash] = true
}
}
return this.changes.filter(change => !seenHashes[decodeChangeMeta(change, true).hash])
}