FileTreeDiffGenerator.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /* eslint-disable
  2. no-return-assign,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. import Core from 'overleaf-editor-core'
  14. import logger from '@overleaf/logger'
  15. import * as Errors from './Errors.js'
  16. const { MoveFileOperation, AddFileOperation, EditFileOperation } = Core
  17. export function buildDiff(chunk, fromVersion, toVersion) {
  18. chunk = Core.Chunk.fromRaw(chunk.chunk)
  19. const chunkStartVersion = chunk.getStartVersion()
  20. const diff = _getInitialDiffSnapshot(chunk, fromVersion)
  21. const changes = chunk
  22. .getChanges()
  23. .slice(fromVersion - chunkStartVersion, toVersion - chunkStartVersion)
  24. for (let i = 0; i < changes.length; i++) {
  25. const change = changes[i]
  26. for (const operation of Array.from(change.getOperations())) {
  27. if (operation.pathname === null || operation.pathname === '') {
  28. // skip operations for missing files
  29. logger.warn({ diff, operation }, 'invalid pathname in operation')
  30. } else if (operation instanceof EditFileOperation) {
  31. _applyEditFileToDiff(diff, operation)
  32. } else if (operation instanceof AddFileOperation) {
  33. _applyAddFileToDiff(diff, operation)
  34. } else if (operation instanceof MoveFileOperation) {
  35. if (operation.isRemoveFile()) {
  36. const deletedAtV = fromVersion + i
  37. _applyDeleteFileToDiff(diff, operation, deletedAtV)
  38. } else {
  39. _applyMoveFileToDiff(diff, operation)
  40. }
  41. }
  42. }
  43. }
  44. return Object.values(diff)
  45. }
  46. function _getInitialDiffSnapshot(chunk, fromVersion) {
  47. // Start with a 'diff' which is snapshot of the filetree at the beginning,
  48. // with nothing in the diff marked as changed.
  49. // Use a bare object to protect against reserved names.
  50. const diff = Object.create(null)
  51. const files = _getInitialFiles(chunk, fromVersion)
  52. for (const [pathname, file] of Object.entries(files)) {
  53. diff[pathname] = { pathname, editable: file.isEditable() }
  54. }
  55. return diff
  56. }
  57. function _getInitialFiles(chunk, fromVersion) {
  58. const snapshot = chunk.getSnapshot()
  59. const changes = chunk
  60. .getChanges()
  61. .slice(0, fromVersion - chunk.getStartVersion())
  62. snapshot.applyAll(changes)
  63. return snapshot.fileMap.files
  64. }
  65. function _applyAddFileToDiff(diff, operation) {
  66. return (diff[operation.pathname] = {
  67. pathname: operation.pathname,
  68. operation: 'added',
  69. editable: operation.file.isEditable(),
  70. })
  71. }
  72. function _applyEditFileToDiff(diff, operation) {
  73. const change = diff[operation.pathname]
  74. if ((change != null ? change.operation : undefined) == null) {
  75. // avoid exception for non-existent change
  76. return (diff[operation.pathname] = {
  77. pathname: operation.pathname,
  78. operation: 'edited',
  79. })
  80. }
  81. }
  82. function _applyMoveFileToDiff(diff, operation) {
  83. if (
  84. diff[operation.newPathname] != null &&
  85. diff[operation.newPathname].operation !== 'removed'
  86. ) {
  87. const err = new Errors.InconsistentChunkError(
  88. 'trying to move to file that already exists',
  89. { diff, operation }
  90. )
  91. throw err
  92. }
  93. const change = diff[operation.pathname]
  94. if (change == null) {
  95. logger.warn({ diff, operation }, 'tried to rename non-existent file')
  96. return
  97. }
  98. change.newPathname = operation.newPathname
  99. if (change.operation === 'added') {
  100. // If this file was added this time, just leave it as an add, but
  101. // at the new name.
  102. change.pathname = operation.newPathname
  103. delete change.newPathname
  104. } else {
  105. change.operation = 'renamed'
  106. }
  107. diff[operation.newPathname] = change
  108. return delete diff[operation.pathname]
  109. }
  110. function _applyDeleteFileToDiff(diff, operation, deletedAtV) {
  111. // avoid exception for non-existent change
  112. if (diff[operation.pathname] != null) {
  113. diff[operation.pathname].operation = 'removed'
  114. }
  115. return diff[operation.pathname] != null
  116. ? (diff[operation.pathname].deletedAtV = deletedAtV)
  117. : undefined
  118. }