FileTreeDiffGenerator.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. const change = diff[operation.pathname]
  67. if (change != null) {
  68. // already exists, likely a delete so just cancel that and put the file back to unchanged
  69. if (change.operation !== 'removed') {
  70. const err = new Errors.InconsistentChunkError(
  71. 'trying to add file that already exists',
  72. { diff, operation }
  73. )
  74. throw err
  75. }
  76. delete diff[operation.pathname].operation
  77. return delete diff[operation.pathname].deletedAtV
  78. } else {
  79. return (diff[operation.pathname] = {
  80. pathname: operation.pathname,
  81. operation: 'added',
  82. editable: operation.file.isEditable(),
  83. })
  84. }
  85. }
  86. function _applyEditFileToDiff(diff, operation) {
  87. const change = diff[operation.pathname]
  88. if ((change != null ? change.operation : undefined) == null) {
  89. // avoid exception for non-existent change
  90. return (diff[operation.pathname] = {
  91. pathname: operation.pathname,
  92. operation: 'edited',
  93. })
  94. }
  95. }
  96. function _applyMoveFileToDiff(diff, operation) {
  97. if (
  98. diff[operation.newPathname] != null &&
  99. diff[operation.newPathname].operation !== 'removed'
  100. ) {
  101. const err = new Errors.InconsistentChunkError(
  102. 'trying to move to file that already exists',
  103. { diff, operation }
  104. )
  105. throw err
  106. }
  107. const change = diff[operation.pathname]
  108. if (change == null) {
  109. logger.warn({ diff, operation }, 'tried to rename non-existent file')
  110. return
  111. }
  112. change.newPathname = operation.newPathname
  113. if (change.operation === 'added') {
  114. // If this file was added this time, just leave it as an add, but
  115. // at the new name.
  116. change.pathname = operation.newPathname
  117. delete change.newPathname
  118. } else {
  119. change.operation = 'renamed'
  120. }
  121. diff[operation.newPathname] = change
  122. return delete diff[operation.pathname]
  123. }
  124. function _applyDeleteFileToDiff(diff, operation, deletedAtV) {
  125. // avoid exception for non-existent change
  126. if (diff[operation.pathname] != null) {
  127. diff[operation.pathname].operation = 'removed'
  128. }
  129. return diff[operation.pathname] != null
  130. ? (diff[operation.pathname].deletedAtV = deletedAtV)
  131. : undefined
  132. }