From 5ecacec30458330df5fa6d591dc58e37afb41cd4 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 4 Oct 2019 15:06:38 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- app/assets/javascripts/ide/stores/actions.js | 71 +++++++++----- app/assets/javascripts/ide/stores/actions/file.js | 9 +- .../javascripts/ide/stores/actions/project.js | 84 +++++++++------- .../ide/stores/modules/commit/actions.js | 2 - .../javascripts/ide/stores/mutation_types.js | 4 +- app/assets/javascripts/ide/stores/mutations.js | 107 ++++++++++++--------- .../javascripts/ide/stores/mutations/file.js | 18 ++-- app/assets/javascripts/ide/stores/state.js | 1 - app/assets/javascripts/ide/stores/utils.js | 70 ++++++++++++-- 9 files changed, 235 insertions(+), 131 deletions(-) (limited to 'app/assets/javascripts/ide/stores') diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index 8c0119a1fed..4e18ec58feb 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -9,6 +9,7 @@ import { decorateFiles } from '../lib/files'; import { stageKeys } from '../constants'; import service from '../services'; import router from '../ide_router'; +import eventHub from '../eventhub'; export const redirectToUrl = (self, url) => visitUrl(url); @@ -171,8 +172,10 @@ export const setCurrentBranchId = ({ commit }, currentBranchId) => { export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, tempFile }) => { commit(types.UPDATE_TEMP_FLAG, { path: file.path, tempFile }); - if (file.parentPath) { - dispatch('updateTempFlagForEntry', { file: state.entries[file.parentPath], tempFile }); + const parent = file.parentPath && state.entries[file.parentPath]; + + if (parent) { + dispatch('updateTempFlagForEntry', { file: parent, tempFile }); } }; @@ -199,51 +202,71 @@ export const openNewEntryModal = ({ commit }, { type, path = '' }) => { export const deleteEntry = ({ commit, dispatch, state }, path) => { const entry = state.entries[path]; - + const { prevPath, prevName, prevParentPath } = entry; + const isTree = entry.type === 'tree'; + + if (prevPath) { + dispatch('renameEntry', { + path, + name: prevName, + parentPath: prevParentPath, + }); + dispatch('deleteEntry', prevPath); + return; + } if (state.unusedSeal) dispatch('burstUnusedSeal'); if (entry.opened) dispatch('closeFile', entry); - if (entry.type === 'tree') { + if (isTree) { entry.tree.forEach(f => dispatch('deleteEntry', f.path)); } commit(types.DELETE_ENTRY, path); - dispatch('stageChange', path); + + // Only stage if we're not a directory or a new file + if (!isTree && !entry.tempFile) { + dispatch('stageChange', path); + } dispatch('triggerFilesChange'); }; export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES); -export const renameEntry = ( - { dispatch, commit, state }, - { path, name, entryPath = null, parentPath }, -) => { - const entry = state.entries[entryPath || path]; +export const renameEntry = ({ dispatch, commit, state }, { path, name, parentPath }) => { + const entry = state.entries[path]; + const newPath = parentPath ? `${parentPath}/${name}` : name; - commit(types.RENAME_ENTRY, { path, name, entryPath, parentPath }); + commit(types.RENAME_ENTRY, { path, name, parentPath }); if (entry.type === 'tree') { - const slashedParentPath = parentPath ? `${parentPath}/` : ''; - const targetEntry = entryPath ? entryPath.split('/').pop() : name; - const newParentPath = `${slashedParentPath}${targetEntry}`; - - state.entries[entryPath || path].tree.forEach(f => { + state.entries[newPath].tree.forEach(f => { dispatch('renameEntry', { - path, - name, - entryPath: f.path, - parentPath: newParentPath, + path: f.path, + name: f.name, + parentPath: newPath, }); }); } else { - const newPath = parentPath ? `${parentPath}/${name}` : name; const newEntry = state.entries[newPath]; - commit(types.TOGGLE_FILE_CHANGED, { file: newEntry, changed: true }); + const isRevert = newPath === entry.prevPath; + const isReset = isRevert && !newEntry.changed && !newEntry.tempFile; + const isInChanges = state.changedFiles + .concat(state.stagedFiles) + .some(({ key }) => key === newEntry.key); + + if (isReset) { + commit(types.REMOVE_FILE_FROM_STAGED_AND_CHANGED, newEntry); + } else if (!isInChanges) { + commit(types.ADD_FILE_TO_CHANGED, newPath); + } + + if (!newEntry.tempFile) { + eventHub.$emit(`editor.update.model.dispose.${entry.key}`); + } - if (entry.opened) { + if (newEntry.opened) { router.push(`/project${newEntry.url}`); - commit(types.TOGGLE_FILE_OPEN, entry.path); } } diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index 7627b6e03af..59445afc7a4 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -5,7 +5,7 @@ import eventHub from '../../eventhub'; import service from '../../services'; import * as types from '../mutation_types'; import router from '../../ide_router'; -import { setPageTitle } from '../utils'; +import { setPageTitle, replaceFileUrl } from '../utils'; import { viewerTypes, stageKeys } from '../../constants'; export const closeFile = ({ commit, state, dispatch }, file) => { @@ -67,7 +67,7 @@ export const getFileData = ( commit(types.TOGGLE_LOADING, { entry: file }); - const url = file.prevPath ? file.url.replace(file.path, file.prevPath) : file.url; + const url = file.prevPath ? replaceFileUrl(file.url, file.path, file.prevPath) : file.url; return service .getFileData(joinPaths(gon.relative_url_root || '', url.replace('/-/', '/'))) @@ -186,11 +186,6 @@ export const discardFileChanges = ({ dispatch, state, commit, getters }, path) = dispatch('restoreTree', file.parentPath); } - if (file.movedPath) { - commit(types.DISCARD_FILE_CHANGES, file.movedPath); - commit(types.REMOVE_FILE_FROM_CHANGED, file.movedPath); - } - commit(types.DISCARD_FILE_CHANGES, path); commit(types.REMOVE_FILE_FROM_CHANGED, path); diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index dd8f17e4f3a..20887e7d0ac 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -92,13 +92,27 @@ export const showEmptyState = ({ commit, state }, { projectId, branchId }) => { }); }; -export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, basePath }) => { - dispatch('setCurrentBranchId', branchId); +export const loadFile = ({ dispatch, state }, { basePath }) => { + if (basePath) { + const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath; + const treeEntryKey = Object.keys(state.entries).find( + key => key === path && !state.entries[key].pending, + ); + const treeEntry = state.entries[treeEntryKey]; - if (getters.emptyRepo) { - return dispatch('showEmptyState', { projectId, branchId }); + if (treeEntry) { + dispatch('handleTreeEntryAction', treeEntry); + } else { + dispatch('createTempEntry', { + name: path, + type: 'blob', + }); + } } - return dispatch('getBranchData', { +}; + +export const loadBranch = ({ dispatch }, { projectId, branchId }) => + dispatch('getBranchData', { projectId, branchId, }) @@ -107,42 +121,38 @@ export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, projectId, branchId, }); - dispatch('getFiles', { + return dispatch('getFiles', { projectId, branchId, - }) - .then(() => { - if (basePath) { - const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath; - const treeEntryKey = Object.keys(state.entries).find( - key => key === path && !state.entries[key].pending, - ); - const treeEntry = state.entries[treeEntryKey]; - - if (treeEntry) { - dispatch('handleTreeEntryAction', treeEntry); - } else { - dispatch('createTempEntry', { - name: path, - type: 'blob', - }); - } - } - }) - .catch( - () => - new Error( - sprintf( - __('An error occurred whilst getting files for - %{branchId}'), - { - branchId: `${_.escape(projectId)}/${_.escape(branchId)}`, - }, - false, - ), - ), - ); + }); }) .catch(() => { dispatch('showBranchNotFoundError', branchId); + return Promise.reject(); }); + +export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, basePath }) => { + const currentProject = state.projects[projectId]; + if (getters.emptyRepo) { + return dispatch('showEmptyState', { projectId, branchId }); + } + if (!currentProject || !currentProject.branches[branchId]) { + dispatch('setCurrentBranchId', branchId); + + return dispatch('loadBranch', { projectId, branchId }) + .then(() => dispatch('loadFile', { basePath })) + .catch( + () => + new Error( + sprintf( + __('An error occurred whilst getting files for - %{branchId}'), + { + branchId: `${_.escape(projectId)}/${_.escape(branchId)}`, + }, + false, + ), + ), + ); + } + return Promise.resolve(dispatch('loadFile', { basePath })); }; diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index f767ca92a56..e89ed49318b 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -154,8 +154,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo .then(() => { commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true }); - commit(rootTypes.CLEAR_REPLACED_FILES, null, { root: true }); - setTimeout(() => { commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true }); }, 5000); diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index f021729c451..f0b4718d025 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -59,8 +59,7 @@ export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE'; export const CLEAR_STAGED_CHANGES = 'CLEAR_STAGED_CHANGES'; export const STAGE_CHANGE = 'STAGE_CHANGE'; export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE'; - -export const CLEAR_REPLACED_FILES = 'CLEAR_REPLACED_FILES'; +export const REMOVE_FILE_FROM_STAGED_AND_CHANGED = 'REMOVE_FILE_FROM_STAGED_AND_CHANGED'; export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; @@ -79,5 +78,6 @@ export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE'; export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL'; export const DELETE_ENTRY = 'DELETE_ENTRY'; export const RENAME_ENTRY = 'RENAME_ENTRY'; +export const REVERT_RENAME_ENTRY = 'REVERT_RENAME_ENTRY'; export const RESTORE_TREE = 'RESTORE_TREE'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index ea125214ebb..2587b57a817 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -5,7 +5,14 @@ import mergeRequestMutation from './mutations/merge_request'; import fileMutations from './mutations/file'; import treeMutations from './mutations/tree'; import branchMutations from './mutations/branch'; -import { sortTree } from './utils'; +import { + sortTree, + replaceFileUrl, + swapInParentTreeWithSorting, + updateFileCollections, + removeFromParentTree, + pathsAreEqual, +} from './utils'; export default { [types.SET_INITIAL_DATA](state, data) { @@ -56,11 +63,6 @@ export default { stagedFiles: [], }); }, - [types.CLEAR_REPLACED_FILES](state) { - Object.assign(state, { - replacedFiles: [], - }); - }, [types.SET_ENTRIES](state, entries) { Object.assign(state, { entries, @@ -157,9 +159,14 @@ export default { changed: Boolean(changedFile), staged: false, replaces: false, - prevPath: '', - moved: false, lastCommitSha: lastCommit.commit.id, + + prevId: undefined, + prevPath: undefined, + prevName: undefined, + prevUrl: undefined, + prevKey: undefined, + prevParentPath: undefined, }); if (prevPath) { @@ -209,7 +216,9 @@ export default { entry.deleted = true; - parent.tree = parent.tree.filter(f => f.path !== entry.path); + if (parent) { + parent.tree = parent.tree.filter(f => f.path !== entry.path); + } if (entry.type === 'blob') { if (tempFile) { @@ -219,51 +228,61 @@ export default { } } }, - [types.RENAME_ENTRY](state, { path, name, entryPath = null, parentPath }) { - const oldEntry = state.entries[entryPath || path]; - const slashedParentPath = parentPath ? `${parentPath}/` : ''; - const newPath = entryPath - ? `${slashedParentPath}${oldEntry.name}` - : `${slashedParentPath}${name}`; + [types.RENAME_ENTRY](state, { path, name, parentPath }) { + const oldEntry = state.entries[path]; + const newPath = parentPath ? `${parentPath}/${name}` : name; + const isRevert = newPath === oldEntry.prevPath; - Vue.set(state.entries, newPath, { + const newUrl = replaceFileUrl(oldEntry.url, oldEntry.path, newPath); + + const newKey = oldEntry.key.replace(new RegExp(oldEntry.path, 'g'), newPath); + + const baseProps = { ...oldEntry, + name, id: newPath, - key: `${newPath}-${oldEntry.type}-${oldEntry.path}`, path: newPath, - name: entryPath ? oldEntry.name : name, - tempFile: true, - prevPath: oldEntry.tempFile ? null : oldEntry.path, - url: oldEntry.url.replace(new RegExp(`${oldEntry.path}/?$`), newPath), - tree: [], - raw: '', - opened: false, - parentPath, - }); + url: newUrl, + key: newKey, + parentPath: parentPath || '', + }; - oldEntry.moved = true; - oldEntry.movedPath = newPath; + const prevProps = + oldEntry.tempFile || isRevert + ? { + prevId: undefined, + prevPath: undefined, + prevName: undefined, + prevUrl: undefined, + prevKey: undefined, + prevParentPath: undefined, + } + : { + prevId: oldEntry.prevId || oldEntry.id, + prevPath: oldEntry.prevPath || oldEntry.path, + prevName: oldEntry.prevName || oldEntry.name, + prevUrl: oldEntry.prevUrl || oldEntry.url, + prevKey: oldEntry.prevKey || oldEntry.key, + prevParentPath: oldEntry.prevParentPath || oldEntry.parentPath, + }; - const parent = parentPath - ? state.entries[parentPath] - : state.trees[`${state.currentProjectId}/${state.currentBranchId}`]; - const newEntry = state.entries[newPath]; - - parent.tree = sortTree(parent.tree.concat(newEntry)); + Vue.set(state.entries, newPath, { + ...baseProps, + ...prevProps, + }); - if (newEntry.type === 'blob') { - state.changedFiles = state.changedFiles.concat(newEntry); + if (pathsAreEqual(oldEntry.parentPath, parentPath)) { + swapInParentTreeWithSorting(state, oldEntry.key, newPath, parentPath); + } else { + removeFromParentTree(state, oldEntry.key, oldEntry.parentPath); + swapInParentTreeWithSorting(state, oldEntry.key, newPath, parentPath); } - if (oldEntry.tempFile) { - const filterMethod = f => f.path !== oldEntry.path; - - state.openFiles = state.openFiles.filter(filterMethod); - state.changedFiles = state.changedFiles.filter(filterMethod); - parent.tree = parent.tree.filter(filterMethod); - - Vue.delete(state.entries, oldEntry.path); + if (oldEntry.type === 'blob') { + updateFileCollections(state, oldEntry.key, newPath); } + + Vue.delete(state.entries, oldEntry.path); }, ...projectMutations, diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js index 1442ea7dbfa..8caeb2d73b2 100644 --- a/app/assets/javascripts/ide/stores/mutations/file.js +++ b/app/assets/javascripts/ide/stores/mutations/file.js @@ -138,8 +138,6 @@ export default { content: stagedFile ? stagedFile.content : state.entries[path].raw, changed: false, deleted: false, - moved: false, - movedPath: '', }); if (deleted) { @@ -179,11 +177,6 @@ export default { }); if (stagedFile) { - Object.assign(state, { - replacedFiles: state.replacedFiles.concat({ - ...stagedFile, - }), - }); Object.assign(stagedFile, { ...state.entries[path], }); @@ -252,4 +245,15 @@ export default { openFiles: state.openFiles.filter(f => f.key !== file.key), }); }, + [types.REMOVE_FILE_FROM_STAGED_AND_CHANGED](state, file) { + Object.assign(state, { + changedFiles: state.changedFiles.filter(f => f.key !== file.key), + stagedFiles: state.stagedFiles.filter(f => f.key !== file.key), + }); + + Object.assign(state.entries[file.path], { + changed: false, + staged: false, + }); + }, }; diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index c4da482bf0a..d400b9831a9 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -6,7 +6,6 @@ export default () => ({ currentMergeRequestId: '', changedFiles: [], stagedFiles: [], - replacedFiles: [], endpoints: {}, lastCommitMsg: '', lastCommitPath: '', diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js index 52200ce7847..a8d8ff31afe 100644 --- a/app/assets/javascripts/ide/stores/utils.js +++ b/app/assets/javascripts/ide/stores/utils.js @@ -50,9 +50,7 @@ export const dataStructure = () => ({ lastOpenedAt: 0, mrChange: null, deleted: false, - prevPath: '', - movedPath: '', - moved: false, + prevPath: undefined, }); export const decorateData = entity => { @@ -129,7 +127,7 @@ export const commitActionForFile = file => { export const getCommitFiles = stagedFiles => stagedFiles.reduce((acc, file) => { - if (file.moved || file.type === 'tree') return acc; + if (file.type === 'tree') return acc; return acc.concat({ ...file, @@ -148,9 +146,9 @@ export const createCommitPayload = ({ commit_message: state.commitMessage || getters.preBuiltCommitMessage, actions: getCommitFiles(rootState.stagedFiles).map(f => ({ action: commitActionForFile(f), - file_path: f.moved ? f.movedPath : f.path, - previous_path: f.prevPath === '' ? undefined : f.prevPath, - content: f.prevPath ? null : f.content || undefined, + file_path: f.path, + previous_path: f.prevPath || undefined, + content: f.prevPath && !f.changed ? null : f.content || undefined, encoding: f.base64 ? 'base64' : 'text', last_commit_id: newBranch || f.deleted || f.prevPath || f.replaces ? undefined : f.lastCommitSha, @@ -213,3 +211,61 @@ export const mergeTrees = (fromTree, toTree) => { return toTree; }; + +export const escapeFileUrl = fileUrl => encodeURIComponent(fileUrl).replace(/%2F/g, '/'); + +export const replaceFileUrl = (url, oldPath, newPath) => { + // Add `/-/` so that we don't accidentally replace project path + const result = url.replace(`/-/${escapeFileUrl(oldPath)}`, `/-/${escapeFileUrl(newPath)}`); + + return result; +}; + +export const swapInStateArray = (state, arr, key, entryPath) => + Object.assign(state, { + [arr]: state[arr].map(f => (f.key === key ? state.entries[entryPath] : f)), + }); + +export const getEntryOrRoot = (state, path) => + path ? state.entries[path] : state.trees[`${state.currentProjectId}/${state.currentBranchId}`]; + +export const swapInParentTreeWithSorting = (state, oldKey, newPath, parentPath) => { + if (!newPath) { + return; + } + + const parent = getEntryOrRoot(state, parentPath); + + if (parent) { + const tree = parent.tree + // filter out old entry && new entry + .filter(({ key, path }) => key !== oldKey && path !== newPath) + // concat new entry + .concat(state.entries[newPath]); + + parent.tree = sortTree(tree); + } +}; + +export const removeFromParentTree = (state, oldKey, parentPath) => { + const parent = getEntryOrRoot(state, parentPath); + + if (parent) { + parent.tree = sortTree(parent.tree.filter(({ key }) => key !== oldKey)); + } +}; + +export const updateFileCollections = (state, key, entryPath) => { + ['openFiles', 'changedFiles', 'stagedFiles'].forEach(fileCollection => { + swapInStateArray(state, fileCollection, key, entryPath); + }); +}; + +export const cleanTrailingSlash = path => path.replace(/\/$/, ''); + +export const pathsAreEqual = (a, b) => { + const cleanA = a ? cleanTrailingSlash(a) : ''; + const cleanB = b ? cleanTrailingSlash(b) : ''; + + return cleanA === cleanB; +}; -- cgit v1.2.1