overleaf/services/project-history/app/js/FileTreeDiffGenerator.js

130 lines
4.1 KiB
JavaScript
Raw Normal View History

/* eslint-disable
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import Core from 'overleaf-editor-core'
import logger from '@overleaf/logger'
import * as Errors from './Errors.js'
const { MoveFileOperation, AddFileOperation, EditFileOperation } = Core
export function buildDiff(chunk, fromVersion, toVersion) {
chunk = Core.Chunk.fromRaw(chunk.chunk)
const chunkStartVersion = chunk.getStartVersion()
const diff = _getInitialDiffSnapshot(chunk, fromVersion)
const changes = chunk
.getChanges()
.slice(fromVersion - chunkStartVersion, toVersion - chunkStartVersion)
for (let i = 0; i < changes.length; i++) {
const change = changes[i]
for (const operation of Array.from(change.getOperations())) {
if (operation.pathname === null || operation.pathname === '') {
// skip operations for missing files
logger.warn({ diff, operation }, 'invalid pathname in operation')
} else if (operation instanceof EditFileOperation) {
_applyEditFileToDiff(diff, operation)
} else if (operation instanceof AddFileOperation) {
_applyAddFileToDiff(diff, operation)
} else if (operation instanceof MoveFileOperation) {
if (operation.isRemoveFile()) {
const deletedAtV = fromVersion + i
_applyDeleteFileToDiff(diff, operation, deletedAtV)
} else {
_applyMoveFileToDiff(diff, operation)
}
}
}
}
return Object.values(diff)
}
function _getInitialDiffSnapshot(chunk, fromVersion) {
// Start with a 'diff' which is snapshot of the filetree at the beginning,
// with nothing in the diff marked as changed.
// Use a bare object to protect against reserved names.
const diff = Object.create(null)
const files = _getInitialFiles(chunk, fromVersion)
for (const [pathname, file] of Object.entries(files)) {
diff[pathname] = { pathname, editable: file.isEditable() }
}
return diff
}
function _getInitialFiles(chunk, fromVersion) {
const snapshot = chunk.getSnapshot()
const changes = chunk
.getChanges()
.slice(0, fromVersion - chunk.getStartVersion())
snapshot.applyAll(changes)
return snapshot.fileMap.files
}
function _applyAddFileToDiff(diff, operation) {
return (diff[operation.pathname] = {
pathname: operation.pathname,
operation: 'added',
editable: operation.file.isEditable(),
})
}
function _applyEditFileToDiff(diff, operation) {
const change = diff[operation.pathname]
if ((change != null ? change.operation : undefined) == null) {
// avoid exception for non-existent change
return (diff[operation.pathname] = {
pathname: operation.pathname,
operation: 'edited',
})
}
}
function _applyMoveFileToDiff(diff, operation) {
if (
diff[operation.newPathname] != null &&
diff[operation.newPathname].operation !== 'removed'
) {
const err = new Errors.InconsistentChunkError(
'trying to move to file that already exists',
{ diff, operation }
)
throw err
}
const change = diff[operation.pathname]
if (change == null) {
logger.warn({ diff, operation }, 'tried to rename non-existent file')
return
}
change.newPathname = operation.newPathname
if (change.operation === 'added') {
// If this file was added this time, just leave it as an add, but
// at the new name.
change.pathname = operation.newPathname
delete change.newPathname
} else {
change.operation = 'renamed'
}
diff[operation.newPathname] = change
return delete diff[operation.pathname]
}
function _applyDeleteFileToDiff(diff, operation, deletedAtV) {
// avoid exception for non-existent change
if (diff[operation.pathname] != null) {
diff[operation.pathname].operation = 'removed'
}
return diff[operation.pathname] != null
? (diff[operation.pathname].deletedAtV = deletedAtV)
: undefined
}