Files
overleaf-cep/libraries/overleaf-editor-core/lib/rebase.js
T
Jakob AckermannandClaude Opus 5 7ae8db6b12 [editor-core] extract shared pieces for history-ot in editor (#36828)
* [editor-core] share the pieces the editor, real-time and history-v1 all need

Project-level history-OT has the browser, real-time and history-v1 all reason
about the same changes, so the rules they reason with move into the shared
library rather than being reimplemented on each side.

- `origin/editor_origin.js` plus `EDITOR_ORIGIN_KIND` and its schema entry: the
  origin an editor client's change carries, holding the editorId that scopes a
  change's timestamp.
- `change_identity.js`: how a submission is recognised when it comes back, so a
  resend of a change that already landed is deduplicated rather than applied
  twice.
- `rebase.js`: transforming a change onto concurrent ones, in the same order on
  both sides, which is what lets the server acknowledge a change with only the
  version it landed at.
- `text_file_defaults.js`: the doc-vs-file extension lists, now read from here
  by web's settings instead of being spelled out again.
- `blob_string_length.js`: the string length of a blob, used by history-v1's
  blob store and by web when it writes an uploaded file into history.
- `file_tree_diff.js`: file-tree operations to a diff, used by
  project-history's FileTreeDiffGenerator.
- `Blob.EMPTY_HASH`, so callers can answer for the empty blob without storing
  it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [project-history] build the file-tree diff with the shared helper

FileTreeDiffGenerator's hand-rolled walk over add/edit/move/delete operations
becomes a call to `buildFileTreeDiff` from overleaf-editor-core, so the editor
and project-history read a file-tree change the same way. Adds the unit tests
the decaffeinated original never had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [web] read the editable-file lists from overleaf-editor-core

web's settings spelled out the text extensions and editable filenames that
decide whether a file becomes a doc, and every other service that has to make
that call carried its own copy. The lists now live in overleaf-editor-core and
web builds settings.textExtensions and settings.editableFilenames from them,
keeping the ADDITIONAL_TEXT_EXTENSIONS override for Server CE/Pro on top.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [history-v1] read a blob's string length from overleaf-editor-core

Two copies of getStringLengthOfFile lived here, the one in recover_zip.js
carrying a TODO asking for exactly this. Both now import the shared
implementation, which the blob store keeps re-exporting for backupArchiver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
GitOrigin-RevId: 31948332f416f56848e3b0dfb5142a4c9f93f508
2026-09-17 12:06:17 +00:00

54 lines
1.7 KiB
JavaScript

// @ts-check
'use strict'
const Operation = require('./operation')
/**
* @typedef {import('./change')} Change
*/
/**
* Rebase changes onto the changes another writer got in first.
*
* `Operation.transformMultiple` rewrites *both* operation lists in place, which
* is what makes a sequence of our changes come out right: each of ours is
* transformed against their change as it stands after our earlier ones, rather
* than against the original.
*
* Operations that transform down to a no-op are pruned, and a change left with
* none is dropped, so the result contains only changes that still carry an
* operation. An empty result means everything we had was already accounted for
* by theirs.
*
* Only the operations are touched. A change's `origin`, `authors`, `v2Authors`
* and `timestamp` travel through untouched, which is what lets a caller
* recognise one of its own changes read back from history after it has been
* rebased.
*
* @param {Change[]} ours modified in place
* @param {Change[]} theirs the intervening changes, in version order
* @return {Change[]} the subset of `ours` that still carries an operation
*/
function rebaseChanges(ours, theirs) {
for (const change of theirs) {
const theirOperations = change.getOperations()
for (const ourChange of ours) {
Operation.transformMultiple(ourChange.getOperations(), theirOperations)
}
}
const rebased = []
for (const change of ours) {
const operations = change
.getOperations()
.filter(operation => !operation.isNoOp())
if (operations.length === 0) continue
change.setOperations(operations)
rebased.push(change)
}
return rebased
}
module.exports = rebaseChanges