Merge pull request #13039 from overleaf/td-history-auto-select-file-ignore-binary

History migration: Ignore binary files when auto-selecting file to display in history view

GitOrigin-RevId: 7d2a314cdb3d54e4e2292a95e7633e3829ea931f
This commit is contained in:
Tim Down 2023-05-25 11:20:57 +01:00 committed by Copybot
parent 9a273949fd
commit 66dc566752
8 changed files with 211 additions and 77 deletions

View file

@ -54,21 +54,20 @@ function _getInitialDiffSnapshot(chunk, fromVersion) {
// with nothing in the diff marked as changed.
// Use a bare object to protect against reserved names.
const diff = Object.create(null)
const pathnames = _getInitialPathnames(chunk, fromVersion)
for (const pathname of Array.from(pathnames)) {
diff[pathname] = { pathname }
const files = _getInitialFiles(chunk, fromVersion)
for (const [pathname, file] of Object.entries(files)) {
diff[pathname] = { pathname, editable: file.isEditable() }
}
return diff
}
function _getInitialPathnames(chunk, fromVersion) {
function _getInitialFiles(chunk, fromVersion) {
const snapshot = chunk.getSnapshot()
const changes = chunk
.getChanges()
.slice(0, fromVersion - chunk.getStartVersion())
snapshot.applyAll(changes)
const pathnames = snapshot.getFilePathnames()
return pathnames
return snapshot.fileMap.files
}
function _applyAddFileToDiff(diff, operation) {
@ -88,6 +87,7 @@ function _applyAddFileToDiff(diff, operation) {
return (diff[operation.pathname] = {
pathname: operation.pathname,
operation: 'added',
editable: operation.file.isEditable(),
})
}
}

View file

@ -123,6 +123,7 @@ describe('FileTree Diffs', function () {
{
file: {
hash: sha('new-sha'),
stringLength: 42,
},
pathname: 'added.tex',
},
@ -155,15 +156,18 @@ describe('FileTree Diffs', function () {
pathname: 'deleted.tex',
operation: 'removed',
deletedAtV: 5,
editable: true,
},
{
newPathname: 'newName.tex',
pathname: 'renamed.tex',
operation: 'renamed',
editable: true,
},
{
pathname: 'added.tex',
operation: 'added',
editable: true,
},
],
})
@ -269,6 +273,7 @@ describe('FileTree Diffs', function () {
{
file: {
hash: sha('new-sha'),
stringLength: 42,
},
pathname: 'added.tex',
},
@ -313,20 +318,24 @@ describe('FileTree Diffs', function () {
},
{
pathname: 'baz.tex',
editable: true,
},
{
pathname: 'deleted.tex',
operation: 'removed',
deletedAtV: 4,
editable: true,
},
{
newPathname: 'newName.tex',
pathname: 'renamed.tex',
operation: 'renamed',
editable: true,
},
{
pathname: 'added.tex',
operation: 'added',
editable: true,
},
],
})
@ -391,6 +400,7 @@ describe('FileTree Diffs', function () {
newPathname: 'three.tex',
pathname: 'one.tex',
operation: 'renamed',
editable: true,
},
],
})
@ -455,6 +465,7 @@ describe('FileTree Diffs', function () {
diff: [
{
pathname: 'one.tex',
editable: true,
},
],
})
@ -523,6 +534,7 @@ describe('FileTree Diffs', function () {
pathname: 'two.tex',
newPathname: 'one.tex',
operation: 'renamed',
editable: true,
},
],
})
@ -547,6 +559,7 @@ describe('FileTree Diffs', function () {
pathname: 'one.tex',
file: {
hash: sha('mock-sha'),
stringLength: 42,
},
},
],
@ -583,6 +596,7 @@ describe('FileTree Diffs', function () {
{
pathname: 'two.tex',
operation: 'added',
editable: true,
},
],
})

View file

@ -1,24 +1,30 @@
import { FileOperation } from './file-operation'
export interface FileUnchanged {
interface File {
pathname: string
}
export interface FileAdded extends FileUnchanged {
export interface FileWithEditable extends File {
editable: boolean
}
export type FileUnchanged = FileWithEditable
export interface FileAdded extends FileWithEditable {
operation: Extract<FileOperation, 'added'>
}
export interface FileRemoved extends FileUnchanged {
export interface FileRemoved extends FileWithEditable {
operation: Extract<FileOperation, 'removed'>
newPathname?: string
deletedAtV: number
}
export interface FileEdited extends FileUnchanged {
export interface FileEdited extends File {
operation: Extract<FileOperation, 'edited'>
}
export interface FileRenamed extends FileUnchanged {
export interface FileRenamed extends FileWithEditable {
newPathname?: string
oldPathname?: string
operation: Extract<FileOperation, 'renamed'>

View file

@ -1,12 +1,12 @@
import _ from 'lodash'
import type { Nullable } from '../../../../../types/utils'
import type { FileDiff } from '../services/types/file'
import type { FileOperation } from '../services/types/file-operation'
import type { LoadedUpdate, Version } from '../services/types/update'
import { fileFinalPathname } from './file-diff'
import { fileFinalPathname, isFileEditable } from './file-diff'
type FileWithOps = {
pathname: FileDiff['pathname']
editable: boolean
operation: FileOperation
}
@ -20,54 +20,70 @@ function getFilesWithOps(
const filesWithOps: FileWithOps[] = []
if (updateForToV) {
const filesByPathname = new Map<string, FileDiff>()
for (const file of files) {
const pathname = fileFinalPathname(file)
filesByPathname.set(pathname, file)
}
const isEditable = (pathname: string) => {
const fileDiff = filesByPathname.get(pathname)
if (!fileDiff) {
return false
}
return isFileEditable(fileDiff)
}
for (const pathname of updateForToV.pathnames) {
filesWithOps.push({
pathname,
editable: isEditable(pathname),
operation: 'edited',
})
}
for (const op of updateForToV.project_ops) {
let fileWithOps: Nullable<FileWithOps> = null
let pathAndOp: Nullable<Pick<FileWithOps, 'pathname' | 'operation'>> =
null
if (op.add) {
fileWithOps = {
pathAndOp = {
pathname: op.add.pathname,
operation: 'added',
}
} else if (op.remove) {
fileWithOps = {
pathAndOp = {
pathname: op.remove.pathname,
operation: 'removed',
}
} else if (op.rename) {
fileWithOps = {
pathAndOp = {
pathname: op.rename.newPathname,
operation: 'renamed',
}
}
if (fileWithOps !== null) {
filesWithOps.push(fileWithOps)
if (pathAndOp !== null) {
filesWithOps.push({
editable: isEditable(pathAndOp.pathname),
...pathAndOp,
})
}
}
}
return filesWithOps
} else {
const filesWithOps = _.reduce(
files,
(curFilesWithOps, file) => {
if ('operation' in file) {
curFilesWithOps.push({
pathname: file.pathname,
operation: file.operation,
})
}
return curFilesWithOps
},
<FileWithOps[]>[]
)
const filesWithOps = files.reduce((curFilesWithOps, file) => {
if ('operation' in file) {
curFilesWithOps.push({
pathname: file.pathname,
editable: isFileEditable(file),
operation: file.operation,
})
}
return curFilesWithOps
}, <FileWithOps[]>[])
return filesWithOps
}
@ -86,44 +102,25 @@ export function autoSelectFile(
comparing: boolean,
updateForToV: LoadedUpdate | undefined
): FileDiff {
let fileToSelect: Nullable<FileDiff> = null
const filesWithOps = getFilesWithOps(files, toV, comparing, updateForToV)
for (const opType of orderedOpTypes) {
const fileWithMatchingOpType = _.find(filesWithOps, {
operation: opType,
})
const fileWithMatchingOpType = filesWithOps.find(
file => file.operation === opType && file.editable
)
if (fileWithMatchingOpType != null) {
fileToSelect =
_.find(
files,
file => fileFinalPathname(file) === fileWithMatchingOpType.pathname
) ?? null
break
}
}
if (!fileToSelect) {
const mainFile = _.find(files, function (file) {
return /main\.tex$/.test(file.pathname)
})
if (mainFile) {
fileToSelect = mainFile
} else {
const anyTeXFile = _.find(files, function (file) {
return /\.tex$/.test(file.pathname)
})
if (anyTeXFile) {
fileToSelect = anyTeXFile
} else {
fileToSelect = files[0]
if (fileWithMatchingOpType) {
const fileToSelect = files.find(
file => fileFinalPathname(file) === fileWithMatchingOpType.pathname
)
if (fileToSelect) {
return fileToSelect
}
}
}
return fileToSelect
return (
files.find(file => /main\.tex$/.test(file.pathname)) ||
files.find(file => /\.tex$/.test(file.pathname)) ||
files[0]
)
}

View file

@ -1,4 +1,9 @@
import type { FileDiff, FileRemoved, FileRenamed } from '../services/types/file'
import type {
FileDiff,
FileRemoved,
FileRenamed,
FileWithEditable,
} from '../services/types/file'
export function isFileRenamed(fileDiff: FileDiff): fileDiff is FileRenamed {
return (fileDiff as FileRenamed).operation === 'renamed'
@ -8,6 +13,18 @@ export function isFileRemoved(fileDiff: FileDiff): fileDiff is FileRemoved {
return (fileDiff as FileRemoved).operation === 'removed'
}
export function fileFinalPathname(fileDiff: FileDiff) {
return isFileRenamed(fileDiff) ? fileDiff.newPathname : fileDiff.pathname
function isFileWithEditable(fileDiff: FileDiff): fileDiff is FileWithEditable {
return 'editable' in (fileDiff as FileWithEditable)
}
export function isFileEditable(fileDiff: FileDiff) {
return isFileWithEditable(fileDiff)
? fileDiff.editable
: fileDiff.operation === 'edited'
}
export function fileFinalPathname(fileDiff: FileDiff) {
return (
(isFileRenamed(fileDiff) ? fileDiff.newPathname : null) || fileDiff.pathname
)
}

View file

@ -1,6 +1,6 @@
import _ from 'lodash'
import type { FileDiff, FileRenamed } from '../services/types/file'
import { isFileRemoved } from './file-diff'
import { isFileEditable, isFileRemoved } from './file-diff'
export type FileTreeEntity = {
name?: string
@ -37,6 +37,7 @@ export function reducePathsToTree(
type: 'folder',
children: <FileTreeEntity[]>[],
pathname: pathPart,
editable: false,
}
currentFileTreeLocation.push(fileTreeEntity)
@ -68,17 +69,12 @@ export function fileTreeDiffToFileTreeData(
if (file.type === 'file') {
const deletedAtV = isFileRemoved(file) ? file.deletedAtV : undefined
let newDoc: HistoryDoc = {
const newDoc: HistoryDoc = {
pathname: file.pathname ?? '',
name: file.name ?? '',
deletedAtV,
}
if ('operation' in file) {
newDoc = {
...newDoc,
operation: file.operation,
}
editable: isFileEditable(file),
operation: 'operation' in file ? file.operation : undefined,
}
docs.push(newDoc)
@ -115,5 +111,6 @@ export function renamePathnameKey(file: FileRenamed): FileRenamed {
oldPathname: file.pathname,
pathname: file.newPathname as string,
operation: file.operation,
editable: file.editable,
}
}

View file

@ -43,13 +43,16 @@ describe('history toolbar', function () {
},
{
pathname: 'sample.bib',
editable: true,
},
{
pathname: 'frog.jpg',
editable: false,
},
],
selectedFile: {
pathname: 'main.tex',
editable: true,
},
}
@ -83,18 +86,22 @@ describe('history toolbar', function () {
{
pathname: 'main.tex',
operation: 'added',
editable: true,
},
{
pathname: 'sample.bib',
operation: 'added',
editable: true,
},
{
pathname: 'frog.jpg',
operation: 'added',
editable: false,
},
],
selectedFile: {
pathname: 'main.tex',
editable: true,
},
}

View file

@ -23,18 +23,23 @@ describe('autoSelectFile', function () {
const files: FileDiff[] = [
{
pathname: 'main.tex',
editable: true,
},
{
pathname: 'sample.bib',
editable: true,
},
{
pathname: 'frog.jpg',
editable: false,
},
{
pathname: 'newfile5.tex',
editable: true,
},
{
pathname: 'newfolder1/newfolder2/newfile2.tex',
editable: true,
},
{
pathname: 'newfolder1/newfile10.tex',
@ -276,18 +281,22 @@ describe('autoSelectFile', function () {
{
pathname: 'main.tex',
operation: 'added',
editable: true,
},
{
pathname: 'sample.bib',
operation: 'added',
editable: true,
},
{
pathname: 'frog.jpg',
operation: 'added',
editable: false,
},
{
pathname: 'newfile1.tex',
operation: 'added',
editable: true,
},
]
@ -347,17 +356,21 @@ describe('autoSelectFile', function () {
pathname: 'main.tex',
operation: 'removed',
deletedAtV: 6,
editable: true,
},
{
pathname: 'sample.bib',
editable: true,
},
{
pathname: 'main2.tex',
operation: 'added',
editable: true,
},
{
pathname: 'main3.tex',
operation: 'added',
editable: true,
},
]
@ -446,18 +459,22 @@ describe('autoSelectFile', function () {
const files: FileDiff[] = [
{
pathname: 'main.tex',
editable: true,
},
{
pathname: 'sample.bib',
editable: true,
},
{
pathname: 'frog.jpg',
editable: false,
},
{
pathname: 'newfolder/maybewillbedeleted.tex',
newPathname: 'newfolder2/maybewillbedeleted.tex',
operation: 'removed',
deletedAtV: 10,
editable: true,
},
]
@ -617,12 +634,15 @@ describe('autoSelectFile', function () {
const files: FileDiff[] = [
{
pathname: 'certainly_not_main.tex',
editable: true,
},
{
pathname: 'newfile.tex',
editable: true,
},
{
pathname: 'file2.tex',
editable: true,
},
]
@ -725,11 +745,13 @@ describe('autoSelectFile', function () {
const files: FileDiff[] = [
{
pathname: 'main.tex',
editable: true,
},
{
pathname: 'original.bib',
newPathname: 'new.bib',
operation: 'renamed',
editable: true,
},
]
@ -792,5 +814,79 @@ describe('autoSelectFile', function () {
expect(pathname).to.equal('new.bib')
})
it('ignores binary file', function () {
const files: FileDiff[] = [
{
pathname: 'frog.jpg',
editable: false,
operation: 'added',
},
{
pathname: 'main.tex',
editable: true,
},
{
pathname: 'sample.bib',
editable: true,
},
]
const updates: LoadedUpdate[] = [
{
fromV: 4,
toV: 7,
meta: {
users: historyUsers,
start_ts: 1680874742389,
end_ts: 1680874755552,
},
labels: [],
pathnames: [],
project_ops: [
{
add: {
pathname: 'frog.jpg',
},
atV: 5,
},
],
},
{
fromV: 0,
toV: 4,
meta: {
users: historyUsers,
start_ts: 1680861975947,
end_ts: 1680861988442,
},
labels: [],
pathnames: [],
project_ops: [
{
add: {
pathname: 'sample.bib',
},
atV: 1,
},
{
add: {
pathname: 'main.tex',
},
atV: 0,
},
],
},
]
const { pathname } = autoSelectFile(
files,
updates[0].toV,
comparing,
getUpdateForVersion(updates[0].toV, updates)
)
expect(pathname).to.equal('main.tex')
})
})
})