function advanceHeads(myOldHeads, myNewHeads, ourOldSharedHeads) {
  const newHeads = myNewHeads.filter(head => !myOldHeads.includes(head))
  const commonHeads = ourOldSharedHeads.filter(head =>
    myNewHeads.includes(head),
  )
  const advancedHeads = [...new Set([...newHeads, ...commonHeads])].sort()
  return advancedHeads
}
Computes the heads that we share with a peer after we have just received some changes from that
peer and applied them. This may not be sufficient to bring our heads in sync with the other
peer's heads, since they may have only sent us a subset of their outstanding changes.

`myOldHeads` are the local heads before the most recent changes were applied, `myNewHeads` are
the local heads after those changes were applied, and `ourOldSharedHeads` is the previous set of
shared heads. Applying the changes will have replaced some heads with others, but some heads may
have remained unchanged (because they are for branches on which no changes have been added). Any
such unchanged heads remain in the sharedHeads. Any sharedHeads that were replaced by applying
changes are also replaced as sharedHeads. This is safe because if we received some changes from
another peer, that means that peer had those changes, and therefore we now both know about them.